feat(render+admin): delete render node
Build backend images / build content-svc (push) Failing after 1m7s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 55s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 1m7s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 59s
Build backend images / build content-svc (push) Failing after 1m7s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 55s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 1m7s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 59s
- render-svc: DELETE /v1/nodes/:node_id (store.DeleteNode + handler + route) - admin: حذف button per node row + DELETE /api/admin/nodes/[nodeId] proxy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,7 @@ func main() {
|
|||||||
nodes.POST("", nodeH.Create)
|
nodes.POST("", nodeH.Create)
|
||||||
nodes.GET("/:node_id", nodeH.Get)
|
nodes.GET("/:node_id", nodeH.Get)
|
||||||
nodes.PATCH("/:node_id", nodeH.Patch)
|
nodes.PATCH("/:node_id", nodeH.Patch)
|
||||||
|
nodes.DELETE("/:node_id", nodeH.Delete)
|
||||||
nodes.POST("/:node_id/restart", nodeH.Restart)
|
nodes.POST("/:node_id/restart", nodeH.Restart)
|
||||||
nodes.POST("/:node_id/release", nodeH.Release)
|
nodes.POST("/:node_id/release", nodeH.Release)
|
||||||
nodes.POST("/:node_id/close-ae", nodeH.CloseAE)
|
nodes.POST("/:node_id/close-ae", nodeH.CloseAE)
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ func (s *Store) PatchNode(ctx context.Context, id uuid.UUID, req *models.NodePat
|
|||||||
return s.GetNodeByID(ctx, id)
|
return s.GetNodeByID(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteNode permanently removes a render node.
|
||||||
|
func (s *Store) DeleteNode(ctx context.Context, id uuid.UUID) error {
|
||||||
|
_, err := s.pool.Exec(ctx, `DELETE FROM render.render_nodes WHERE id = $1`, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) UpdateNodeHeartbeat(ctx context.Context, nodeID uuid.UUID, req *models.NodeHeartbeatRequest) error {
|
func (s *Store) UpdateNodeHeartbeat(ctx context.Context, nodeID uuid.UUID, req *models.NodeHeartbeatRequest) error {
|
||||||
_, err := s.pool.Exec(ctx, `
|
_, err := s.pool.Exec(ctx, `
|
||||||
UPDATE render.render_nodes SET
|
UPDATE render.render_nodes SET
|
||||||
|
|||||||
@@ -107,6 +107,20 @@ func (h *NodeHandler) Release(c *gin.Context) {
|
|||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DELETE /v1/nodes/:node_id
|
||||||
|
func (h *NodeHandler) Delete(c *gin.Context) {
|
||||||
|
nodeID, err := uuid.Parse(c.Param("node_id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid node_id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.store.DeleteNode(c.Request.Context(), nodeID); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// POST /v1/nodes/:node_id/close-ae — Stub: signals the node agent to kill AE process
|
// POST /v1/nodes/:node_id/close-ae — Stub: signals the node agent to kill AE process
|
||||||
func (h *NodeHandler) CloseAE(c *gin.Context) {
|
func (h *NodeHandler) CloseAE(c *gin.Context) {
|
||||||
_, err := uuid.Parse(c.Param("node_id"))
|
_, err := uuid.Parse(c.Param("node_id"))
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { type NextRequest } from "next/server";
|
||||||
|
import { adminProxy } from "@/app/api/admin/_adminProxy";
|
||||||
|
|
||||||
|
export const runtime = "nodejs";
|
||||||
|
interface Ctx { params: { nodeId: string } }
|
||||||
|
|
||||||
|
export async function DELETE(req: NextRequest, { params }: Ctx) {
|
||||||
|
return adminProxy(req, `/v1/nodes/${params.nodeId}`, "DELETE");
|
||||||
|
}
|
||||||
@@ -55,6 +55,17 @@ export function NodesTable({ nodes }: { nodes: V2Node[] }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteNode = async (nodeId: string) => {
|
||||||
|
if (!confirm("این نود برای همیشه حذف شود؟")) return;
|
||||||
|
setLoading((p) => ({ ...p, [nodeId]: true }));
|
||||||
|
try {
|
||||||
|
await fetch(`/api/admin/nodes/${nodeId}`, { method: "DELETE" });
|
||||||
|
router.refresh();
|
||||||
|
} finally {
|
||||||
|
setLoading((p) => ({ ...p, [nodeId]: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const addNode = async () => {
|
const addNode = async () => {
|
||||||
setSaving(true); setErr(null);
|
setSaving(true); setErr(null);
|
||||||
const res = await fetch("/api/admin/nodes", {
|
const res = await fetch("/api/admin/nodes", {
|
||||||
@@ -204,6 +215,13 @@ export function NodesTable({ nodes }: { nodes: V2Node[] }) {
|
|||||||
>
|
>
|
||||||
{t("actionRelease")}
|
{t("actionRelease")}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => deleteNode(node.id)}
|
||||||
|
disabled={loading[node.id]}
|
||||||
|
className="rounded px-2.5 py-1 text-xs text-red-400 border border-red-600/50 hover:bg-red-600/20 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
حذف
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user