- Vite + React 18 + TypeScript scaffolding - Tailwind CSS configured for 4K dark theme (24px base) - Three full-screen rotating views: Network Topology (D3.js), Kanban Board (Odoo tasks), Calendar (Odoo events) - Hooks for data fetching, WebSocket, and view rotation - Header with live clock and connection status - Framer Motion fade transitions between views - Docker Compose with backend (host network for nmap) and frontend (nginx proxy to backend API/WS) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
import { useState } from "react";
|
|
import type { NetworkNode } from "../../types";
|
|
|
|
const ICON_MAP: Record<string, string> = {
|
|
router: "\uD83C\uDF10",
|
|
server: "\uD83D\uDDA5\uFE0F",
|
|
switch: "\uD83D\uDD00",
|
|
ap: "\uD83D\uDCE1",
|
|
pc: "\uD83D\uDCBB",
|
|
nas: "\uD83D\uDCBE",
|
|
device: "\uD83D\uDCF1",
|
|
};
|
|
|
|
interface NodeCardProps {
|
|
node: NetworkNode;
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
export function NodeCard({ node, x, y }: NodeCardProps) {
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
const statusColor =
|
|
node.status === "up"
|
|
? "bg-success"
|
|
: node.status === "down"
|
|
? "bg-danger"
|
|
: "bg-warning";
|
|
|
|
return (
|
|
<foreignObject x={x - 120} y={y - 80} width={240} height={180}>
|
|
<div
|
|
className="bg-bg-card border border-border rounded-xl p-4 flex flex-col items-center gap-1 shadow-lg cursor-pointer select-none"
|
|
onClick={() => setShowPassword((prev) => !prev)}
|
|
>
|
|
<span className="text-3xl">{ICON_MAP[node.icon] || "\uD83D\uDCE6"}</span>
|
|
<span className="text-base font-bold text-text-primary truncate w-full text-center">
|
|
{node.name}
|
|
</span>
|
|
<span className="text-sm text-text-secondary font-mono">{node.ip}</span>
|
|
{node.username && (
|
|
<span className="text-sm text-text-secondary">
|
|
{node.username} / {showPassword ? node.password : "\u2022\u2022\u2022\u2022"}
|
|
</span>
|
|
)}
|
|
{node.public_url && (
|
|
<span className="text-xs text-accent truncate w-full text-center">
|
|
{node.public_url}
|
|
</span>
|
|
)}
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<span className={`w-3 h-3 rounded-full ${statusColor}`} />
|
|
<span className="text-xs text-text-secondary">
|
|
{node.status === "up" ? "Online" : node.status === "down" ? "Offline" : "Unknown"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</foreignObject>
|
|
);
|
|
}
|