mirror of
https://github.com/YuzuZensai/Minikura.git
synced 2026-03-30 23:29:45 +00:00
211 lines
8.1 KiB
TypeScript
211 lines
8.1 KiB
TypeScript
|
|
import type { NodeProps } from "@xyflow/react";
|
||
|
|
import { Handle, Position } from "@xyflow/react";
|
||
|
|
import { Box, Check, Copy, Cpu, HardDrive, Server } from "lucide-react";
|
||
|
|
import { useState } from "react";
|
||
|
|
import { Badge } from "@/components/ui/badge";
|
||
|
|
import { Button } from "@/components/ui/button";
|
||
|
|
import { cn } from "@/lib/cn";
|
||
|
|
import type { ServerMetadata, TopologyNodeData } from "@/lib/topology-types";
|
||
|
|
|
||
|
|
export function ServerNode({ data, selected }: NodeProps) {
|
||
|
|
const nodeData = data as TopologyNodeData | TopologyNodeData;
|
||
|
|
const metadata = nodeData.metadata as ServerMetadata | ServerMetadata;
|
||
|
|
const { server, readyPods, podCount, health } = metadata;
|
||
|
|
|
||
|
|
const k8sNodes = "k8sNodes" in metadata ? metadata.k8sNodes : [];
|
||
|
|
const connectedProxies = "connectedProxies" in metadata ? metadata.connectedProxies : [];
|
||
|
|
const connectionInfo = "connectionInfo" in metadata ? metadata.connectionInfo : null;
|
||
|
|
const metrics = "metrics" in metadata ? metadata.metrics : undefined;
|
||
|
|
|
||
|
|
const [copied, setCopied] = useState(false);
|
||
|
|
|
||
|
|
const handleCopy = async (e: React.MouseEvent) => {
|
||
|
|
e.stopPropagation();
|
||
|
|
if (connectionInfo?.connectionString) {
|
||
|
|
await navigator.clipboard.writeText(connectionInfo.connectionString);
|
||
|
|
setCopied(true);
|
||
|
|
setTimeout(() => setCopied(false), 2000);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getStatusBadge = () => {
|
||
|
|
switch (health) {
|
||
|
|
case "healthy":
|
||
|
|
return <Badge className="bg-green-500 hover:bg-green-600 text-xs">Healthy</Badge>;
|
||
|
|
case "degraded":
|
||
|
|
return <Badge className="bg-yellow-500 hover:bg-yellow-600 text-xs">Degraded</Badge>;
|
||
|
|
case "unhealthy":
|
||
|
|
return <Badge className="bg-red-500 hover:bg-red-600 text-xs">Unhealthy</Badge>;
|
||
|
|
default:
|
||
|
|
return (
|
||
|
|
<Badge variant="secondary" className="text-xs">
|
||
|
|
Unknown
|
||
|
|
</Badge>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
"rounded-lg border bg-card p-3 shadow-md hover:shadow-lg transition-all w-[300px]",
|
||
|
|
selected && "ring-2 ring-primary ring-offset-2 shadow-xl"
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<Handle type="target" position={Position.Top} className="w-3 h-3 !bg-gray-400" />
|
||
|
|
|
||
|
|
<div className="space-y-2.5">
|
||
|
|
<div className="flex items-start justify-between gap-2">
|
||
|
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||
|
|
<div className="rounded-md bg-primary/10 p-1.5">
|
||
|
|
<Server className="h-4 w-4 text-primary" />
|
||
|
|
</div>
|
||
|
|
<div className="flex-1 min-w-0">
|
||
|
|
<p className="font-bold text-sm truncate">{server.id}</p>
|
||
|
|
{server.description && (
|
||
|
|
<p className="text-xs text-muted-foreground truncate">{server.description}</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{getStatusBadge()}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex flex-wrap gap-1">
|
||
|
|
<Badge variant="outline" className="text-[10px] h-5">
|
||
|
|
{server.jar_type}
|
||
|
|
</Badge>
|
||
|
|
<Badge variant="outline" className="text-[10px] h-5">
|
||
|
|
MC {server.minecraft_version}
|
||
|
|
</Badge>
|
||
|
|
<Badge variant="outline" className="text-[10px] h-5">
|
||
|
|
{server.type}
|
||
|
|
</Badge>
|
||
|
|
{connectionInfo && (
|
||
|
|
<Badge variant="outline" className="text-[10px] h-5">
|
||
|
|
{connectionInfo.type}
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center justify-between text-xs bg-muted/50 rounded px-2 py-1.5">
|
||
|
|
<span className="text-muted-foreground">Pods</span>
|
||
|
|
<span
|
||
|
|
className={cn(
|
||
|
|
"font-semibold",
|
||
|
|
readyPods === podCount ? "text-green-600" : "text-yellow-600"
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
{readyPods}/{podCount}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-1 text-[11px]">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground flex items-center gap-1">
|
||
|
|
<HardDrive className="h-3 w-3" /> Memory
|
||
|
|
</span>
|
||
|
|
<span className="font-medium text-xs">
|
||
|
|
{metrics?.memoryUsage && (
|
||
|
|
<span className="text-blue-600">{metrics.memoryUsage} / </span>
|
||
|
|
)}
|
||
|
|
<span className="text-muted-foreground">
|
||
|
|
{server.memory_request}/{server.memory}MB
|
||
|
|
</span>
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground flex items-center gap-1">
|
||
|
|
<Cpu className="h-3 w-3" /> CPU
|
||
|
|
</span>
|
||
|
|
<span className="font-medium text-xs">
|
||
|
|
{metrics?.cpuUsage && <span className="text-blue-600">{metrics.cpuUsage} / </span>}
|
||
|
|
<span className="text-muted-foreground">
|
||
|
|
{server.cpu_request || "N/A"}/{server.cpu_limit || "N/A"}
|
||
|
|
</span>
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{connectionInfo?.connectionString && (
|
||
|
|
<div className="space-y-1 text-[11px] pt-1 border-t">
|
||
|
|
<div className="flex items-center gap-1">
|
||
|
|
<code className="text-[10px] bg-muted px-1.5 py-0.5 rounded font-mono flex-1 truncate">
|
||
|
|
{connectionInfo.connectionString}
|
||
|
|
</code>
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="icon"
|
||
|
|
className="h-5 w-5 shrink-0"
|
||
|
|
onClick={handleCopy}
|
||
|
|
title={copied ? "Copied!" : "Copy address"}
|
||
|
|
>
|
||
|
|
{copied ? (
|
||
|
|
<Check className="h-3 w-3 text-green-500" />
|
||
|
|
) : (
|
||
|
|
<Copy className="h-3 w-3" />
|
||
|
|
)}
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
{connectionInfo.note && (
|
||
|
|
<p className="text-[10px] text-muted-foreground italic">{connectionInfo.note}</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{"pods" in metadata && metadata.pods && metadata.pods.length > 0 && (
|
||
|
|
<div className="space-y-1 text-[11px] pt-1 border-t">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground">Restarts</span>
|
||
|
|
<span
|
||
|
|
className={cn(
|
||
|
|
"font-medium",
|
||
|
|
metadata.pods.reduce((sum, p) => sum + (p.restarts || 0), 0) > 0
|
||
|
|
? "text-yellow-600"
|
||
|
|
: "text-green-600"
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
{metadata.pods.reduce((sum, p) => sum + (p.restarts || 0), 0)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
{metadata.pods[0]?.age && (
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground">Age</span>
|
||
|
|
<span className="font-medium">{metadata.pods[0].age}</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{metadata.pods[0]?.ip && (
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground">Pod IP</span>
|
||
|
|
<span className="font-medium font-mono text-[10px]">{metadata.pods[0].ip}</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{(k8sNodes.length > 0 || connectedProxies.length > 0) && (
|
||
|
|
<div className="space-y-1 text-[11px] pt-1 border-t">
|
||
|
|
{connectedProxies.length > 0 && (
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground">Exposed By</span>
|
||
|
|
<span className="font-semibold text-blue-600">
|
||
|
|
{connectedProxies.length} proxies
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{k8sNodes.length > 0 && (
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<span className="text-muted-foreground flex items-center gap-1">
|
||
|
|
<Box className="h-3 w-3" /> K8s Node
|
||
|
|
</span>
|
||
|
|
<span className="font-medium">{k8sNodes[0]}</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<Handle type="source" position={Position.Bottom} className="w-3 h-3 !bg-gray-400" />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|