Node management

This commit is contained in:
2025-12-28 00:46:06 +01:00
parent 97d374920b
commit 127e946780
14 changed files with 666 additions and 31 deletions
+166
View File
@@ -0,0 +1,166 @@
"use client";
import { useState, ReactNode } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "../ui/textarea";
import {
authNode,
getNodes,
setNodes as updateNodes,
} from "@/core/client/node";
export default function AddNode({ children }: { children: ReactNode }) {
const [step, setStep] = useState<1 | 2>(1);
const [loading, setLoading] = useState(false);
const [ip, setIp] = useState("");
const [key, setKey] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [auth, setAuth] = useState<{ node_id: string; os: string }>();
const authenticateNode = async () => {
setLoading(true);
try {
setAuth(await authNode(ip.trim(), key.trim()));
setStep(2);
} catch (e) {
console.log(e);
} finally {
setLoading(false);
}
};
const addNode = async () => {
if (!auth) return;
const nodes = await getNodes();
await updateNodes([
...nodes,
{
node_id: auth.node_id,
key,
name,
description,
ip,
os: auth.os,
labels: [],
},
]);
setStep(1);
setAuth(undefined);
setIp("");
setKey("");
setName("");
setDescription("");
};
return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="sm:max-w-106.25">
<DialogHeader>
<DialogTitle>
{step === 1 && "Add Node"}
{step === 2 && "Node Details"}
</DialogTitle>
</DialogHeader>
{/* STEP 1: IP & Node Key */}
{step === 1 && (
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="ip">
IP address <span className="text-destructive">*</span>
</Label>
<Input
id="ip"
value={ip}
onChange={(e) => setIp(e.target.value)}
placeholder="10.7.1.21"
/>
</div>
<div className="grid gap-3">
<Label htmlFor="key">
Node Key <span className="text-destructive">*</span>
</Label>
<Input
id="key"
type="password"
value={key}
onChange={(e) => setKey(e.target.value)}
placeholder="xxxxxxxxxxxxxxxx"
/>
</div>
</div>
)}
{/* STEP 3: Node Name & Description */}
{step === 2 && (
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="name">
Node Name <span className="text-destructive">*</span>
</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Captain's node"
/>
</div>
<div className="grid gap-3">
<Label htmlFor="description">
Node Description{" "}
<span className="text-muted-foreground/60">(optional)</span>
</Label>
<Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="A node for the Captain's main operations"
/>
</div>
</div>
)}
<DialogFooter>
{step === 1 && (
<>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button onClick={authenticateNode} disabled={loading}>
Next
</Button>
</>
)}
{step === 2 && (
<>
<Button variant="outline" onClick={() => setStep(1)}>
Back
</Button>
<DialogClose asChild>
<Button onClick={addNode}>Add Node</Button>
</DialogClose>
</>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}