feat: 自动备份与保留策略、健康持续化与状态页、指标历史与趋势图,并修隧道判定误报与仪表盘布局(v1.2.11)

- 自动备份:每日一份(按最新备份是否在 24h 内判定,重启不重复)、保留份数可配、列表/下载/恢复/删除,恢复前先校验归档;顺手修 writeArchive 逐个检查 tar/gzip/文件 Close()(审查报告 §二)

- 健康持续化:只存状态变更(up/down/unknown)并推导可用率与不可用区间;公开状态页默认关闭、访问码存 SHA-256、按 IP 限流、只暴露域名与可用率

- 隧道判定修复:RouteHealthCandidate 新增 tunnel_known,frps 管理接口不可用时不再把「判不出来」当成「不通」(此前会误报「隧道连续不通」);汇总与徽标同样区分「无法判定」

- 指标历史:metrics_samples 纵向点表(本机 CPU/内存/磁盘 + 每规则上下行,30 天保留、查询按桶平均、不做降采样表);仪表盘趋势卡支持 1h/24h/7d

- 仪表盘布局:运行状态并入顶部统计区(运行时长补进「服务」卡);性能并入系统资源那一行,整行 4 列

- 测试:backup/metrics/frp/api/app 新增用例;gofmt、go build/vet/test、vue-tsc、vite build 全绿

- agent 无改动,AgentVersion 保持 0.12.2
This commit is contained in:
2026-09-25 13:05:58 +08:00
parent c8264bf280
commit 74d731d0ac
46 changed files with 2967 additions and 175 deletions
+55 -2
View File
@@ -228,6 +228,15 @@ func (c *Collector) pruneHost(st *hostState, now time.Time) {
}
func (c *Collector) aggregateEndpoints(endpoints []proxy.Endpoint, now time.Time) (uploadTotal, downloadTotal int64, uploadRate, downloadRate float64, connections int) {
keys := make([]string, 0, len(endpoints))
for _, ep := range endpoints {
keys = append(keys, EndpointKey(ep.Hostname, ep.Port))
}
return c.aggregateHostKeys(keys, now)
}
// aggregateHostKeys 汇总若干 host:port 条目的总量、速率与活跃连接数
func (c *Collector) aggregateHostKeys(keys []string, now time.Time) (uploadTotal, downloadTotal int64, uploadRate, downloadRate float64, connections int) {
c.mu.RLock()
defer c.mu.RUnlock()
@@ -235,8 +244,7 @@ func (c *Collector) aggregateEndpoints(endpoints []proxy.Endpoint, now time.Time
var oldest time.Time
clientSet := map[string]time.Time{}
for _, ep := range endpoints {
key := EndpointKey(ep.Hostname, ep.Port)
for _, key := range keys {
st, ok := c.hosts[key]
if !ok {
continue
@@ -297,6 +305,51 @@ func (c *Collector) SnapshotForRules(rules []proxy.Rule) []RuleTraffic {
return out
}
// HostTraffic 单个域名的当前速率与总量(指标历史按域名落点)
type HostTraffic struct {
Hostname string `json:"hostname"`
UploadTotal int64 `json:"upload_total"`
DownloadTotal int64 `json:"download_total"`
UploadRate float64 `json:"upload_rate"`
DownloadRate float64 `json:"download_rate"`
Connections int `json:"connections"`
}
// SnapshotByHost 按域名汇总当前速率:内部键是 host:port,
// 同一域名的多个端口会合并,与 aggregateEndpoints 的算法保持一致。
func (c *Collector) SnapshotByHost(hostnames []string) map[string]HostTraffic {
now := time.Now()
out := map[string]HostTraffic{}
for _, host := range hostnames {
host = strings.ToLower(strings.TrimSpace(host))
if host == "" {
continue
}
prefix := host + ":"
c.mu.RLock()
keys := make([]string, 0, 2)
for key := range c.hosts {
if strings.HasPrefix(key, prefix) {
keys = append(keys, key)
}
}
c.mu.RUnlock()
if len(keys) == 0 {
continue
}
up, down, upRate, downRate, conns := c.aggregateHostKeys(keys, now)
out[host] = HostTraffic{
Hostname: host,
UploadTotal: up,
DownloadTotal: down,
UploadRate: upRate,
DownloadRate: downRate,
Connections: conns,
}
}
return out
}
func (c *Collector) ClientsForRule(rule proxy.Rule) []ClientConn {
now := time.Now()
activeCutoff := now.Add(-activeWindow)