feat: add complete frontend with React, Tailwind 4K, and Docker setup
- 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>
This commit is contained in:
60
frontend/src/components/Topology/NodeCard.tsx
Normal file
60
frontend/src/components/Topology/NodeCard.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user