feat: 配置版本历史/对比/回滚、国内 IM 通知渠道、阈值告警与 API Token(v1.2.9)
- 配置版本:fsutil 版本化备份(内容相同自动去重),主 nginx.conf 与 VPS vhost 的版本列表/逐行差异/一键回滚,回滚先校验语法与 nginx -t 再写回 - agent 0.12.2:写 vhost 前留版本;nginx -t 校验失败改为恢复上一个可用版本(此前直接删文件),新增域名 vhost 版本端点,双架构二进制已重编 - 通知:补企业微信 / 钉钉 / 飞书机器人,切换预设时服务地址跟随该预设默认值 - 阈值告警:新增 internal/monitor 巡检(CPU/内存/磁盘持续超阈值、隧道连续 N 次未注册),默认关闭,带静默期与恢复通知 - API Token:api_tokens 表 + Authorization Bearer 认证,只读/读写两种范围;令牌管理与改密/退出仅限登录会话
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// 极简行级差异:Nginx 配置通常只有几十行,用 LCS 就够,不为此引入第三方 diff 依赖。
|
||||
|
||||
export interface DiffLine {
|
||||
kind: 'same' | 'add' | 'del'
|
||||
text: string
|
||||
oldLine?: number
|
||||
newLine?: number
|
||||
}
|
||||
|
||||
// maxDiffCells 限制 LCS 表大小:超长文本(例如整份日志误粘进来)退化为「整体替换」,
|
||||
// 避免 O(n*m) 的表把页面卡死。
|
||||
const maxDiffCells = 250_000
|
||||
|
||||
export function diffLines(fromText: string, toText: string): DiffLine[] {
|
||||
const from = splitLines(fromText)
|
||||
const to = splitLines(toText)
|
||||
|
||||
if (from.length * to.length > maxDiffCells) {
|
||||
return [
|
||||
...from.map((text, index) => ({ kind: 'del' as const, text, oldLine: index + 1 })),
|
||||
...to.map((text, index) => ({ kind: 'add' as const, text, newLine: index + 1 })),
|
||||
]
|
||||
}
|
||||
|
||||
// dp[i][j] = from[i..] 与 to[j..] 的最长公共子序列长度
|
||||
const dp: number[][] = Array.from({ length: from.length + 1 }, () => new Array<number>(to.length + 1).fill(0))
|
||||
for (let i = from.length - 1; i >= 0; i -= 1) {
|
||||
for (let j = to.length - 1; j >= 0; j -= 1) {
|
||||
dp[i][j] = from[i] === to[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1])
|
||||
}
|
||||
}
|
||||
|
||||
const out: DiffLine[] = []
|
||||
let i = 0
|
||||
let j = 0
|
||||
while (i < from.length && j < to.length) {
|
||||
if (from[i] === to[j]) {
|
||||
out.push({ kind: 'same', text: from[i], oldLine: i + 1, newLine: j + 1 })
|
||||
i += 1
|
||||
j += 1
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
out.push({ kind: 'del', text: from[i], oldLine: i + 1 })
|
||||
i += 1
|
||||
} else {
|
||||
out.push({ kind: 'add', text: to[j], newLine: j + 1 })
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
while (i < from.length) {
|
||||
out.push({ kind: 'del', text: from[i], oldLine: i + 1 })
|
||||
i += 1
|
||||
}
|
||||
while (j < to.length) {
|
||||
out.push({ kind: 'add', text: to[j], newLine: j + 1 })
|
||||
j += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export function diffStats(lines: DiffLine[]): { added: number; removed: number } {
|
||||
let added = 0
|
||||
let removed = 0
|
||||
for (const line of lines) {
|
||||
if (line.kind === 'add') added += 1
|
||||
else if (line.kind === 'del') removed += 1
|
||||
}
|
||||
return { added, removed }
|
||||
}
|
||||
|
||||
function splitLines(text: string): string[] {
|
||||
if (text === '') return []
|
||||
return text.replace(/\r\n/g, '\n').replace(/\n+$/, '').split('\n')
|
||||
}
|
||||
Reference in New Issue
Block a user