-
{dateStr}
-
{timeStr}
-
-
-
- {connected ? "Conectado" : "Sin conexiΓ³n"}
-
-
+
+ {dateStr}
+ {timeStr}
+
);
diff --git a/frontend/src/components/Tasks/KanbanBoard.tsx b/frontend/src/components/Tasks/KanbanBoard.tsx
index 862e988..77d75a0 100644
--- a/frontend/src/components/Tasks/KanbanBoard.tsx
+++ b/frontend/src/components/Tasks/KanbanBoard.tsx
@@ -5,57 +5,37 @@ interface KanbanBoardProps {
}
const STAGE_COLORS = [
- "bg-blue-500",
- "bg-emerald-500",
- "bg-amber-500",
- "bg-purple-500",
- "bg-rose-500",
- "bg-cyan-500",
- "bg-orange-500",
- "bg-teal-500",
+ "bg-blue-500", "bg-emerald-500", "bg-amber-500", "bg-purple-500",
+ "bg-rose-500", "bg-cyan-500", "bg-orange-500", "bg-teal-500",
];
function ProjectRow({ project }: { project: Project }) {
const stages = Object.entries(project.stages);
const totalTasks = stages.reduce((sum, [, tasks]) => sum + tasks.length, 0);
-
if (totalTasks === 0) return null;
return (
-
-
-
+
+
+
{project.name}
-
- {totalTasks}
-
+ {totalTasks}
-
-
- {stages.map(([stageName, tasks], i) => {
+
+ {stages.map(([name, tasks], i) => {
const pct = (tasks.length / totalTasks) * 100;
if (pct < 1) return null;
return (
-
+
);
})}
-
-
- {stages.map(([stageName, tasks], i) => (
-
-
- {stageName}
+
+ {stages.map(([name, tasks], i) => (
+
+
+ {name}
{tasks.length}
))}
@@ -65,33 +45,27 @@ function ProjectRow({ project }: { project: Project }) {
}
export function KanbanBoard({ projects }: KanbanBoardProps) {
- const activeProjects = projects.filter((p) => {
- const total = Object.values(p.stages).reduce((s, t) => s + t.length, 0);
- return total > 0;
- });
-
- const totalTasks = activeProjects.reduce(
- (sum, p) => sum + Object.values(p.stages).reduce((s, t) => s + t.length, 0),
- 0
+ const active = projects.filter((p) =>
+ Object.values(p.stages).reduce((s, t) => s + t.length, 0) > 0
+ );
+ const total = active.reduce(
+ (sum, p) => sum + Object.values(p.stages).reduce((s, t) => s + t.length, 0), 0
);
return (
-
-
- {activeProjects.length} proyectos
+
+
+ {active.length} proyectos
|
-
- {totalTasks} tareas
+
+ {total} tareas
-
-
-
- {activeProjects.map((project) => (
-
- ))}
+
diff --git a/frontend/src/components/Topology/NetworkGraph.tsx b/frontend/src/components/Topology/NetworkGraph.tsx
index ad75435..bea4c1c 100644
--- a/frontend/src/components/Topology/NetworkGraph.tsx
+++ b/frontend/src/components/Topology/NetworkGraph.tsx
@@ -11,171 +11,137 @@ const ICON_MAP: Record
= {
phone: "π", camera: "π·", device: "π±",
};
-/* ββ Compact VM pill βββββββββββββββββββββββββββββββββββββββ */
function VmPill({ node }: { node: NetworkNode }) {
- const isUp = node.status === "up";
- const dotColor = isUp ? "bg-success" : "bg-danger";
-
+ const dot = node.status === "up" ? "bg-success" : "bg-danger";
return (
-
-
-
{node.name}
+
+
+ {node.name}
);
}
-/* ββ Infrastructure card βββββββββββββββββββββββββββββββββββ */
function InfraCard({ node }: { node: NetworkNode }) {
const [showPass, setShowPass] = useState(false);
const isUp = node.status === "up";
- const borderColor = isUp ? "border-success/40" : "border-danger/40";
- const bgColor = isUp ? "bg-success-dim" : "bg-danger-dim";
- const dotColor = isUp ? "bg-success" : "bg-danger";
+ const border = isUp ? "border-success/30" : "border-danger/30";
+ const bg = isUp ? "bg-success-dim" : "bg-danger-dim";
+ const dot = isUp ? "bg-success" : "bg-danger";
return (
setShowPass((p) => !p)}
>
-
{ICON_MAP[node.icon] || "π¦"}
-
-
{node.name}
-
{node.ip}
+
{ICON_MAP[node.icon] || "π¦"}
+
+
{node.name}
+
{node.ip}
-
+
{showPass && node.username && (
-
+
{node.username}
- {node.password && / {node.password}}
+ {node.password && / {node.password}}
)}
{showPass && node.public_url && (
-
{node.public_url}
+
{node.public_url}
)}
);
}
-/* ββ Proxmox server column βββββββββββββββββββββββββββββββββ */
-function ProxmoxColumn({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) {
- const upCount = vms.filter((v) => v.status === "up").length;
-
+function ProxmoxCol({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) {
+ const up = vms.filter((v) => v.status === "up").length;
return (
-
+
+
-
-
- {upCount}/{vms.length} activos
-
-
- {vms.map((vm) => (
-
- ))}
+
+
{up}/{vms.length}
+
+ {vms.map((vm) => )}
);
}
-/* ββ Vertical line βββββββββββββββββββββββββββββββββββββββββ */
-function VLine() {
- return
;
-}
-
-/* ββ Main topology βββββββββββββββββββββββββββββββββββββββββ */
export function NetworkGraph({ nodes }: NetworkGraphProps) {
- const findByName = (name: string) => nodes.find((n) => n.name === name);
- const modem = findByName("Router Telmex");
- const firewall = findByName("Firewall OPNsense");
- const switchNode = findByName("Switch Cisco");
+ const find = (name: string) => nodes.find((n) => n.name === name);
+ const modem = find("Router Telmex");
+ const firewall = find("Firewall OPNsense");
+ const sw = find("Switch Cisco");
- const proxmoxServers = nodes.filter((n) => n.type === "proxmox");
- const otherDevices = nodes.filter(
- (n) =>
- !n.type &&
- n.name !== "Router Telmex" &&
- n.name !== "Firewall OPNsense" &&
- n.name !== "Switch Cisco"
- );
-
- const vmsByParent = new Map
();
- for (const node of nodes) {
- if ((node.type === "vm" || node.type === "ct") && node.parent) {
- const list = vmsByParent.get(node.parent) || [];
- list.push(node);
- vmsByParent.set(node.parent, list);
+ const pve = nodes.filter((n) => n.type === "proxmox");
+ const vms = new Map();
+ for (const n of nodes) {
+ if ((n.type === "vm" || n.type === "ct") && n.parent) {
+ const list = vms.get(n.parent) || [];
+ list.push(n);
+ vms.set(n.parent, list);
}
}
+ const other = nodes.filter(
+ (n) => !n.type && !["Router Telmex", "Firewall OPNsense", "Switch Cisco"].includes(n.name)
+ );
- const onlineCount = nodes.filter((n) => n.status === "up").length;
- const total = nodes.length;
+ const online = nodes.filter((n) => n.status === "up").length;
+ const vmTotal = nodes.filter((n) => n.type === "vm" || n.type === "ct").length;
return (
- {/* Summary bar */}
-
-
-
-
- {onlineCount} online
-
-
-
-
-
- {total - onlineCount} offline
-
-
+ {/* Stats */}
+
+
+
+ {online}
+ online
+
+
+
+ {nodes.length - online}
+ offline
+
|
-
- {proxmoxServers.length} Proxmox Β·{" "}
-
- {nodes.filter((n) => n.type === "vm" || n.type === "ct").length}
- {" "}
- VMs/CTs
+
+ {pve.length} Proxmox Β·{" "}
+ {vmTotal} VMs/CTs
- {/* Diagram */}
-
- {/* Top chain: Modem β Firewall β Switch */}
-
+ {/* Tree */}
+
+ {/* Top chain */}
+
{modem && }
- β
+ β
{firewall && }
- β
- {switchNode && }
+ β
+ {sw && }
-
-
- {/* Horizontal branch line */}
-
+ {/* Branch down */}
+
+
{/* Proxmox columns */}
-
- {proxmoxServers.map((server) => {
- const vms = vmsByParent.get(server.name) || [];
- return (
-
- );
- })}
+
+ {pve.map((s) => (
+
+ ))}
- {/* Other devices */}
- {otherDevices.length > 0 && (
-
-
+ {/* Others */}
+ {other.length > 0 && (
+
+
Otros dispositivos
-
- {otherDevices.map((node) => (
-
- ))}
+
+ {other.map((n) => )}
)}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 4c66f66..308d25d 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -21,7 +21,8 @@
}
html {
- font-size: clamp(14px, 1.15vw, 24px);
+ /* Scales with viewport: ~14px at 1920w, ~16px at 3840w, min 10px */
+ font-size: clamp(10px, 0.75vw, 16px);
}
body {
@@ -35,12 +36,12 @@ body {
}
::-webkit-scrollbar {
- width: 6px;
+ width: 4px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--color-border);
- border-radius: 3px;
+ border-radius: 2px;
}