From e3e210a9dca327b30300bd09f60a8996712ea563 Mon Sep 17 00:00:00 2001 From: "I. Alcaraz Salazar" Date: Mon, 16 Feb 2026 01:44:50 +0000 Subject: [PATCH] feat: hierarchical tree topology diagram MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace grid layout with tree diagram: Modem → Firewall → Switch → 3 Proxmox servers → VMs/CTs - Services.yaml restructured with parent/type fields from real Proxmox API data (TYAN CAS, Cisco1, DellT430-2) - NetworkGraph renders vertical hierarchy with branch lines - ProxmoxColumn shows server card + VM pills grid - Compact VmPill for VMs/CTs (status dot + name + last octet) - InfraCard for physical infrastructure nodes - Other devices section at bottom (AP, printer, iDRACs, PCs) - Added type/parent fields to NetworkNode TypeScript interface Co-Authored-By: Claude Opus 4.6 --- backend/config/services.yaml | 652 +++++++++--------- .../src/components/Topology/NetworkGraph.tsx | 231 +++++-- frontend/src/types/index.ts | 2 + 3 files changed, 500 insertions(+), 385 deletions(-) diff --git a/backend/config/services.yaml b/backend/config/services.yaml index 512c8d9..6b2efc7 100644 --- a/backend/config/services.yaml +++ b/backend/config/services.yaml @@ -1,26 +1,28 @@ # ============================================================ # Topología de Red - Consultoria AS -# Edita este archivo para agregar credenciales y URLs públicas +# Jerarquía: Modem → Firewall → Switch → Proxmox → VMs # ============================================================ nodes: - # ── Infraestructura de Red ────────────────────────────────── - - name: "Firewall OPNsense" - ip: "192.168.10.1" - username: "root" - password: "opnsense" - public_url: "https://192.168.10.1:8443" - icon: "firewall" - connections: [] - + # ── Nivel 1: ISP ─────────────────────────────────────────── - name: "Router Telmex" ip: "192.168.1.254" username: "TELMEX" password: "" public_url: "http://192.168.1.254" icon: "router" - connections: ["Firewall OPNsense"] + connections: [] + # ── Nivel 2: Firewall ────────────────────────────────────── + - name: "Firewall OPNsense" + ip: "192.168.10.1" + username: "root" + password: "opnsense" + public_url: "https://192.168.10.1:8443" + icon: "firewall" + connections: ["Router Telmex"] + + # ── Nivel 3: Switch ──────────────────────────────────────── - name: "Switch Cisco" ip: "192.168.10.250" username: "" @@ -28,355 +30,335 @@ nodes: icon: "switch" connections: ["Firewall OPNsense"] - - name: "Switch Mellanox" + # ── Nivel 4: Proxmox Servers ─────────────────────────────── + - name: "TYAN CAS" ip: "192.168.10.3" - username: "" - password: "" - icon: "switch" - connections: ["Firewall OPNsense"] - - - name: "Access Point EAP610" - ip: "192.168.10.166" - username: "" - password: "" - icon: "ap" - connections: ["Firewall OPNsense"] - - # ── Servidores Dell (iDRAC) ───────────────────────────────── - - name: "iDRAC Servidor 1" - ip: "192.168.10.159" - username: "" - password: "" + username: "root" + password: "Aasi940812" + public_url: "https://192.168.10.3:8006" icon: "server" + type: "proxmox" connections: ["Switch Cisco"] - - name: "iDRAC Servidor 2" - ip: "192.168.10.160" - username: "" - password: "" - icon: "server" - connections: ["Switch Cisco"] - - - name: "Dell Server .185" + - name: "Cisco1" ip: "192.168.10.185" - username: "" - password: "" + username: "root" + password: "Aasi940812" + public_url: "https://192.168.10.185:8006" icon: "server" + type: "proxmox" connections: ["Switch Cisco"] - - name: "Dell Server .187" + - name: "DellT430-2" ip: "192.168.10.187" - username: "" - password: "" + username: "root" + password: "Aasi940812" + public_url: "https://192.168.10.187:8006" icon: "server" + type: "proxmox" connections: ["Switch Cisco"] - # ── Servicios Principales ─────────────────────────────────── - - name: "Servidor Odoo" + # ── VMs de TYAN CAS ──────────────────────────────────────── + - name: "OMV" + ip: "192.168.10.5" + icon: "nas" + type: "vm" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "NocoDB" + ip: "192.168.10.134" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Dashy" + ip: "192.168.10.8" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Paperless-NGX" + ip: "192.168.10.9" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Immich" + ip: "192.168.10.10" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Debian" + ip: "192.168.10.148" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Dockge" + ip: "192.168.10.8" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "BookLore" + ip: "192.168.10.205" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "n8n" + ip: "192.168.10.14" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Navidrome" + ip: "192.168.10.202" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Uptime Kuma" + ip: "192.168.10.16" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Metabase" + ip: "192.168.10.142" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "Gitea" + ip: "192.168.10.150" + public_url: "https://git.consultoria-as.com" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "AMP" + ip: "192.168.10.151" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + - name: "NodeBB" + ip: "192.168.10.191" + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] + + # ── VMs de Cisco1 ────────────────────────────────────────── + - name: "Ubuntu" + ip: "192.168.10.182" + icon: "device" + type: "vm" + parent: "Cisco1" + connections: ["Cisco1"] + + - name: "Tilmatli" + ip: "192.168.10.119" + icon: "device" + type: "vm" + parent: "Cisco1" + connections: ["Cisco1"] + + - name: "WhatsappBot Clawbot" + ip: "192.168.10.68" + icon: "device" + type: "vm" + parent: "Cisco1" + connections: ["Cisco1"] + + - name: "Ubuntu CT" + ip: "192.168.10.182" + icon: "device" + type: "ct" + parent: "Cisco1" + connections: ["Cisco1"] + + - name: "PostgreSQL" + ip: "192.168.10.71" + icon: "device" + type: "ct" + parent: "Cisco1" + connections: ["Cisco1"] + + # ── VMs de DellT430-2 ───────────────────────────────────── + - name: "JobHero" + ip: "192.168.10.197" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Autopartes" + ip: "192.168.10.200" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "ProksBot" + ip: "192.168.10.204" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "CAS PaginasWeb" + ip: "192.168.10.211" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "SIO Mexus" + ip: "192.168.10.207" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Horux" + ip: "192.168.10.212" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "ATLAS GPS" + ip: "192.168.10.216" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Trivy" + ip: "192.168.10.217" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Social Automation" + ip: "192.168.10.218" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Padel" + ip: "192.168.10.219" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "MSP" + ip: "192.168.10.223" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "VoIP" + ip: "192.168.10.228" + icon: "phone" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Sistema Hotel" + ip: "192.168.10.229" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Dashboard TV" + ip: "192.168.10.230" + icon: "device" + type: "vm" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "HoruxDB" + ip: "192.168.10.208" + icon: "device" + type: "ct" + parent: "DellT430-2" + connections: ["DellT430-2"] + + - name: "Odoo" ip: "192.168.10.188" username: "root" password: "Aasi940812" public_url: "https://crm.consultoria-as.com" - icon: "server" - connections: ["Firewall OPNsense"] + icon: "device" + type: "ct" + parent: "DellT430-2" + connections: ["DellT430-2"] - - name: "Servidor Multimedia (Jellyfin)" + - name: "NodeBB 2" + ip: "192.168.10.192" + icon: "device" + type: "ct" + parent: "DellT430-2" + connections: ["DellT430-2"] + + # ── Otros dispositivos (no Proxmox) ──────────────────────── + - name: "Access Point EAP610" + ip: "192.168.10.166" + icon: "ap" + connections: ["Switch Cisco"] + + - name: "Impresora Epson" + ip: "192.168.10.177" + icon: "printer" + connections: ["Switch Cisco"] + + - name: "iDRAC TYAN" + ip: "192.168.10.159" + icon: "server" + connections: ["Switch Cisco"] + + - name: "iDRAC Dell" + ip: "192.168.10.160" + icon: "server" + connections: ["Switch Cisco"] + + - name: "Jellyfin" ip: "192.168.10.103" username: "consultoria-as" password: "Aasi940812" public_url: "https://jellyfin.consultoria-as.com" - icon: "server" - connections: ["Firewall OPNsense"] + icon: "device" + type: "ct" + parent: "TYAN CAS" + connections: ["TYAN CAS"] - - name: "Gitea" - ip: "192.168.10.150" - username: "" - password: "" - public_url: "https://git.consultoria-as.com" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "PostgreSQL" - ip: "192.168.10.71" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "OpenMediaVault (NAS)" - ip: "192.168.10.5" - username: "" - password: "" - icon: "nas" - connections: ["Firewall OPNsense"] - - # ── Monitoreo y Automatización ────────────────────────────── - - name: "Uptime Kuma" - ip: "192.168.10.16" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "n8n" - ip: "192.168.10.14" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Dashy" - ip: "192.168.10.8" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - # ── Gestión Documental y Datos ────────────────────────────── - - name: "Paperless-NGX" - ip: "192.168.10.9" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "NocoDB" - ip: "192.168.10.134" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Metabase" - ip: "192.168.10.142" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - # ── Multimedia y Contenido ────────────────────────────────── - - name: "Immich (Fotos)" - ip: "192.168.10.10" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Navidrome (Música)" - ip: "192.168.10.202" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "BookLore" - ip: "192.168.10.205" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - # ── Proyectos y Aplicaciones ──────────────────────────────── - - name: "Hotel Production" - ip: "192.168.10.200" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Sistema Hotel" - ip: "192.168.10.229" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "HoruxDB" - ip: "192.168.10.208" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Adan Mesh Horux" - ip: "192.168.10.212" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "SIO Complete" - ip: "192.168.10.197" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "ATLAS GPS" - ip: "192.168.10.216" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "CAS Páginas Web" - ip: "192.168.10.211" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "AMP" - ip: "192.168.10.151" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - # ── Comunicaciones ────────────────────────────────────────── - - name: "NodeBB (Foro)" - ip: "192.168.10.191" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "NodeBB 2" - ip: "192.168.10.192" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "VoIP" - ip: "192.168.10.228" - username: "" - password: "" - icon: "phone" - connections: ["Firewall OPNsense"] - - - name: "MSP" - ip: "192.168.10.223" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - # ── Servidores Auxiliares ──────────────────────────────────── - - name: "Debian Server" - ip: "192.168.10.148" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu Server .182" - ip: "192.168.10.182" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Test CAS" - ip: "192.168.10.119" - username: "" - password: "" - icon: "pc" - connections: ["Firewall OPNsense"] - - - name: "Server .198" - ip: "192.168.10.198" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .204" - ip: "192.168.10.204" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .207" - ip: "192.168.10.207" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .217" - ip: "192.168.10.217" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .218" - ip: "192.168.10.218" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .219" - ip: "192.168.10.219" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .221" - ip: "192.168.10.221" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - - name: "Ubuntu .222" - ip: "192.168.10.222" - username: "" - password: "" - icon: "server" - connections: ["Firewall OPNsense"] - - # ── Equipos de Escritorio ─────────────────────────────────── - name: "HP Consultoria-AS" ip: "192.168.10.147" - username: "" - password: "" icon: "pc" - connections: ["Firewall OPNsense"] - - - name: "Desktop PC .57" - ip: "192.168.10.57" - username: "" - password: "" - icon: "pc" - connections: ["Firewall OPNsense"] - - - name: "Desktop PC .143" - ip: "192.168.10.143" - username: "" - password: "" - icon: "pc" - connections: ["Firewall OPNsense"] - - # ── Periféricos ───────────────────────────────────────────── - - name: "Impresora Epson" - ip: "192.168.10.177" - username: "" - password: "" - icon: "printer" - connections: ["Firewall OPNsense"] - - # ── Dashboard ─────────────────────────────────────────────── - - name: "Dashboard TV" - ip: "192.168.10.230" - username: "" - password: "" - icon: "device" - connections: ["Firewall OPNsense"] + connections: ["Switch Cisco"] network_scan: enabled: true diff --git a/frontend/src/components/Topology/NetworkGraph.tsx b/frontend/src/components/Topology/NetworkGraph.tsx index 27ecf4e..9054d33 100644 --- a/frontend/src/components/Topology/NetworkGraph.tsx +++ b/frontend/src/components/Topology/NetworkGraph.tsx @@ -1,50 +1,137 @@ -import { NodeCard } from "./NodeCard"; +import { useState } from "react"; import type { NetworkNode } from "../../types"; interface NetworkGraphProps { nodes: NetworkNode[]; } -const CATEGORY_ORDER: [string, string[]][] = [ - ["Infraestructura", ["firewall", "router", "switch", "ap"]], - ["Servidores", ["server"]], - ["Almacenamiento", ["nas"]], - ["Equipos", ["pc"]], - ["Periféricos", ["printer", "phone", "camera"]], - ["Otros", ["device"]], -]; +const ICON_MAP: Record = { + router: "🌐", firewall: "🛡️", server: "🖥️", switch: "🔀", + ap: "📡", pc: "💻", nas: "💾", printer: "🖨️", + phone: "📞", camera: "📷", device: "📱", +}; -function categorizeNodes(nodes: NetworkNode[]) { - const categorized: { label: string; nodes: NetworkNode[] }[] = []; - const assigned = new Set(); +/* ── Compact VM pill ─────────────────────────────────────── */ +function VmPill({ node }: { node: NetworkNode }) { + const isUp = node.status === "up"; + const dotColor = isUp ? "bg-success" : "bg-danger"; - for (const [label, icons] of CATEGORY_ORDER) { - const matching = nodes.filter( - (n) => icons.includes(n.icon) && !assigned.has(n.ip) - ); - if (matching.length > 0) { - categorized.push({ label, nodes: matching }); - for (const n of matching) assigned.add(n.ip); + return ( +
+ + {node.name} + {node.ip.split(".").pop()} +
+ ); +} + +/* ── Full node card (infrastructure + proxmox) ───────────── */ +function InfraCard({ node, isCenter }: { node: NetworkNode; isCenter?: boolean }) { + 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"; + + return ( +
setShowPass((p) => !p)} + > +
+ {ICON_MAP[node.icon] || "📦"} +
+

{node.name}

+

{node.ip}

+
+ +
+ {node.username && ( +

+ {node.username} + {node.password && ( + + {showPass ? ` / ${node.password}` : " / ••••••"} + + )} +

+ )} + {node.public_url && ( +

{node.public_url}

+ )} +
+ ); +} + +/* ── Proxmox server with its VMs ─────────────────────────── */ +function ProxmoxColumn({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) { + const vmCount = vms.length; + const upCount = vms.filter((v) => v.status === "up").length; + + return ( +
+ {/* Server card */} + + + {/* Vertical connector */} +
+ + {/* VM count badge */} +
+ + {upCount}/{vmCount} VMs + +
+ + {/* VM grid */} +
+ {vms.map((vm) => ( + + ))} +
+
+ ); +} + +/* ── Vertical connector line ─────────────────────────────── */ +function VLine() { + return
; +} + +/* ── Main topology graph ─────────────────────────────────── */ +export function NetworkGraph({ nodes }: NetworkGraphProps) { + // Find nodes by role + 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 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" + ); + + // Group VMs by parent + 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 remaining = nodes.filter((n) => !assigned.has(n.ip)); - if (remaining.length > 0) { - categorized.push({ label: "Otros", nodes: remaining }); - } - - return categorized; -} - -export function NetworkGraph({ nodes }: NetworkGraphProps) { - const categories = categorizeNodes(nodes); const onlineCount = nodes.filter((n) => n.status === "up").length; const total = nodes.length; return (
{/* Summary bar */} -
+
@@ -59,30 +146,74 @@ export function NetworkGraph({ nodes }: NetworkGraphProps) {
| - {total} dispositivos + {proxmoxServers.length} Proxmox + · + + {nodes.filter((n) => n.type === "vm" || n.type === "ct").length} + {" "} + VMs/CTs
- {/* Scrollable grid */} -
- {categories.map((cat) => ( -
-

- {cat.label} - ({cat.nodes.length}) -

-
- {cat.nodes.map((node) => ( - - ))} + {/* Diagram area */} +
+
+ + {/* ── Level 1: Modem ── */} + {modem && } + + + {/* ── Level 2: Firewall ── */} + {firewall && } + + + {/* ── Level 3: Switch ── */} + {switchNode && } + + + {/* ── Branch lines to Proxmox servers ── */} +
+ {/* Horizontal bar spanning all columns */} +
+ {/* Horizontal line */} +
+ + {/* Proxmox columns */} +
+ {proxmoxServers.map((server) => { + const vms = vmsByParent.get(server.name) || []; + return ( +
+ {/* Vertical connector from horizontal bar */} +
+ +
+ ); + })} +
-
- ))} +
+ + {/* ── Other devices ── */} + {otherDevices.length > 0 && ( +
+

+ Otros dispositivos conectados al switch +

+
+ {otherDevices.map((node) => ( + + ))} +
+
+ )} +
); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 9019acc..5e9b4ec 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -7,6 +7,8 @@ export interface NetworkNode { icon: string; status: "up" | "down" | "unknown"; connections: string[]; + type?: "proxmox" | "vm" | "ct" | string; + parent?: string; auto_discovered?: boolean; vendor?: string; }