refactor(*): Change to use components and css

This commit is contained in:
Josh Creek
2025-04-13 23:51:16 +01:00
parent d3b24ef764
commit 4247212d1a
7 changed files with 396 additions and 364 deletions
+1 -5
View File
@@ -41,9 +41,5 @@
"esbuild"
]
},
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
"dependencies": {
"cytoscape": "^3.31.2",
"cytoscape-dagre": "^2.5.0"
}
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
}
-43
View File
@@ -7,13 +7,6 @@ settings:
importers:
.:
dependencies:
cytoscape:
specifier: ^3.31.2
version: 3.31.2
cytoscape-dagre:
specifier: ^2.5.0
version: 2.5.0(cytoscape@3.31.2)
devDependencies:
'@eslint/compat':
specifier: ^1.2.5
@@ -698,18 +691,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
cytoscape-dagre@2.5.0:
resolution: {integrity: sha512-VG2Knemmshop4kh5fpLO27rYcyUaaDkRw+6PiX4bstpB+QFt0p2oauMrsjVbUamGWQ6YNavh7x2em2uZlzV44g==}
peerDependencies:
cytoscape: ^3.2.22
cytoscape@3.31.2:
resolution: {integrity: sha512-/eOXg2uGdMdpGlEes5Sf6zE+jUG+05f3htFNQIxLxduOH/SsaUZiPBfAwP1btVIVzsnhiNOdi+hvDRLYfMZjGw==}
engines: {node: '>=0.10'}
dagre@0.8.5:
resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
@@ -880,9 +861,6 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
graphlib@2.1.8:
resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -1035,9 +1013,6 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -1920,18 +1895,6 @@ snapshots:
cssesc@3.0.0: {}
cytoscape-dagre@2.5.0(cytoscape@3.31.2):
dependencies:
cytoscape: 3.31.2
dagre: 0.8.5
cytoscape@3.31.2: {}
dagre@0.8.5:
dependencies:
graphlib: 2.1.8
lodash: 4.17.21
debug@4.4.0:
dependencies:
ms: 2.1.3
@@ -2134,10 +2097,6 @@ snapshots:
graphemer@1.4.0: {}
graphlib@2.1.8:
dependencies:
lodash: 4.17.21
has-flag@4.0.0: {}
ignore@5.3.2: {}
@@ -2249,8 +2208,6 @@ snapshots:
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
+21
View File
@@ -0,0 +1,21 @@
<script lang="ts">
import type { Device } from '../../stores/networkStore';
export let device: Device;
</script>
<div class="device-card">
<h3>{device.name}</h3>
<p><strong>Type:</strong> {device.type}</p>
<p><strong>IP:</strong> {device.ipAddress}</p>
{#if device.notes}
<p><em>{device.notes}</em></p>
{/if}
</div>
<style>
.device-card {
border: 1px dashed #aaa;
padding: 1rem;
border-radius: 5px;
}
</style>
+43
View File
@@ -0,0 +1,43 @@
<script lang="ts">
import type { Machine } from '../../stores/networkStore';
import VMCard from './VMCard.svelte';
export let machine: Machine;
</script>
<div class="machine-card">
<h2>{machine.machineName}</h2>
<p><strong>Role:</strong> {machine.role}</p>
<p><strong>IP Address:</strong> {machine.ipAddress}</p>
<p><strong>OS:</strong> {machine.operatingSystem}</p>
{#if machine.software?.vms?.length}
<details>
<summary>Show VMs (Total: {machine.software.vms.length})</summary>
<div class="vm-list">
{#each machine.software.vms as vm}
<VMCard {vm} />
{/each}
</div>
</details>
{/if}
</div>
<style>
.machine-card {
border: 2px solid #ddd;
padding: 1rem;
border-radius: 8px;
max-width: 300px;
background-color: #f9f9f9;
}
.machine-card h2 {
margin: 0 0 0.5rem;
}
.vm-list {
margin-top: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
</style>
+40 -316
View File
@@ -1,324 +1,48 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { NetworkData } from '../types';
import cytoscape from 'cytoscape';
import dagre from 'cytoscape-dagre';
let cy: cytoscape.Core | undefined;
cytoscape.use(dagre);
import { networkStore, loadNetworkData } from '../../stores/networkStore';
import { get } from 'svelte/store';
const networkData: NetworkData = {
machines: [
{
machineName: 'ProxRouter',
ipAddress: '10.0.0.3',
role: 'Hypervisor',
operatingSystem: 'Proxmox',
software: {
vms: [
{
name: 'OpnSense',
role: 'Router',
ipAddress: '10.0.0.4'
},
{
name: 'PiVPN',
role: 'VPN Server',
ipAddress: '10.0.0.5'
},
{
name: 'PiHole',
role: 'DNS Ad-blocker',
ipAddress: '10.0.0.6'
},
{
name: 'Dashy',
role: 'Dashboard',
ipAddress: '10.0.0.12'
},
{
name: 'DockerHost',
role: 'Docker/Reverse Proxy',
ipAddress: '10.0.0.23'
}
]
},
hardware: {
cpu: 'Intel N100',
ram: '8GB',
networkPorts: 4,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Asustor NAS',
ipAddress: '10.0.0.9',
role: 'NAS',
operatingSystem: 'Asustor ADM',
software: {
vms: []
},
hardware: {
cpu: 'Realtek RTD1296 Quad Core 1.4GHz',
ram: '2GB',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Home Assistant Green',
ipAddress: '10.0.0.13',
role: 'Smart Home Controller',
operatingSystem: 'Home Assistant OS',
software: {
vms: []
},
hardware: {
cpu: 'Home Assistant Custom SoC',
ram: 'Unknown',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Plex Server',
ipAddress: '10.0.0.11',
role: 'Media Server',
operatingSystem: 'Ubuntu Server',
software: {
vms: []
},
hardware: {
cpu: 'Intel N100',
ram: 'Unknown',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Win11 N100',
ipAddress: '10.0.0.8',
role: 'Media Downloader',
operatingSystem: 'Windows 11',
software: {
vms: []
},
hardware: {
cpu: 'Intel N100',
ram: 'Unknown',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Win11 Backblaze NAS',
ipAddress: '10.0.0.7',
role: 'Backup NAS + Hypervisor',
operatingSystem: 'TrueNAS Scale',
software: {
vms: [
{
name: 'Win11-Backblaze',
role: 'Backblaze Backup',
ipAddress: '10.0.0.24'
},
{
name: 'UbuntuDockerHost',
role: 'Docker Host',
ipAddress: '10.0.0.14'
},
{
name: 'MakeMKV',
role: 'Blu-ray Ripper',
ipAddress: '10.0.0.14'
},
{
name: 'Handbrake',
role: 'Video Transcoder',
ipAddress: '10.0.0.15'
},
{
name: 'NextCloud',
role: 'Cloud Storage',
ipAddress: 'TBD'
}
]
},
hardware: {
cpu: 'AMD Ryzen 5 4600G',
ram: '16GB',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: 'EVGA GeForce GTX 1050 Ti'
}
},
{
machineName: 'AI Server',
ipAddress: '10.0.0.20',
role: 'AI Dev/Inference',
operatingSystem: 'Pop!_OS',
software: {
vms: [
{
name: 'Ollama',
role: 'LLM Inference',
ipAddress: '10.0.0.21'
},
{
name: 'Bot Training',
role: 'AI Training',
ipAddress: '10.0.0.22'
}
]
},
hardware: {
cpu: 'AMD Ryzen 5 3600',
ram: '32GB',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: 'Gigabyte GeForce GTX 1080'
}
}
],
devices: [
{
name: 'Router',
ipAddress: '10.0.0.1',
type: 'Gateway',
notes: 'Main internet-facing router'
},
{
name: 'HDHomeRun',
ipAddress: '10.0.0.2',
type: 'TV Tuner'
},
{
name: 'Omada Controller',
ipAddress: '10.0.0.4',
type: 'Network Controller',
notes: 'Access via port 8043'
},
{
name: '3DS',
ipAddress: '10.0.0.17',
type: 'Handheld Console'
},
{
name: '2DS',
ipAddress: '10.0.0.18',
type: 'Handheld Console'
},
{
name: 'Nintendo Switch',
ipAddress: '10.0.0.19',
type: 'Gaming Console',
notes: 'IP to be confirmed'
}
]
};
onMount(() => {
const container = document.getElementById('cy') as HTMLDivElement;
if (!container) {
console.error("Container element '#cy' not found in the DOM.");
return;
}
renderDiagram(networkData, container);
import MachineCard from './MachineCard.svelte';
import DeviceCard from './DeviceCard.svelte';
// Optionally pass in a JSON path as a prop
export let jsonPath: string = '/data/network.json';
// Local state
let data: any;
onMount(async () => {
// Load data into the store
await loadNetworkData(jsonPath);
// Subscribe once or use the $ syntax in markup
data = get(networkStore);
});
function renderDiagram(data: NetworkData, container: HTMLDivElement) {
if (cy) {
cy.destroy();
}
const nodes: cytoscape.NodeDefinition[] = [];
const edges: cytoscape.EdgeDefinition[] = [];
data.machines.forEach((machine) => {
nodes.push({
data: { id: machine.machineName, label: machine.machineName },
classes: 'machine-node',
position: { x: 0, y: 0 } // Initial position
});
machine.software.vms.forEach((vm) => {
nodes.push({
data: { id: vm.name, label: vm.name, parent: machine.machineName },
classes: 'vm-node'
});
});
});
const elementsArray = [...nodes, ...edges];
cy = cytoscape({
container: container,
elements: elementsArray,
style: [
{
selector: 'node',
style: {
'background-color': '#3e4451',
'border-color': '#555555',
'border-width': 2,
color: '#ffffff',
'font-size': '14px',
'text-halign': 'center',
'text-valign': 'top',
'text-margin-y': 20,
padding: '20px'
}
},
{
selector: '.machine-node',
style: {
label: 'data(label)',
'background-color': '#61afef',
color: '#282c34',
height: 150, // Set height and width for the parent node
width: 200,
shape: 'roundrectangle'
}
},
{
selector: '.vm-node',
style: {
label: 'data(label)',
'background-color': '#c6c6c6',
color: '#282c34',
height: 50, // Set height and width for the child nodes
width: 150,
shape: 'roundrectangle'
}
},
{
selector: 'edge',
style: {
width: 3,
'line-color': '#9dbaea',
'target-arrow-color': '#9dbaea',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier'
}
},
{
selector: ':parent',
style: {
'border-width': 3,
'border-color': '#555555'
}
}
],
layout: {
name: 'dagre',
rankDir: 'TB', // Top-to-Bottom direction
nodeSep: 50, // Pixel value between each node on the same level
edgeSep: 30, // Minimum vertical separation thats enforced between edges
rankSep: 80 // Vertical separation that's used between ranks (levels of nodes)
},
minZoom: 1, // Set min and max zoom levels if needed
maxZoom: 3
});
}
</script>
<main>
<div id="cy" style="width: 100%; height: 1000px; background-color: #282c34;"></div>
{#if data}
<div class="diagram-container">
<!-- Render Machines -->
{#each data.machines as machine}
<MachineCard {machine} />
{/each}
<!-- Render Other Devices -->
{#each data.devices as device}
<DeviceCard {device} />
{/each}
</div>
{/if}
</main>
<style>
.diagram-container {
/* Example layout style - customize as needed */
display: flex;
flex-wrap: wrap;
gap: 1rem;
padding: 1rem;
}
</style>
+19
View File
@@ -0,0 +1,19 @@
<script lang="ts">
import type { VMInfo } from '../../stores/networkStore';
export let vm: VMInfo;
</script>
<div class="vm-card">
<p><strong>{vm.name}</strong></p>
<p>Role: {vm.role}</p>
<p>IP: {vm.ipAddress}</p>
</div>
<style>
.vm-card {
padding: 0.5rem;
background-color: #eaeaea;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
+272
View File
@@ -0,0 +1,272 @@
import { writable } from 'svelte/store';
export interface VMInfo {
name: string;
role: string;
ipAddress: string;
}
export interface Software {
vms: VMInfo[];
}
export interface Hardware {
cpu: string;
ram: string;
networkPorts: number;
networkPortSpeedGbps?: number;
gpu?: string | null;
}
export interface Machine {
machineName: string;
ipAddress: string;
role: string;
operatingSystem: string;
software: Software;
hardware: Hardware;
}
export interface Device {
name: string;
ipAddress: string;
type: string;
notes?: string;
}
export interface NetworkData {
machines: Machine[];
devices: Device[];
}
// Create a writable store for the data
export const networkStore = writable<NetworkData>({
machines: [],
devices: []
});
// A function to load JSON from an API or local file if needed
export async function loadNetworkData(jsonPath: string) {
// For a real app, fetch the file or do an import:
// const response = await fetch(jsonPath);
// const data = await response.json();
// networkStore.set(data);
// For now, set a static example:
const exampleData: NetworkData = {
machines: [
{
machineName: 'ProxRouter',
ipAddress: '10.0.0.3',
role: 'Hypervisor',
operatingSystem: 'Proxmox',
software: {
vms: [
{
name: 'OpnSense',
role: 'Router',
ipAddress: '10.0.0.4'
},
{
name: 'PiVPN',
role: 'VPN Server',
ipAddress: '10.0.0.5'
},
{
name: 'PiHole',
role: 'DNS Ad-blocker',
ipAddress: '10.0.0.6'
},
{
name: 'Dashy',
role: 'Dashboard',
ipAddress: '10.0.0.12'
},
{
name: 'DockerHost',
role: 'Docker/Reverse Proxy',
ipAddress: '10.0.0.23'
}
]
},
hardware: {
cpu: 'Intel N100',
ram: '8GB',
networkPorts: 4,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Asustor NAS',
ipAddress: '10.0.0.9',
role: 'NAS',
operatingSystem: 'Asustor ADM',
software: {
vms: []
},
hardware: {
cpu: 'Realtek RTD1296 Quad Core 1.4GHz',
ram: '2GB',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Home Assistant Green',
ipAddress: '10.0.0.13',
role: 'Smart Home Controller',
operatingSystem: 'Home Assistant OS',
software: {
vms: []
},
hardware: {
cpu: 'Home Assistant Custom SoC',
ram: 'Unknown',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Plex Server',
ipAddress: '10.0.0.11',
role: 'Media Server',
operatingSystem: 'Ubuntu Server',
software: {
vms: []
},
hardware: {
cpu: 'Intel N100',
ram: 'Unknown',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Win11 N100',
ipAddress: '10.0.0.8',
role: 'Media Downloader',
operatingSystem: 'Windows 11',
software: {
vms: []
},
hardware: {
cpu: 'Intel N100',
ram: 'Unknown',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: null
}
},
{
machineName: 'Win11 Backblaze NAS',
ipAddress: '10.0.0.7',
role: 'Backup NAS + Hypervisor',
operatingSystem: 'TrueNAS Scale',
software: {
vms: [
{
name: 'Win11-Backblaze',
role: 'Backblaze Backup',
ipAddress: '10.0.0.24'
},
{
name: 'UbuntuDockerHost',
role: 'Docker Host',
ipAddress: '10.0.0.14'
},
{
name: 'MakeMKV',
role: 'Blu-ray Ripper',
ipAddress: '10.0.0.14'
},
{
name: 'Handbrake',
role: 'Video Transcoder',
ipAddress: '10.0.0.15'
},
{
name: 'NextCloud',
role: 'Cloud Storage',
ipAddress: 'TBD'
}
]
},
hardware: {
cpu: 'AMD Ryzen 5 4600G',
ram: '16GB',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: 'EVGA GeForce GTX 1050 Ti'
}
},
{
machineName: 'AI Server',
ipAddress: '10.0.0.20',
role: 'AI Dev/Inference',
operatingSystem: 'Pop!_OS',
software: {
vms: [
{
name: 'Ollama',
role: 'LLM Inference',
ipAddress: '10.0.0.21'
},
{
name: 'Bot Training',
role: 'AI Training',
ipAddress: '10.0.0.22'
}
]
},
hardware: {
cpu: 'AMD Ryzen 5 3600',
ram: '32GB',
networkPorts: 1,
networkPortSpeedGbps: 1,
gpu: 'Gigabyte GeForce GTX 1080'
}
}
],
devices: [
{
name: 'Router',
ipAddress: '10.0.0.1',
type: 'Gateway',
notes: 'Main internet-facing router'
},
{
name: 'HDHomeRun',
ipAddress: '10.0.0.2',
type: 'TV Tuner'
},
{
name: 'Omada Controller',
ipAddress: '10.0.0.4',
type: 'Network Controller',
notes: 'Access via port 8043'
},
{
name: '3DS',
ipAddress: '10.0.0.17',
type: 'Handheld Console'
},
{
name: '2DS',
ipAddress: '10.0.0.18',
type: 'Handheld Console'
},
{
name: 'Nintendo Switch',
ipAddress: '10.0.0.19',
type: 'Gaming Console',
notes: 'IP to be confirmed'
}
]
};
networkStore.set(exampleData);
}