feat: show server Reality keypair on node edit form (item 17)

New read-only GET /api/nodes/:id/reality-keys returns the
panel-hosted keypair for the node's server from keyStore (display
only — it never mints a pair; a missing entry reports nulls). The
node edit form shows public and private key rows with copy buttons
for Reality protocols.
This commit is contained in:
aaaamoooo
2026-10-21 10:44:32 +00:00
parent 8189a513e1
commit a9d98675ac
3 changed files with 104 additions and 0 deletions
+21
View File
@@ -2580,6 +2580,27 @@ func (a *App) handleAdminApi(w http.ResponseWriter, r *http.Request, pathname st
SendJSON(w, 201, map[string]any{"node": PublicNode(nodeAny.(*Node))}, nil)
return true, nil
}
if params, ok := RoutePattern("/api/nodes/:id/reality-keys", pathname); ok && r.Method == http.MethodGet {
// Round-3 item 17: display-only view of the panel-hosted Reality
// keypair for the node's server. This NEVER generates a pair —
// a missing entry simply reports nulls (the pair is minted by
// EnsureRealityKey at deploy time).
data := a.store.Snapshot()
node := findNode(data, params["id"])
if node == nil {
return true, httpErr(404, "节点不存在")
}
if !IsRealityProtocol(node.Protocol) {
return true, httpErr(400, "该节点不是 Reality 协议")
}
keyID := RealityKeyIDForNode(node)
var publicKey, privateKey any
if entry := data.KeyStore[keyID]; entry != nil {
publicKey, privateKey = entry.PublicKey, entry.PrivateKey
}
SendJSON(w, 200, map[string]any{"keyId": keyID, "publicKey": publicKey, "privateKey": privateKey}, nil)
return true, nil
}
if params, ok := RoutePattern("/api/nodes/:id", pathname); ok && r.Method == http.MethodPatch {
body, err := ReadJSON(r, defaultMaxBody)
if err != nil {