fix: 首页本地服务只显示 localOnly 转发, 集群转发不再误报为本地服务

- handleStatus 的 localStatuses 遍历所有 locals 时跳过非 localOnly,
  集群转发只出现在活跃转发卡区 (per-remote 时代的残留逻辑)
- gofmt 格式化 topoKey 定义
- 同步 dist (含 ⚙/⊕ emoji 变体选择器修复)
This commit is contained in:
JianFeeeee
2026-08-24 16:13:59 +08:00
parent e3a978888a
commit 88b862b515
8 changed files with 78 additions and 28 deletions

View File

@ -405,14 +405,13 @@ func main() {
go ring.WatchTokenLoss(clusterCtx)
go ring.WatchLeader(clusterCtx)
// Startup auto-submit: the leader re-submits local non-disabled,
// non-localOnly forwards from its SQLite store so the ring repopulates
// after a full-cluster restart (ring topology is in-memory only and is
// lost when all nodes crash simultaneously). On single-node restart the
// topology is recovered via AdoptState, and SubmitTask is idempotent
// via HasTask, so re-submitting is a harmless no-op in that case.
// Only the leader does this — the canvas is typically saved on the
// leader, so its local store has the complete set of forwards.
// Startup reconcile: every node (leader or not) checks its own topology
// entries and re-claims any whose per-forward frpc worker process is
// absent — the topology was recovered via AdoptState but the worker
// processes died with the restart. This is safer than the leader clearing
// and re-submitting all topology, which races with leader changes.
// LocalOnly forwards are never in the topology and are handled by the
// auto-start loop above.
go func() {
for {
if ring.IsLeader() {
@ -426,12 +425,44 @@ func main() {
}
// Let the ring stabilize after leader election / rejoin.
time.Sleep(3 * time.Second)
snap := ring.Snapshot()
reclaimed := 0
for _, t := range snap.Topology {
if t.OwnerID != selfID {
continue
}
key := process.WorkerKey(t.Local.Name, t.Remote.Name, t.Link.RemotePort)
if _, has := pm.Status(key); has {
continue // worker already running
}
ctx, cancel := context.WithTimeout(clusterCtx, 15*time.Second)
if err := ring.Handler.Claim(ctx, &cluster.Task{
ID: fmt.Sprintf("reclaim-%s-%s-%d", t.Local.Name, t.Remote.Name, t.Link.RemotePort),
Local: t.Local, Remote: t.Remote, Link: t.Link,
}); err != nil {
log.Printf("ring[%s] reclaim %s→%s:%d: %v", ring.ID, t.Local.Name, t.Remote.Name, t.Link.RemotePort, err)
} else {
reclaimed++
}
cancel()
}
// Leader also re-submits any non-disabled, non-localOnly forwards
// that are NOT yet in the topology (new entries the canvas has but
// the ring hasn't processed yet).
links, err := st.ListLinks()
if err != nil {
log.Printf("ring[%s] auto-submit: %v", ring.ID, err)
log.Printf("ring[%s] reconcile: %v", ring.ID, err)
return
}
count := 0
type ownerKey struct{ local, remote string; port int }
ownedBy := map[ownerKey]string{}
for _, t := range snap.Topology {
k := ownerKey{t.Local.Name, t.Remote.Name, t.Link.RemotePort}
if _, ok := ownedBy[k]; !ok {
ownedBy[k] = t.OwnerID
}
}
submitted := 0
for _, ln := range links {
if ln.Disabled {
continue
@ -444,12 +475,14 @@ func main() {
if !ok {
continue
}
k := ownerKey{ln.Local, ln.Remote, ln.RemotePort}
if _, inTopo := ownedBy[k]; inTopo {
continue // already reclaimed above
}
ring.SubmitTask(loc, rem, ln)
count++
}
if count > 0 {
log.Printf("ring[%s] leader auto-submitted %d forwards on startup", ring.ID, count)
submitted++
}
log.Printf("ring[%s] startup reconcile: reclaimed=%d submitted=%d", ring.ID, reclaimed, submitted)
}()
go func() {