feat: 配置版本历史/对比/回滚、国内 IM 通知渠道、阈值告警与 API Token(v1.2.9)
CI / verify (push) Canceled after 0s

- 配置版本: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:
2026-09-24 20:50:17 +08:00
parent 8f75e99339
commit 862442e77f
42 changed files with 3034 additions and 190 deletions
+69
View File
@@ -0,0 +1,69 @@
<template>
<div class="config-diff">
<div class="config-diff__head">
<span class="config-diff__range">{{ fromLabel }} → {{ toLabel }}</span>
<span class="config-diff__stats">
<span class="config-diff__add">+{{ stats.added }}</span>
<span class="config-diff__del">−{{ stats.removed }}</span>
</span>
</div>
<div class="config-diff__body">
<div
v-for="(line, index) in lines"
:key="index"
class="config-diff__row"
:class="`config-diff__row--${line.kind}`"
>
<span class="config-diff__sign">{{ sign(line.kind) }}</span>
<span class="config-diff__text">{{ line.text || ' ' }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { diffLines, diffStats, type DiffLine } from '../utils/diff'
const props = withDefaults(
defineProps<{
fromText: string
toText: string
fromLabel?: string
toLabel?: string
}>(),
{ fromLabel: '当前', toLabel: '所选版本' },
)
const lines = computed<DiffLine[]>(() => diffLines(props.fromText, props.toText))
const stats = computed(() => diffStats(lines.value))
function sign(kind: DiffLine['kind']): string {
if (kind === 'add') return '+'
if (kind === 'del') return '−'
return ' '
}
</script>
<style scoped>
.config-diff { border: 1px solid var(--fonu-border); border-radius: var(--fonu-radius-sm); overflow: hidden; }
.config-diff__head {
display: flex; align-items: center; justify-content: space-between; gap: var(--fonu-space-3);
padding: 6px var(--fonu-space-3); border-bottom: 1px solid var(--fonu-border);
background: var(--fonu-bg-muted); font-size: 12px; color: var(--fonu-text-secondary);
}
.config-diff__stats { display: flex; gap: 8px; font-family: var(--fonu-mono); }
.config-diff__add { color: var(--fonu-brand-text); }
.config-diff__del { color: var(--fonu-error); }
.config-diff__body { max-height: 420px; overflow: auto; background: var(--fonu-surface); }
.config-diff__row {
display: flex; gap: 8px; padding: 1px 10px;
font-family: var(--fonu-mono); font-size: 12px; line-height: 1.6;
}
.config-diff__row--add { background: var(--fonu-brand-soft); }
.config-diff__row--del { background: var(--fonu-error-soft); }
.config-diff__sign { flex: none; width: 10px; color: var(--fonu-text-muted); }
.config-diff__row--add .config-diff__sign { color: var(--fonu-brand-text); }
.config-diff__row--del .config-diff__sign { color: var(--fonu-error); }
.config-diff__text { white-space: pre-wrap; overflow-wrap: anywhere; }
</style>