97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
// SLA 服务可用性档位定义
|
||
// 参考:mail_monitor_wechat/README.md 服务可用性档位说明
|
||
|
||
export interface SlaTier {
|
||
name: string
|
||
availability: number
|
||
penalty: number
|
||
ratio: number | null // 当月总秒数占比,第五档 ratio 为 null(无上限)
|
||
}
|
||
|
||
export const SLA_TIERS: SlaTier[] = [
|
||
{ name: '第一档', availability: 0.99, penalty: 0, ratio: 0.01 },
|
||
{ name: '第二档', availability: 0.97, penalty: 0.10, ratio: 0.03 },
|
||
{ name: '第三档', availability: 0.95, penalty: 0.25, ratio: 0.05 },
|
||
{ name: '第四档', availability: 0.90, penalty: 0.50, ratio: 0.10 },
|
||
{ name: '第五档', availability: 0, penalty: 1.00, ratio: null },
|
||
]
|
||
|
||
export interface SlaStatus {
|
||
level: 'safe' | 'warning' | 'over-tier1' | 'critical' | 'excluded'
|
||
color: string
|
||
text: string
|
||
}
|
||
|
||
/**
|
||
* 返回指定年月的总秒数
|
||
*/
|
||
export function getMonthSeconds(year: number, month: number): number {
|
||
const daysInMonth = new Date(year, month, 0).getDate()
|
||
return daysInMonth * 24 * 3600
|
||
}
|
||
|
||
/**
|
||
* 返回第一档 SLA 截止时间
|
||
* 截止 = assign_time + 当月总秒数 × 1%
|
||
*/
|
||
export function getTier1Deadline(assignTime: string): Date {
|
||
const at = new Date(assignTime)
|
||
const monthSec = getMonthSeconds(at.getFullYear(), at.getMonth() + 1)
|
||
const tier1Window = monthSec * 0.01 // 1%
|
||
return new Date(at.getTime() + tier1Window * 1000)
|
||
}
|
||
|
||
/**
|
||
* 返回第二档 SLA 截止时间(用于"严重"判定)
|
||
*/
|
||
export function getTier2Deadline(assignTime: string): Date {
|
||
const at = new Date(assignTime)
|
||
const monthSec = getMonthSeconds(at.getFullYear(), at.getMonth() + 1)
|
||
const tier2Window = monthSec * 0.03 // 3%
|
||
return new Date(at.getTime() + tier2Window * 1000)
|
||
}
|
||
|
||
/**
|
||
* 根据 assign_time 和 counted_in_sla 返回 SLA 状态
|
||
*/
|
||
export function getSlaStatus(assignTime: string, countedInSla: number): SlaStatus {
|
||
if (countedInSla === 0) {
|
||
return { level: 'excluded', color: '#94a3b8', text: '不计入SLA' }
|
||
}
|
||
|
||
const now = new Date()
|
||
const tier1Deadline = getTier1Deadline(assignTime)
|
||
const tier1Window = tier1Deadline.getTime() - new Date(assignTime).getTime()
|
||
|
||
// 剩余时间(毫秒),正数表示还没超,负数表示已超
|
||
const remaining = tier1Deadline.getTime() - now.getTime()
|
||
|
||
if (remaining > 0) {
|
||
const remainingRatio = remaining / tier1Window
|
||
if (remainingRatio > 0.5) {
|
||
const h = Math.floor(remaining / 3600000)
|
||
const m = Math.floor((remaining % 3600000) / 60000)
|
||
return { level: 'safe', color: '#22c55e', text: `还剩 ${h}h ${m}m` }
|
||
} else {
|
||
const h = Math.floor(remaining / 3600000)
|
||
const m = Math.floor((remaining % 3600000) / 60000)
|
||
return { level: 'warning', color: '#eab308', text: `还剩 ${h}h ${m}m` }
|
||
}
|
||
}
|
||
|
||
// 已超第一档
|
||
const exceeded = Math.abs(remaining)
|
||
const tier2Deadline = getTier2Deadline(assignTime)
|
||
|
||
if (now < tier2Deadline) {
|
||
const h = Math.floor(exceeded / 3600000)
|
||
const m = Math.floor((exceeded % 3600000) / 60000)
|
||
return { level: 'over-tier1', color: '#f97316', text: `超时 ${h}h ${m}m` }
|
||
}
|
||
|
||
// 已超第二档(严重)
|
||
const h = Math.floor(exceeded / 3600000)
|
||
const m = Math.floor((exceeded % 3600000) / 60000)
|
||
return { level: 'critical', color: '#ef4444', text: `超时 ${h}h ${m}m` }
|
||
}
|