feat: 删除故障分类冗余选项 + 报告口径统一 + SSRF 非规范IP加固
- 故障大类删除'其他'、故障小类删除'无故障'(TicketForm/ProcessForm/TicketList) - 周报按'真实故障+设备类型'分章节(去 ticketType 判断,新增 isRealFault 排除 无故障/误判/空) - 月报口径与周报对齐,统一排除'误判' - 监控设置 SSRF 加固:拒绝整数/十六进制/八进制/短式等非规范数字型 IP,防绕过内网黑名单
This commit is contained in:
parent
f7bc9fd5fb
commit
7ea37afc75
|
|
@ -19,11 +19,22 @@ function isPrivateIPv4(host: string): boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拒绝非规范数字型 IP 写法(整数/十六进制/八进制/短式),防止绕过内网黑名单:
|
||||||
|
// net.isIPv4 只认标准点分十进制,但 socket 层会把 2130706433 / 0x7f000001 / 127.1 / 0177.0.0.1
|
||||||
|
// 等写法同样解析为回环/内网地址,必须在校验层拦截
|
||||||
|
function isNonCanonicalNumericHost(host: string): boolean {
|
||||||
|
if (/(^|\.)0x/i.test(host)) return true // 十六进制:0x7f000001 / 0x7f.0.0.1
|
||||||
|
const segs = host.split('.')
|
||||||
|
if (segs.every((s) => /^\d+$/.test(s)) && !net.isIPv4(host)) return true // 整数型/短式/八进制数字 IP(含前导零)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 主机名格式校验(纵深防御):只允许合法主机名/IP 字符(IPv6 字面量含 ':' 天然被拒),
|
// 主机名格式校验(纵深防御):只允许合法主机名/IP 字符(IPv6 字面量含 ':' 天然被拒),
|
||||||
// 拒绝 URL/路径/空格及内网 IPv4,收窄 SSRF 面
|
// 拒绝 URL/路径/空格、非规范数字型 IP 及内网 IPv4,收窄 SSRF 面
|
||||||
const isValidHost = (v: unknown): boolean => {
|
const isValidHost = (v: unknown): boolean => {
|
||||||
if (typeof v !== 'string' || v.length === 0 || v.length > 253) return false
|
if (typeof v !== 'string' || v.length === 0 || v.length > 253) return false
|
||||||
if (!/^[a-zA-Z0-9.-]+$/.test(v)) return false
|
if (!/^[a-zA-Z0-9.-]+$/.test(v)) return false
|
||||||
|
if (isNonCanonicalNumericHost(v)) return false
|
||||||
if (isPrivateIPv4(v)) return false
|
if (isPrivateIPv4(v)) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ interface ProcessFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const FAULT_CATEGORIES = [
|
const FAULT_CATEGORIES = [
|
||||||
'硬件故障', '软件故障', '网络故障', '存储故障', '电源故障', '无故障', '其他',
|
'硬件故障', '软件故障', '网络故障', '存储故障', '电源故障', '无故障',
|
||||||
]
|
]
|
||||||
|
|
||||||
const TICKET_TYPES = ['OEM诊断', 'OEM维修']
|
const TICKET_TYPES = ['OEM诊断', 'OEM维修']
|
||||||
|
|
@ -35,7 +35,7 @@ const TICKET_TYPES = ['OEM诊断', 'OEM维修']
|
||||||
const FAULT_SUBCATEGORIES = [
|
const FAULT_SUBCATEGORIES = [
|
||||||
'GPU故障', 'CPU故障', '内存故障', '硬盘故障', '电源故障', '风扇故障',
|
'GPU故障', 'CPU故障', '内存故障', '硬盘故障', '电源故障', '风扇故障',
|
||||||
'OS崩溃', '驱动故障', '软件崩溃', '配置错误', '网络丢包', '网络中断',
|
'OS崩溃', '驱动故障', '软件崩溃', '配置错误', '网络丢包', '网络中断',
|
||||||
'存储掉盘', 'RAID故障', '无故障', '其他',
|
'存储掉盘', 'RAID故障', '其他',
|
||||||
]
|
]
|
||||||
|
|
||||||
const HANDLER_OPTIONS = ['腾讯', '图灵']
|
const HANDLER_OPTIONS = ['腾讯', '图灵']
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ interface TicketFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFaultCategories = [
|
const defaultFaultCategories = [
|
||||||
'硬件故障', '软件故障', '网络故障', '存储故障', '电源故障', '无故障', '其他',
|
'硬件故障', '软件故障', '网络故障', '存储故障', '电源故障', '无故障',
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultTicketTypes = ['OEM诊断', 'OEM维修']
|
const defaultTicketTypes = ['OEM诊断', 'OEM维修']
|
||||||
|
|
@ -19,7 +19,7 @@ const defaultTicketTypes = ['OEM诊断', 'OEM维修']
|
||||||
const defaultFaultSubcategories = [
|
const defaultFaultSubcategories = [
|
||||||
'GPU故障', 'CPU故障', '内存故障', '硬盘故障', '电源故障', '风扇故障',
|
'GPU故障', 'CPU故障', '内存故障', '硬盘故障', '电源故障', '风扇故障',
|
||||||
'OS崩溃', '驱动故障', '软件崩溃', '配置错误', '网络丢包', '网络中断',
|
'OS崩溃', '驱动故障', '软件崩溃', '配置错误', '网络丢包', '网络中断',
|
||||||
'存储掉盘', 'RAID故障', '无故障', '其他',
|
'存储掉盘', 'RAID故障', '其他',
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function TicketForm({ initialData, ticketId }: TicketFormProps) {
|
export default function TicketForm({ initialData, ticketId }: TicketFormProps) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const statusMap: Record<string, { label: string; variant: 'default' | 'info' | '
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_OPTIONS = ['open', 'in_progress', 'resolved', 'closed']
|
const STATUS_OPTIONS = ['open', 'in_progress', 'resolved', 'closed']
|
||||||
const FAULT_CATEGORY_OPTIONS = ['硬件故障', '软件故障', '网络故障', '存储故障', '电源故障', '无故障', '其他']
|
const FAULT_CATEGORY_OPTIONS = ['硬件故障', '软件故障', '网络故障', '存储故障', '电源故障', '无故障']
|
||||||
|
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
{ key: 'id', label: '工单号', sortable: true, filterable: true, filterType: 'text' as const },
|
{ key: 'id', label: '工单号', sortable: true, filterable: true, filterType: 'text' as const },
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ export async function collectMonthlyReportData(
|
||||||
const dates = getDateRange(periodStart, periodEnd)
|
const dates = getDateRange(periodStart, periodEnd)
|
||||||
// 排除"无故障"分类和"其他"子分类(其他工单不计入节点在线数)
|
// 排除"无故障"分类和"其他"子分类(其他工单不计入节点在线数)
|
||||||
const monthFaults = tickets.filter(t =>
|
const monthFaults = tickets.filter(t =>
|
||||||
t.fault_category !== '无故障' && t.fault_subcategory !== '其他'
|
t.fault_category !== '无故障' && t.fault_category !== '误判' && t.fault_subcategory !== '其他'
|
||||||
)
|
)
|
||||||
|
|
||||||
const dailyStats: DailyOnlineStats[] = dates.map(date => {
|
const dailyStats: DailyOnlineStats[] = dates.map(date => {
|
||||||
|
|
@ -137,7 +137,7 @@ export async function collectMonthlyReportData(
|
||||||
})
|
})
|
||||||
|
|
||||||
// 5. 第二章:运营数据总览(仅 gpu/storage,排除"无故障"和"其他"工单)
|
// 5. 第二章:运营数据总览(仅 gpu/storage,排除"无故障"和"其他"工单)
|
||||||
const gpuStorageTickets = tickets.filter(t => t.device_type !== 'other' && t.fault_category !== '无故障' && t.fault_subcategory !== '其他')
|
const gpuStorageTickets = tickets.filter(t => t.device_type !== 'other' && t.fault_category !== '无故障' && t.fault_category !== '误判' && t.fault_subcategory !== '其他')
|
||||||
const chapter2Map = new Map<string, Chapter2FaultItem[]>()
|
const chapter2Map = new Map<string, Chapter2FaultItem[]>()
|
||||||
for (const t of gpuStorageTickets) {
|
for (const t of gpuStorageTickets) {
|
||||||
const assignDate = t.assign_time.slice(0, 10)
|
const assignDate = t.assign_time.slice(0, 10)
|
||||||
|
|
@ -256,7 +256,7 @@ export async function collectMonthlyReportData(
|
||||||
/** 从采集数据构建 metadata JSON(供 API 路由和 generate 函数共用) */
|
/** 从采集数据构建 metadata JSON(供 API 路由和 generate 函数共用) */
|
||||||
export function buildMonthlyMetadata(data: MonthlyReportData): string {
|
export function buildMonthlyMetadata(data: MonthlyReportData): string {
|
||||||
const gpuStorageTickets = data.tickets.filter(t =>
|
const gpuStorageTickets = data.tickets.filter(t =>
|
||||||
t.device_type !== 'other' && t.fault_category !== '无故障' && t.fault_subcategory !== '其他'
|
t.device_type !== 'other' && t.fault_category !== '无故障' && t.fault_category !== '误判' && t.fault_subcategory !== '其他'
|
||||||
)
|
)
|
||||||
const resolvedTickets = gpuStorageTickets.filter(t => !t.isOngoing)
|
const resolvedTickets = gpuStorageTickets.filter(t => !t.isOngoing)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,20 +92,20 @@ export async function collectWeeklyReportData(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 4. 按规则分三类
|
// 4. 按规则分三类(是否有真实故障 + 设备类型)
|
||||||
// GPU服务器故障:OEM维修 + fault_category != '无故障' + device_type = 'gpu'
|
const isRealFault = (t: typeof entries[number]): boolean =>
|
||||||
|
t.faultCategory !== null && t.faultCategory !== '无故障' && t.faultCategory !== '误判'
|
||||||
|
|
||||||
|
// GPU服务器故障
|
||||||
const gpuFaultTickets = entries.filter(t =>
|
const gpuFaultTickets = entries.filter(t =>
|
||||||
t.ticketType === 'OEM维修' && t.faultCategory !== '无故障' && t.deviceType === 'gpu'
|
isRealFault(t) && t.deviceType === 'gpu'
|
||||||
)
|
)
|
||||||
// 存储服务器故障
|
// 存储服务器故障
|
||||||
const storageFaultTickets = entries.filter(t =>
|
const storageFaultTickets = entries.filter(t =>
|
||||||
t.ticketType === 'OEM维修' && t.faultCategory !== '无故障' && t.deviceType === 'storage'
|
isRealFault(t) && t.deviceType === 'storage'
|
||||||
)
|
|
||||||
// 其他工单:OEM诊断 + (OEM维修且fault_category='无故障')
|
|
||||||
const otherTickets = entries.filter(t =>
|
|
||||||
t.ticketType === 'OEM诊断' ||
|
|
||||||
(t.ticketType === 'OEM维修' && t.faultCategory === '无故障')
|
|
||||||
)
|
)
|
||||||
|
// 其他工单:无故障、误判、或 deviceType 未匹配
|
||||||
|
const otherTickets = entries.filter(t => !isRealFault(t))
|
||||||
|
|
||||||
// 5. 每日在线统计(GPU + 存储分别统计)
|
// 5. 每日在线统计(GPU + 存储分别统计)
|
||||||
const dates = getDateRange(periodStart, periodEnd)
|
const dates = getDateRange(periodStart, periodEnd)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue