From d6b83a0c1bec66f3d628519adaf610b54bdc0abe Mon Sep 17 00:00:00 2001 From: sagit Date: Wed, 11 Feb 2026 02:48:57 +0000 Subject: [PATCH] feat(panel-sharing): add edit support for peer shares Allow editing port range, traffic limit, allowed domains, allowed API IPs and expiry time on existing shares via a new update endpoint and edit modal in the frontend. --- .../internal/http/handler/federation.go | 85 ++++++++++++ go-backend/internal/http/handler/handler.go | 1 + vite-frontend/src/api/index.ts | 10 ++ vite-frontend/src/pages/panel-sharing.tsx | 127 ++++++++++++++++++ 4 files changed, 223 insertions(+) diff --git a/go-backend/internal/http/handler/federation.go b/go-backend/internal/http/handler/federation.go index e3bea0d..4f6c26f 100644 --- a/go-backend/internal/http/handler/federation.go +++ b/go-backend/internal/http/handler/federation.go @@ -40,6 +40,17 @@ type resetPeerShareFlowRequest struct { ID int64 `json:"id"` } +type updatePeerShareRequest struct { + ID int64 `json:"id"` + Name string `json:"name"` + MaxBandwidth int64 `json:"maxBandwidth"` + ExpiryTime int64 `json:"expiryTime"` + PortRangeStart int `json:"portRangeStart"` + PortRangeEnd int `json:"portRangeEnd"` + AllowedDomains string `json:"allowedDomains"` + AllowedIPs string `json:"allowedIps"` +} + type nodeImportRequest struct { RemoteURL string `json:"remoteUrl"` Token string `json:"token"` @@ -325,6 +336,80 @@ func (h *Handler) federationShareResetFlow(w http.ResponseWriter, r *http.Reques response.WriteJSON(w, response.OKEmpty()) } +func (h *Handler) federationShareUpdate(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + response.WriteJSON(w, response.ErrDefault("Invalid method")) + return + } + + var req updatePeerShareRequest + if err := decodeJSON(r.Body, &req); err != nil { + response.WriteJSON(w, response.ErrDefault("Invalid JSON")) + return + } + if req.ID <= 0 { + response.WriteJSON(w, response.ErrDefault("Share ID is required")) + return + } + + share, err := h.repo.GetPeerShare(req.ID) + if err != nil { + response.WriteJSON(w, response.Err(-2, err.Error())) + return + } + if share == nil { + response.WriteJSON(w, response.ErrDefault("Share not found")) + return + } + + if req.Name == "" { + response.WriteJSON(w, response.ErrDefault("Name is required")) + return + } + + if req.MaxBandwidth < 0 { + response.WriteJSON(w, response.ErrDefault("Max bandwidth cannot be negative")) + return + } + + if req.ExpiryTime < 0 { + response.WriteJSON(w, response.ErrDefault("Expiry time cannot be negative")) + return + } + + if req.PortRangeStart < 0 || req.PortRangeStart > 65535 || req.PortRangeEnd < 0 || req.PortRangeEnd > 65535 { + response.WriteJSON(w, response.ErrDefault("Invalid port range")) + return + } + + if req.PortRangeStart > req.PortRangeEnd { + response.WriteJSON(w, response.ErrDefault("Port range start cannot be greater than end")) + return + } + + allowedIPs, err := normalizePeerShareAllowedIPs(req.AllowedIPs) + if err != nil { + response.WriteJSON(w, response.ErrDefault(err.Error())) + return + } + + share.Name = req.Name + share.MaxBandwidth = req.MaxBandwidth + share.ExpiryTime = req.ExpiryTime + share.PortRangeStart = req.PortRangeStart + share.PortRangeEnd = req.PortRangeEnd + share.AllowedDomains = req.AllowedDomains + share.AllowedIPs = allowedIPs + share.UpdatedTime = time.Now().UnixMilli() + + if err := h.repo.UpdatePeerShare(share); err != nil { + response.WriteJSON(w, response.Err(-2, err.Error())) + return + } + + response.WriteJSON(w, response.OKEmpty()) +} + func (h *Handler) federationRemoteUsageList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("Invalid method")) diff --git a/go-backend/internal/http/handler/handler.go b/go-backend/internal/http/handler/handler.go index 4bfec49..a0ce67c 100644 --- a/go-backend/internal/http/handler/handler.go +++ b/go-backend/internal/http/handler/handler.go @@ -155,6 +155,7 @@ func (h *Handler) Register(mux *http.ServeMux) { mux.HandleFunc("/api/v1/open_api/sub_store", h.openAPISubStore) mux.HandleFunc("/api/v1/federation/share/list", h.federationShareList) mux.HandleFunc("/api/v1/federation/share/create", h.federationShareCreate) + mux.HandleFunc("/api/v1/federation/share/update", h.federationShareUpdate) mux.HandleFunc("/api/v1/federation/share/delete", h.federationShareDelete) mux.HandleFunc("/api/v1/federation/share/reset-flow", h.federationShareResetFlow) mux.HandleFunc("/api/v1/federation/share/remote-usage/list", h.federationRemoteUsageList) diff --git a/vite-frontend/src/api/index.ts b/vite-frontend/src/api/index.ts index f0b56d2..bd8c71e 100644 --- a/vite-frontend/src/api/index.ts +++ b/vite-frontend/src/api/index.ts @@ -200,6 +200,16 @@ export const createPeerShare = (data: { allowedDomains?: string; allowedIps?: string; }) => Network.post("/federation/share/create", data); +export const updatePeerShare = (data: { + id: number; + name: string; + maxBandwidth: number; + expiryTime: number; + portRangeStart: number; + portRangeEnd: number; + allowedDomains: string; + allowedIps: string; +}) => Network.post("/federation/share/update", data); export const deletePeerShare = (id: number) => Network.post("/federation/share/delete", { id }); export const resetPeerShareFlow = (id: number) => diff --git a/vite-frontend/src/pages/panel-sharing.tsx b/vite-frontend/src/pages/panel-sharing.tsx index fdd881c..2b5205a 100644 --- a/vite-frontend/src/pages/panel-sharing.tsx +++ b/vite-frontend/src/pages/panel-sharing.tsx @@ -20,6 +20,7 @@ import { resetPeerShareFlow, getPeerRemoteUsageList, importRemoteNode, + updatePeerShare, } from "@/api"; interface Node { @@ -91,6 +92,7 @@ export default function PanelSharingPage() { // Modals const [createShareOpen, setCreateShareOpen] = useState(false); + const [editShareOpen, setEditShareOpen] = useState(false); const [importNodeOpen, setImportNodeOpen] = useState(false); // Forms @@ -110,6 +112,17 @@ export default function PanelSharingPage() { token: "", }); + const [editForm, setEditForm] = useState({ + id: 0, + name: "", + maxBandwidth: 0, + expiryTime: 0, + portRangeStart: 10000, + portRangeEnd: 20000, + allowedDomains: "", + allowedIps: "", + }); + const loadShares = useCallback(async () => { setLoading(true); try { @@ -239,6 +252,52 @@ export default function PanelSharingPage() { } }; + const openEditShare = (share: PeerShare) => { + setEditForm({ + id: share.id, + name: share.name, + maxBandwidth: share.maxBandwidth > 0 ? Math.round(share.maxBandwidth / (1024 * 1024 * 1024)) : 0, + expiryTime: share.expiryTime, + portRangeStart: share.portRangeStart, + portRangeEnd: share.portRangeEnd, + allowedDomains: share.allowedDomains || "", + allowedIps: share.allowedIps || "", + }); + setEditShareOpen(true); + }; + + const handleEditShare = async () => { + if (!editForm.name) { + toast.error("名称不能为空"); + return; + } + if (editForm.maxBandwidth < 0) { + toast.error("流量上限不能为负数"); + return; + } + try { + const res = await updatePeerShare({ + id: editForm.id, + name: editForm.name, + maxBandwidth: Math.max(0, editForm.maxBandwidth) * 1024 * 1024 * 1024, + expiryTime: editForm.expiryTime, + portRangeStart: editForm.portRangeStart, + portRangeEnd: editForm.portRangeEnd, + allowedDomains: editForm.allowedDomains, + allowedIps: editForm.allowedIps, + }); + if (res.code === 0) { + toast.success("编辑成功"); + setEditShareOpen(false); + loadShares(); + } else { + toast.error(res.msg || "编辑失败"); + } + } catch { + toast.error("网络错误"); + } + }; + const handleImportNode = async () => { if (!importForm.remoteUrl || !importForm.token) { toast.error("请填写完整信息"); @@ -326,6 +385,13 @@ export default function PanelSharingPage() {

{share.name}

+ + + + + + {/* Import Node Modal */} setImportNodeOpen(false)}>