Node status checking

This commit is contained in:
2025-12-29 19:49:37 +01:00
parent 98c8e86872
commit 4d18edcffe
10 changed files with 59 additions and 95 deletions
+1 -2
View File
@@ -4,7 +4,6 @@ import { DashboardLayout } from "@/components/dashboard/DashboardLayout";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { StatusIndicator } from "@/components/dashboard/StatusIndicator";
import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react"; import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
@@ -111,7 +110,7 @@ export default function ApplicationsPage() {
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 md:items-center"> <div className="grid grid-cols-1 md:grid-cols-12 gap-4 md:items-center">
{/* Status & Name */} {/* Status & Name */}
<div className="flex items-center gap-3 md:col-span-3"> <div className="flex items-center gap-3 md:col-span-3">
<StatusIndicator status={app.status} /> {/* {app.status} */}
<div className="min-w-0"> <div className="min-w-0">
<div className="font-mono text-sm font-medium"> <div className="font-mono text-sm font-medium">
{app.name} {app.name}
+2 -5
View File
@@ -4,7 +4,6 @@ import { DashboardLayout } from "@/components/dashboard/DashboardLayout";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { StatusIndicator } from "@/components/dashboard/StatusIndicator";
import { Terminal, MoreVertical, Play, Square, Trash2 } from "lucide-react"; import { Terminal, MoreVertical, Play, Square, Trash2 } from "lucide-react";
const containers = [ const containers = [
@@ -184,9 +183,7 @@ export default function ContainersPage() {
<Badge>{container.node}</Badge> <Badge>{container.node}</Badge>
</td> </td>
<td className="py-3 px-4"> <td className="py-3 px-4">{/* {container.status} */}</td>
<StatusIndicator status={container.status} showLabel />
</td>
<td className="py-3 px-4 font-mono text-xs"> <td className="py-3 px-4 font-mono text-xs">
{container.uptime} {container.uptime}
@@ -235,7 +232,7 @@ export default function ContainersPage() {
<Terminal className="w-4 h-4 text-muted-foreground" /> <Terminal className="w-4 h-4 text-muted-foreground" />
<span className="font-mono text-sm">{container.name}</span> <span className="font-mono text-sm">{container.name}</span>
</div> </div>
<StatusIndicator status={container.status} /> {/* {container.status} */}
</div> </div>
<div className="text-[10px] font-mono text-muted-foreground"> <div className="text-[10px] font-mono text-muted-foreground">
+2 -5
View File
@@ -4,7 +4,6 @@ import { DashboardLayout } from "@/components/dashboard/DashboardLayout";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { StatusIndicator } from "@/components/dashboard/StatusIndicator";
import { import {
Rocket, Rocket,
RotateCcw, RotateCcw,
@@ -153,9 +152,7 @@ export default function DeploymentsPage() {
</div> </div>
</td> </td>
<td className="py-3 px-4"> <td className="py-3 px-4">{dep.status}</td>
<StatusIndicator status={dep.status} showLabel />
</td>
<td className="py-3 px-4 font-mono text-sm"> <td className="py-3 px-4 font-mono text-sm">
{dep.replicas} {dep.replicas}
@@ -201,7 +198,7 @@ export default function DeploymentsPage() {
<Rocket className="w-4 h-4 text-muted-foreground" /> <Rocket className="w-4 h-4 text-muted-foreground" />
<span className="font-mono text-sm">{dep.app}</span> <span className="font-mono text-sm">{dep.app}</span>
</div> </div>
<StatusIndicator status={dep.status} /> {dep.status}
</div> </div>
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
+2 -2
View File
@@ -56,8 +56,8 @@ export default function MonitoringPage() {
const fetchNodes = async () => { const fetchNodes = async () => {
const nodes = await getLiveNodes(); const nodes = await getLiveNodes();
setLiveData({ setLiveData({
cpuUsage: average(nodes, (n) => n.liveData.cpu.usage), cpuUsage: average(nodes, (n) => n.liveData?.cpu.usage || 0),
ramUsage: average(nodes, (n) => n.liveData.memory.usage), ramUsage: average(nodes, (n) => n.liveData?.memory.usage || 0),
}); });
}; };
fetchNodes(); fetchNodes();
+20 -13
View File
@@ -8,12 +8,15 @@ import { Server, Plus, Settings, Trash2 } from "lucide-react";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
import { Docker, Node, NodeLiveData } from "@/lib/types"; import { Docker, Node, NodeLiveData } from "@/lib/types";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getDockerNode, getLiveNodes } from "@/core/node"; import { getLiveNodes } from "@/core/node";
import { formatBytes } from "@/lib/format"; import { formatBytes } from "@/lib/format";
import { setNodes as updateNodes } from "@/core/node"; import { setNodes as updateNodes } from "@/core/node";
import AddNode from "@/components/dialogs/AddNode"; import AddNode from "@/components/dialogs/AddNode";
import { getDocker } from "@/core/docker";
function DiskUsage({ liveData }: NodeLiveData) { function DiskUsage({ liveData }: NodeLiveData) {
if (!liveData) return null;
const usage = (liveData.disk.used / liveData.disk.size) * 100; const usage = (liveData.disk.used / liveData.disk.size) * 100;
return ( return (
@@ -44,7 +47,7 @@ export default function NodesPage() {
useEffect(() => { useEffect(() => {
const fetchDocker = async () => { const fetchDocker = async () => {
setDockerNodes(await Promise.all(nodes.map(getDockerNode))); setDockerNodes(await getDocker(nodes));
}; };
fetchDocker(); fetchDocker();
}, [nodes]); }, [nodes]);
@@ -54,9 +57,7 @@ export default function NodesPage() {
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-6 gap-2"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-6 gap-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Badge>{nodes.length} nodes</Badge> <Badge>{nodes.length} nodes</Badge>
<Badge> <Badge>{nodes.filter((node) => node.liveData).length} online</Badge>
{/* {nodes.filter((n) => n.status === "running").length} online */}
</Badge>
</div> </div>
<AddNode> <AddNode>
<Button size="sm" className="gap-2"> <Button size="sm" className="gap-2">
@@ -75,8 +76,12 @@ export default function NodesPage() {
</div> </div>
<div> <div>
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
{/* <StatusIndicator status={node.status} /> */}
<CardTitle className="font-mono">{node.name}</CardTitle> <CardTitle className="font-mono">{node.name}</CardTitle>
{node.liveData ? (
<span className="size-2 rounded-full bg-chart-2" />
) : (
<span className="size-2 rounded-full bg-chart-5" />
)}
</div> </div>
<div className="text-xs text-muted-foreground font-mono"> <div className="text-xs text-muted-foreground font-mono">
{node.ip} • {node.ip} {node.ip} • {node.ip}
@@ -135,13 +140,13 @@ export default function NodesPage() {
{/* CPU */} {/* CPU */}
<div> <div>
<div className="text-xs text-muted-foreground mb-2"> <div className="text-xs text-muted-foreground mb-2">
CPU ({node.liveData.cpu.cores} cores) CPU ({node.liveData?.cpu.cores} cores)
</div> </div>
<div className="stat-value mb-2"> <div className="stat-value mb-2">
{node.liveData.cpu.usage.toFixed(0)}% {node.liveData?.cpu.usage.toFixed(0)}%
</div> </div>
<Progress <Progress
value={node.liveData.cpu.usage} value={node.liveData?.cpu.usage}
className="w-full" className="w-full"
/> />
</div> </div>
@@ -152,15 +157,17 @@ export default function NodesPage() {
Memory Memory
</div> </div>
<div className="stat-value mb-2"> <div className="stat-value mb-2">
{node.liveData.memory.usage.toFixed(0)}% {node.liveData?.memory.usage.toFixed(0)}%
</div> </div>
<Progress <Progress
value={node.liveData.memory.usage} value={node.liveData?.memory.usage}
className="w-full" className="w-full"
/> />
<div className="text-[10px] text-muted-foreground mt-1 font-mono"> <div className="text-[10px] text-muted-foreground mt-1 font-mono">
{formatBytes(node.liveData.memory.used)} /{" "} {node.liveData &&
{formatBytes(node.liveData.memory.total)} formatBytes(node.liveData?.memory.used) +
"/" +
formatBytes(node.liveData?.memory.total)}
</div> </div>
</div> </div>
+11 -10
View File
@@ -5,7 +5,6 @@ import { StatCard } from "@/components/dashboard/StatCard";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { StatusIndicator } from "@/components/dashboard/StatusIndicator";
import { import {
Box, Box,
Server, Server,
@@ -178,9 +177,7 @@ export default function OverviewPage() {
{app.memory}% {app.memory}%
</span> </span>
</div> </div>
<div className="col-span-2"> <div className="col-span-2">{app.status}</div>
<StatusIndicator status={app.status} showLabel />
</div>
</div> </div>
))} ))}
</div> </div>
@@ -205,7 +202,7 @@ export default function OverviewPage() {
className="flex flex-col sm:flex-row sm:items-center sm:justify-between py-2 border-b border-border last:border-0" className="flex flex-col sm:flex-row sm:items-center sm:justify-between py-2 border-b border-border last:border-0"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<StatusIndicator status={dep.status} size="sm" /> {dep.status}
<div> <div>
<div className="font-mono text-sm">{dep.app}</div> <div className="font-mono text-sm">{dep.app}</div>
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
@@ -235,7 +232,11 @@ export default function OverviewPage() {
<div key={i} className="border border-border p-4 bg-secondary/30"> <div key={i} className="border border-border p-4 bg-secondary/30">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-3 gap-2"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-3 gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<StatusIndicator status={"running"} /> {node.liveData ? (
<span className="size-2 rounded-full bg-chart-2" />
) : (
<span className="size-2 rounded-full bg-chart-5" />
)}
<span className="font-mono text-sm">{node.name}</span> <span className="font-mono text-sm">{node.name}</span>
</div> </div>
<span className="text-xs text-muted-foreground font-mono"> <span className="text-xs text-muted-foreground font-mono">
@@ -246,21 +247,21 @@ export default function OverviewPage() {
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">CPU</span> <span className="text-muted-foreground">CPU</span>
<span className="font-mono"> <span className="font-mono">
{node.liveData.cpu.usage.toFixed(0)}% {node.liveData?.cpu.usage.toFixed(0)}%
</span> </span>
</div> </div>
<Progress <Progress
value={node.liveData.cpu.usage} value={node.liveData?.cpu.usage}
className="[&>div]:bg-chart-1" className="[&>div]:bg-chart-1"
/> />
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">Memory</span> <span className="text-muted-foreground">Memory</span>
<span className="font-mono"> <span className="font-mono">
{node.liveData.memory.usage.toFixed(0)}% {node.liveData?.memory.usage.toFixed(0)}%
</span> </span>
</div> </div>
<Progress <Progress
value={node.liveData.memory.usage} value={node.liveData?.memory.usage}
className="w-full [&>div]:bg-chart-3" className="w-full [&>div]:bg-chart-3"
/> />
<div className="flex items-center justify-between text-xs pt-2 border-t border-border mt-2"> <div className="flex items-center justify-between text-xs pt-2 border-t border-border mt-2">
@@ -1,33 +0,0 @@
import { cn } from "@/lib/utils";
interface StatusIndicatorProps {
status: "running" | "stopped" | "error" | "pending";
showLabel?: boolean;
size?: "sm" | "md";
}
const statusConfig = {
running: { label: "Running", className: "status-running" },
stopped: { label: "Stopped", className: "status-stopped" },
error: { label: "Error", className: "status-error" },
pending: { label: "Pending", className: "status-pending" },
};
export function StatusIndicator({ status, showLabel = false, size = "md" }: StatusIndicatorProps) {
const config = statusConfig[status];
return (
<div className="flex items-center gap-2">
<div
className={cn(
"rounded-full",
config.className,
size === "sm" ? "w-1.5 h-1.5" : "w-2 h-2"
)}
/>
{showLabel && (
<span className="text-xs text-muted-foreground">{config.label}</span>
)}
</div>
);
}
+12 -3
View File
@@ -1,12 +1,21 @@
"use server"; "use server";
import axios from "axios"; import axios from "axios";
import { Node } from "@/lib/types"; import { Docker, Node } from "@/lib/types";
import { verifySession } from "./auth/session";
export async function getDocker(node: Node): Promise<{ version: string }> { export async function getDocker(nodes: Node[]): Promise<Docker[]> {
return await ( if (await verifySession()) return [];
return await Promise.all(
nodes.map(async (node) => {
const data = await (
await axios.get(`http://${node.ip}:3000/api/get-docker`, { await axios.get(`http://${node.ip}:3000/api/get-docker`, {
headers: { Authorization: `ApiKey ${node.key}` }, headers: { Authorization: `ApiKey ${node.key}` },
}) })
).data; ).data;
return { ...node, containers: data.containers, version: data.version };
})
);
} }
+5 -18
View File
@@ -1,12 +1,12 @@
"use server"; "use server";
import { read, write } from "@/app/api/file"; import { read, write } from "@/app/api/file";
import { Docker, Node, NodeLiveData } from "@/lib/types"; import { Node, NodeLiveData } from "@/lib/types";
import axios from "axios"; import axios from "axios";
import { verifySession } from "./auth/session"; import { verifySession } from "./auth/session";
export async function getNodes(): Promise<Node[] | undefined> { export async function getNodes(): Promise<Node[]> {
if (await verifySession()) return; if (await verifySession()) return [];
return await read(() => [], "data", "nodes.json"); return await read(() => [], "data", "nodes.json");
} }
@@ -17,10 +17,8 @@ export async function setNodes(nodes: Node[]) {
await write(nodes, "data", "nodes.json"); await write(nodes, "data", "nodes.json");
} }
export async function getLiveNodes(): Promise< export async function getLiveNodes(): Promise<(NodeLiveData & Node)[]> {
(NodeLiveData & Node)[] | undefined if (await verifySession()) return [];
> {
if (await verifySession()) return;
const nodes: Node[] = await read(() => [], "data", "nodes.json"); const nodes: Node[] = await read(() => [], "data", "nodes.json");
@@ -36,17 +34,6 @@ export async function getLiveNodes(): Promise<
); );
} }
export async function getDockerNode(node: Node): Promise<Docker | undefined> {
if (await verifySession()) return;
const data = await (
await axios.get(`http://${node.ip}:3000/api/get-docker`, {
headers: { Authorization: `ApiKey ${node.key}` },
})
).data;
return { ...node, containers: data.containers, version: data.version };
}
export async function authNode(ip: string, key: string) { export async function authNode(ip: string, key: string) {
if (await verifySession()) return; if (await verifySession()) return;
+1 -1
View File
@@ -11,7 +11,7 @@ export interface Node {
} }
export interface NodeLiveData { export interface NodeLiveData {
liveData: { liveData?: {
cpu: { cpu: {
manufacturer: string; manufacturer: string;
brand: string; brand: string;