mirror of
https://github.com/jcreek/OpenNetworkDiagram.git
synced 2026-07-12 18:43:44 +00:00
feat(#2): replace card layout with cytoscape network graph
This commit is contained in:
Generated
+5987
File diff suppressed because it is too large
Load Diff
+10
-6
@@ -15,7 +15,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^8.57.1",
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/adapter-netlify": "^5.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
@@ -23,17 +24,19 @@
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/cytoscape-dagre": "^2.3.3",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^6.2.5"
|
||||
},
|
||||
@@ -44,6 +47,7 @@
|
||||
},
|
||||
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
|
||||
"dependencies": {
|
||||
"leader-line": "^1.0.8"
|
||||
"cytoscape": "^3.31.2",
|
||||
"cytoscape-dagre": "^2.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
<script src="/node_modules/leader-line/leader-line.min.js"></script>
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { NetworkDevice } from '../../lib/types';
|
||||
export let device: NetworkDevice;
|
||||
</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}
|
||||
|
||||
{#if device.ports && device.ports.length}
|
||||
<h4>Ports:</h4>
|
||||
<div class="ports-container">
|
||||
{#each device.ports as port}
|
||||
<div class="port-item" data-port-key={device.name + '-' + port.portName}>
|
||||
<p>{port.portName} ({port.speedGbps ?? 1}GbE)</p>
|
||||
{#if port.connectedTo}
|
||||
<p>Connected to: {port.connectedTo}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.device-card {
|
||||
border: 1px dashed #aaa;
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
max-width: 300px;
|
||||
}
|
||||
.ports-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.port-item {
|
||||
background-color: #ffe7d6;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,59 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Machine } from '../../lib/types';
|
||||
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.ports && machine.ports.length}
|
||||
<h4>Ports:</h4>
|
||||
<div class="ports-container">
|
||||
{#each machine.ports as port}
|
||||
<div class="port-item" data-port-key="{machine.machineName}-{port.portName}">
|
||||
<p>{port.portName} ({port.speedGbps ?? 1}GbE)</p>
|
||||
{#if port.connectedTo}
|
||||
<p>Connected to: {port.connectedTo}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- VMs -->
|
||||
{#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;
|
||||
}
|
||||
.ports-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.port-item {
|
||||
background-color: #e7f1f9;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let isOpen = false;
|
||||
export let title = '';
|
||||
export let maxWidth = '680px';
|
||||
|
||||
const dispatch = createEventDispatcher<{ close: void }>();
|
||||
|
||||
function closeModal() {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
function onBackdropClick(event: MouseEvent) {
|
||||
if (event.target === event.currentTarget) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape' && isOpen) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onWindowKeydown} />
|
||||
|
||||
{#if isOpen}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="modal-backdrop" on:click={onBackdropClick}>
|
||||
<div class="modal-container" style={`max-width: ${maxWidth}`}>
|
||||
<header class="modal-header">
|
||||
{#if title}
|
||||
<h2>{title}</h2>
|
||||
{/if}
|
||||
<button type="button" class="close-button" on:click={closeModal} aria-label="Close">×</button>
|
||||
</header>
|
||||
<div class="modal-body">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background: rgba(15, 23, 42, 0.58);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
width: min(100%, 680px);
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.24);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
padding: 0.9rem 1rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: #475569;
|
||||
font-size: 1.7rem;
|
||||
line-height: 1;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.modal-backdrop {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
<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>
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { NetworkData } from '../types';
|
||||
|
||||
function isNetworkData(value: unknown): value is NetworkData {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = value as Partial<NetworkData>;
|
||||
return Array.isArray(candidate.machines) && Array.isArray(candidate.devices);
|
||||
}
|
||||
|
||||
export default async function loadNetworkData(jsonPath: string): Promise<NetworkData> {
|
||||
const response = await fetch(jsonPath, {
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load ${jsonPath} (${response.status} ${response.statusText})`);
|
||||
}
|
||||
|
||||
const rawData: unknown = await response.json();
|
||||
if (!isNetworkData(rawData)) {
|
||||
throw new Error(`JSON at ${jsonPath} is not valid network data`);
|
||||
}
|
||||
|
||||
return rawData;
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
import type { ConnectedPortRef, NetworkData, Port } from '../types';
|
||||
import type { GraphEdgeElement, GraphNodeElement, GraphTransformResult } from './types';
|
||||
|
||||
interface EndpointOwner {
|
||||
nodeId: string;
|
||||
name: string;
|
||||
ports: Map<string, Port>;
|
||||
}
|
||||
|
||||
function toMachineNodeId(name: string): string {
|
||||
return `machine:${encodeURIComponent(name)}`;
|
||||
}
|
||||
|
||||
function toDeviceNodeId(name: string): string {
|
||||
return `device:${encodeURIComponent(name)}`;
|
||||
}
|
||||
|
||||
function toVmNodeId(hostName: string, vmName: string): string {
|
||||
return `vm:${encodeURIComponent(hostName)}:${encodeURIComponent(vmName)}`;
|
||||
}
|
||||
|
||||
function parseConnectedTo(connection: Port['connectedTo']): ConnectedPortRef | null {
|
||||
if (!connection?.device || !connection?.port) return null;
|
||||
return {
|
||||
device: connection.device.trim(),
|
||||
port: connection.port.trim()
|
||||
};
|
||||
}
|
||||
|
||||
function hasReciprocalLink(
|
||||
targetPort: Port | undefined,
|
||||
expectedDeviceName: string,
|
||||
expectedPortName: string
|
||||
): boolean {
|
||||
const reverse = parseConnectedTo(targetPort?.connectedTo);
|
||||
if (!reverse) return false;
|
||||
return reverse.device === expectedDeviceName && reverse.port === expectedPortName;
|
||||
}
|
||||
|
||||
function connectionKey(a: string, b: string): string {
|
||||
return a < b ? `${a}|${b}` : `${b}|${a}`;
|
||||
}
|
||||
|
||||
function edgeSpeed(a: number | undefined, b: number | undefined): number | undefined {
|
||||
if (typeof a === 'number' && typeof b === 'number') return Math.min(a, b);
|
||||
return typeof a === 'number' ? a : b;
|
||||
}
|
||||
|
||||
export function transformNetworkDataToGraph(data: NetworkData): GraphTransformResult {
|
||||
const nodes: GraphNodeElement[] = [];
|
||||
const edges: GraphEdgeElement[] = [];
|
||||
const warnings: string[] = [];
|
||||
const machineVmIndex: Record<
|
||||
string,
|
||||
{
|
||||
vmNodeIds: string[];
|
||||
hostingEdgeIds: string[];
|
||||
vmCount: number;
|
||||
}
|
||||
> = {};
|
||||
const ownersByName = new Map<string, EndpointOwner>();
|
||||
const seenWarnings = new Set<string>();
|
||||
|
||||
const warn = (message: string) => {
|
||||
if (!seenWarnings.has(message)) {
|
||||
seenWarnings.add(message);
|
||||
warnings.push(message);
|
||||
}
|
||||
};
|
||||
|
||||
const addOwner = (owner: EndpointOwner) => {
|
||||
if (ownersByName.has(owner.name)) {
|
||||
warn(
|
||||
`Duplicate node name "${owner.name}" found. First occurrence will be used for link resolution.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
ownersByName.set(owner.name, owner);
|
||||
};
|
||||
|
||||
let hostingEdgeCounter = 0;
|
||||
|
||||
for (const machine of data.machines) {
|
||||
const machineNodeId = toMachineNodeId(machine.machineName);
|
||||
const machinePortsList = [...(machine.ports ?? [])];
|
||||
const machineVms = machine.software.vms.map((vm) => ({
|
||||
name: vm.name,
|
||||
ip: vm.ipAddress,
|
||||
role: vm.role
|
||||
}));
|
||||
|
||||
nodes.push({
|
||||
data: {
|
||||
id: machineNodeId,
|
||||
label: machine.machineName,
|
||||
rawName: machine.machineName,
|
||||
kind: 'machine',
|
||||
nodeWidth: 200,
|
||||
nodeHeight: 110,
|
||||
details: {
|
||||
type: 'machine',
|
||||
name: machine.machineName,
|
||||
ip: machine.ipAddress,
|
||||
role: machine.role,
|
||||
os: machine.operatingSystem,
|
||||
cpu: machine.hardware.cpu,
|
||||
ram: machine.hardware.ram,
|
||||
gpu: machine.hardware.gpu,
|
||||
ports: machinePortsList,
|
||||
vmCount: machineVms.length,
|
||||
vms: machineVms
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
machineVmIndex[machineNodeId] = {
|
||||
vmNodeIds: [],
|
||||
hostingEdgeIds: [],
|
||||
vmCount: machine.software.vms.length
|
||||
};
|
||||
|
||||
for (const vm of machine.software.vms) {
|
||||
const vmNodeId = toVmNodeId(machine.machineName, vm.name);
|
||||
|
||||
nodes.push({
|
||||
data: {
|
||||
id: vmNodeId,
|
||||
label: vm.name,
|
||||
rawName: vm.name,
|
||||
kind: 'vm',
|
||||
hostMachineId: machineNodeId,
|
||||
nodeWidth: 140,
|
||||
nodeHeight: 66,
|
||||
details: {
|
||||
type: 'vm',
|
||||
name: vm.name,
|
||||
ip: vm.ipAddress,
|
||||
role: vm.role,
|
||||
hostName: machine.machineName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hostingEdgeCounter += 1;
|
||||
const hostingEdgeId = `hosting:${hostingEdgeCounter}`;
|
||||
edges.push({
|
||||
data: {
|
||||
id: hostingEdgeId,
|
||||
source: machineNodeId,
|
||||
target: vmNodeId,
|
||||
kind: 'hosting',
|
||||
label: 'hosts'
|
||||
}
|
||||
});
|
||||
|
||||
machineVmIndex[machineNodeId].vmNodeIds.push(vmNodeId);
|
||||
machineVmIndex[machineNodeId].hostingEdgeIds.push(hostingEdgeId);
|
||||
}
|
||||
|
||||
const machinePorts = new Map<string, Port>();
|
||||
for (const port of machine.ports ?? []) {
|
||||
machinePorts.set(port.portName, port);
|
||||
}
|
||||
|
||||
addOwner({
|
||||
nodeId: machineNodeId,
|
||||
name: machine.machineName,
|
||||
ports: machinePorts
|
||||
});
|
||||
}
|
||||
|
||||
for (const device of data.devices) {
|
||||
const deviceNodeId = toDeviceNodeId(device.name);
|
||||
const devicePorts = [...(device.ports ?? [])];
|
||||
|
||||
nodes.push({
|
||||
data: {
|
||||
id: deviceNodeId,
|
||||
label: device.name,
|
||||
rawName: device.name,
|
||||
kind: 'device',
|
||||
nodeWidth: 176,
|
||||
nodeHeight: 116,
|
||||
details: {
|
||||
type: 'device',
|
||||
name: device.name,
|
||||
ip: device.ipAddress,
|
||||
deviceType: device.type,
|
||||
notes: device.notes,
|
||||
ports: devicePorts
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const devicePortMap = new Map<string, Port>();
|
||||
for (const port of device.ports ?? []) {
|
||||
devicePortMap.set(port.portName, port);
|
||||
}
|
||||
|
||||
addOwner({
|
||||
nodeId: deviceNodeId,
|
||||
name: device.name,
|
||||
ports: devicePortMap
|
||||
});
|
||||
}
|
||||
|
||||
const seenPhysicalConnections = new Set<string>();
|
||||
let physicalEdgeCounter = 0;
|
||||
|
||||
for (const owner of ownersByName.values()) {
|
||||
for (const port of owner.ports.values()) {
|
||||
const connectedTo = parseConnectedTo(port.connectedTo);
|
||||
if (!connectedTo) continue;
|
||||
|
||||
const targetOwner = ownersByName.get(connectedTo.device);
|
||||
if (!targetOwner) {
|
||||
warn(
|
||||
`Link from "${owner.name}:${port.portName}" references unknown device "${connectedTo.device}".`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetPort = targetOwner.ports.get(connectedTo.port);
|
||||
if (!targetPort) {
|
||||
warn(
|
||||
`Link from "${owner.name}:${port.portName}" references unknown port "${connectedTo.device}:${connectedTo.port}".`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const a = `${owner.nodeId}:${port.portName}`;
|
||||
const b = `${targetOwner.nodeId}:${connectedTo.port}`;
|
||||
const dedupeKey = connectionKey(a, b);
|
||||
if (seenPhysicalConnections.has(dedupeKey)) continue;
|
||||
seenPhysicalConnections.add(dedupeKey);
|
||||
|
||||
const reciprocal = hasReciprocalLink(targetPort, owner.name, port.portName);
|
||||
if (!reciprocal) {
|
||||
warn(
|
||||
`Non-reciprocal link: "${owner.name}:${port.portName}" points to "${connectedTo.device}:${connectedTo.port}" but reverse link is missing.`
|
||||
);
|
||||
}
|
||||
|
||||
physicalEdgeCounter += 1;
|
||||
const speedGbps = edgeSpeed(port.speedGbps, targetPort.speedGbps);
|
||||
|
||||
edges.push({
|
||||
data: {
|
||||
id: `physical:${physicalEdgeCounter}`,
|
||||
source: owner.nodeId,
|
||||
target: targetOwner.nodeId,
|
||||
kind: 'physical',
|
||||
label: speedGbps ? `${speedGbps}GbE` : undefined,
|
||||
sourcePort: port.portName,
|
||||
targetPort: connectedTo.port,
|
||||
speedGbps,
|
||||
reciprocal
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { nodes, edges, warnings, machineVmIndex };
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
export type GraphNodeKind = 'machine' | 'device' | 'vm';
|
||||
export type GraphEdgeKind = 'physical' | 'hosting';
|
||||
|
||||
export interface PortDetails {
|
||||
portName: string;
|
||||
speedGbps?: number;
|
||||
connectedTo?: {
|
||||
device: string;
|
||||
port: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MachineDetails {
|
||||
type: 'machine';
|
||||
name: string;
|
||||
ip: string;
|
||||
role: string;
|
||||
os: string;
|
||||
cpu: string;
|
||||
ram: string;
|
||||
gpu?: string;
|
||||
ports: PortDetails[];
|
||||
vmCount: number;
|
||||
vms: Array<{
|
||||
name: string;
|
||||
ip: string;
|
||||
role: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface DeviceDetails {
|
||||
type: 'device';
|
||||
name: string;
|
||||
ip: string;
|
||||
deviceType: string;
|
||||
notes?: string;
|
||||
ports: PortDetails[];
|
||||
}
|
||||
|
||||
export interface VmDetails {
|
||||
type: 'vm';
|
||||
name: string;
|
||||
ip: string;
|
||||
role: string;
|
||||
hostName: string;
|
||||
}
|
||||
|
||||
export type GraphNodeDetails = MachineDetails | DeviceDetails | VmDetails;
|
||||
|
||||
export interface GraphNodeData {
|
||||
id: string;
|
||||
label: string;
|
||||
kind: GraphNodeKind;
|
||||
rawName?: string;
|
||||
hostMachineId?: string;
|
||||
nodeWidth?: number;
|
||||
nodeHeight?: number;
|
||||
details?: GraphNodeDetails;
|
||||
}
|
||||
|
||||
export interface GraphEdgeData {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
kind: GraphEdgeKind;
|
||||
label?: string;
|
||||
sourcePort?: string;
|
||||
targetPort?: string;
|
||||
speedGbps?: number;
|
||||
reciprocal?: boolean;
|
||||
}
|
||||
|
||||
export interface GraphNodeElement {
|
||||
data: GraphNodeData;
|
||||
}
|
||||
|
||||
export interface GraphEdgeElement {
|
||||
data: GraphEdgeData;
|
||||
}
|
||||
|
||||
export interface GraphTransformResult {
|
||||
nodes: GraphNodeElement[];
|
||||
edges: GraphEdgeElement[];
|
||||
warnings: string[];
|
||||
machineVmIndex: Record<
|
||||
string,
|
||||
{
|
||||
vmNodeIds: string[];
|
||||
hostingEdgeIds: string[];
|
||||
vmCount: number;
|
||||
}
|
||||
>;
|
||||
}
|
||||
+6
-1
@@ -1,7 +1,12 @@
|
||||
export interface Port {
|
||||
portName: string;
|
||||
speedGbps?: number; // default to 1 if not provided
|
||||
connectedTo?: string; // e.g. "Router-LAN2"
|
||||
connectedTo?: ConnectedPortRef;
|
||||
}
|
||||
|
||||
export interface ConnectedPortRef {
|
||||
device: string;
|
||||
port: string;
|
||||
}
|
||||
|
||||
export interface Hardware {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// Simple grid-based A* pathfinding for orthogonal routing
|
||||
// Returns a list of [x, y] points from start to end, or [] if no path found
|
||||
|
||||
export type Point = [number, number];
|
||||
|
||||
interface Node {
|
||||
x: number;
|
||||
y: number;
|
||||
g: number;
|
||||
h: number;
|
||||
f: number;
|
||||
parent?: Node;
|
||||
}
|
||||
|
||||
export function astar(grid: number[][], start: Point, end: Point): Point[] {
|
||||
const height = grid.length;
|
||||
const width = grid[0].length;
|
||||
const open: Node[] = [];
|
||||
const closed: boolean[][] = Array.from({ length: height }, () => Array(width).fill(false));
|
||||
|
||||
function heuristic([x, y]: Point): number {
|
||||
// Manhattan distance
|
||||
return Math.abs(x - end[0]) + Math.abs(y - end[1]);
|
||||
}
|
||||
|
||||
open.push({ x: start[0], y: start[1], g: 0, h: heuristic(start), f: heuristic(start) });
|
||||
|
||||
while (open.length > 0) {
|
||||
// Get node with lowest f
|
||||
open.sort((a, b) => a.f - b.f);
|
||||
const current = open.shift()!;
|
||||
if (current.x === end[0] && current.y === end[1]) {
|
||||
// Reconstruct path
|
||||
const path: Point[] = [];
|
||||
let node: Node | undefined = current;
|
||||
while (node) {
|
||||
path.push([node.x, node.y]);
|
||||
node = node.parent;
|
||||
}
|
||||
return path.reverse();
|
||||
}
|
||||
closed[current.y][current.x] = true;
|
||||
// Explore neighbors (orthogonal only)
|
||||
for (const [dx, dy] of [[1,0], [-1,0], [0,1], [0,-1]]) {
|
||||
const nx = current.x + dx;
|
||||
const ny = current.y + dy;
|
||||
if (nx < 0 || ny < 0 || nx >= width || ny >= height) continue;
|
||||
if (grid[ny][nx] === 1 || closed[ny][nx]) continue; // Obstacle or closed
|
||||
const g = current.g + 1;
|
||||
const h = heuristic([nx, ny]);
|
||||
const existing = open.find(n => n.x === nx && n.y === ny);
|
||||
if (!existing) {
|
||||
open.push({ x: nx, y: ny, g, h, f: g + h, parent: current });
|
||||
} else if (g < existing.g) {
|
||||
existing.g = g;
|
||||
existing.f = g + h;
|
||||
existing.parent = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { NetworkData } from '../lib/types';
|
||||
|
||||
// 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 with 'ports' and sample connections:
|
||||
const exampleData: NetworkData = {
|
||||
machines: [
|
||||
{
|
||||
machineName: 'ProxRouter',
|
||||
ipAddress: '10.0.0.3',
|
||||
role: 'Hypervisor',
|
||||
operatingSystem: 'Proxmox',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 10,
|
||||
// Connect to the router's LAN2
|
||||
connectedTo: 'Router-LAN2'
|
||||
},
|
||||
{
|
||||
portName: 'eth1',
|
||||
speedGbps: 1
|
||||
// Not connected
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1
|
||||
// No connection
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1
|
||||
// Not connected
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1
|
||||
// Not connected
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1
|
||||
// Not connected
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1
|
||||
// Not connected
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 10
|
||||
// Not connected
|
||||
}
|
||||
],
|
||||
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',
|
||||
ports: [
|
||||
{
|
||||
portName: 'LAN1',
|
||||
speedGbps: 1,
|
||||
// Connect to Omada Controller
|
||||
connectedTo: 'Omada Controller-eth0'
|
||||
},
|
||||
{
|
||||
portName: 'LAN2',
|
||||
speedGbps: 10,
|
||||
// Connected to ProxRouter
|
||||
connectedTo: 'ProxRouter-eth0'
|
||||
},
|
||||
{
|
||||
portName: 'LAN3',
|
||||
speedGbps: 1,
|
||||
// Connect to HDHomeRun
|
||||
connectedTo: 'HDHomeRun-eth0'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'HDHomeRun',
|
||||
ipAddress: '10.0.0.2',
|
||||
type: 'TV Tuner',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1,
|
||||
connectedTo: 'Router-LAN3'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Omada Controller',
|
||||
ipAddress: '10.0.0.4',
|
||||
type: 'Network Controller',
|
||||
notes: 'Access via port 8043',
|
||||
ports: [
|
||||
{
|
||||
portName: 'eth0',
|
||||
speedGbps: 1,
|
||||
connectedTo: 'Router-LAN1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '3DS',
|
||||
ipAddress: '10.0.0.17',
|
||||
type: 'Handheld Console'
|
||||
// No ports => Wi-Fi only
|
||||
},
|
||||
{
|
||||
name: '2DS',
|
||||
ipAddress: '10.0.0.18',
|
||||
type: 'Handheld Console'
|
||||
// No ports => Wi-Fi only
|
||||
},
|
||||
{
|
||||
name: 'Nintendo Switch',
|
||||
ipAddress: '10.0.0.19',
|
||||
type: 'Gaming Console',
|
||||
notes: 'IP to be confirmed'
|
||||
// No ports => Wi-Fi only
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
networkStore.set(exampleData);
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
{
|
||||
"machines": [
|
||||
{
|
||||
"machineName": "ProxRouter",
|
||||
"ipAddress": "10.0.0.3",
|
||||
"role": "Hypervisor",
|
||||
"operatingSystem": "Proxmox",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "eth1",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "eth2",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "eth3",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "WAN Uplink",
|
||||
"port": "wan"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": {
|
||||
"vms": [
|
||||
{
|
||||
"name": "OpnSense",
|
||||
"role": "Router/Firewall/Gateway (DHCP 10.0.0.100-254)",
|
||||
"ipAddress": "10.0.0.1"
|
||||
},
|
||||
{
|
||||
"name": "TPLink Omada Controller",
|
||||
"role": "Network Controller (:8043)",
|
||||
"ipAddress": "10.0.0.4"
|
||||
},
|
||||
{ "name": "PiVPN (WireGuard)", "role": "VPN Server", "ipAddress": "10.0.0.5" },
|
||||
{
|
||||
"name": "PiHole",
|
||||
"role": "DNS Ad-blocker (installed, not in active use)",
|
||||
"ipAddress": "10.0.0.6"
|
||||
},
|
||||
{ "name": "Dashy", "role": "Dashboard", "ipAddress": "10.0.0.12" },
|
||||
{
|
||||
"name": "ProxRouter-Docker",
|
||||
"role": "Docker host (Nginx reverse proxy, RustDesk, TwinGate)",
|
||||
"ipAddress": "10.0.0.23"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hardware": {
|
||||
"cpu": "Intel N100",
|
||||
"ram": "8GB",
|
||||
"networkPorts": 4,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "Asustor NAS",
|
||||
"ipAddress": "10.0.0.9",
|
||||
"role": "NAS",
|
||||
"operatingSystem": "Asustor ADM",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": { "vms": [] },
|
||||
"hardware": {
|
||||
"cpu": "Realtek RTD1296 Quad Core 1.4GHz",
|
||||
"ram": "2GB",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "Home Assistant Green",
|
||||
"ipAddress": "10.0.0.13",
|
||||
"role": "Smart Home Controller",
|
||||
"operatingSystem": "Home Assistant OS",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": { "vms": [] },
|
||||
"hardware": {
|
||||
"cpu": "Home Assistant Custom SoC",
|
||||
"ram": "Unknown",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "Plex Server",
|
||||
"ipAddress": "10.0.0.11",
|
||||
"role": "Media Server",
|
||||
"operatingSystem": "Ubuntu Server",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "5"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": { "vms": [] },
|
||||
"hardware": {
|
||||
"cpu": "Intel N100",
|
||||
"ram": "Unknown",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "Win11 N100",
|
||||
"ipAddress": "10.0.0.8",
|
||||
"role": "Media Downloader",
|
||||
"operatingSystem": "Windows 11",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": { "vms": [] },
|
||||
"hardware": {
|
||||
"cpu": "Intel N100",
|
||||
"ram": "Unknown",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "Win11 Backblaze NAS",
|
||||
"ipAddress": "10.0.0.7",
|
||||
"role": "Backup NAS + Hypervisor",
|
||||
"operatingSystem": "TrueNAS Scale",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": {
|
||||
"vms": [
|
||||
{ "name": "Win11NAS", "role": "Backblaze Backup VM", "ipAddress": "10.0.0.24" },
|
||||
{ "name": "MakeMKV", "role": "Blu-ray Ripper", "ipAddress": "10.0.0.14" },
|
||||
{ "name": "Handbrake", "role": "Video Transcoder", "ipAddress": "10.0.0.15" }
|
||||
]
|
||||
},
|
||||
"hardware": {
|
||||
"cpu": "AMD Ryzen 5 4600G",
|
||||
"ram": "16GB",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1,
|
||||
"gpu": "EVGA GeForce GTX 1050 Ti"
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "TrueNAS Host",
|
||||
"ipAddress": "10.0.0.10",
|
||||
"role": "Storage + VM Host",
|
||||
"operatingSystem": "TrueNAS Scale",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "8"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": {
|
||||
"vms": [
|
||||
{
|
||||
"name": "TrueNAS-Docker",
|
||||
"role": "Docker host on TrueNAS",
|
||||
"ipAddress": "10.0.0.25"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hardware": {
|
||||
"cpu": "Unknown",
|
||||
"ram": "Unknown",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "AI Server",
|
||||
"ipAddress": "10.0.0.20",
|
||||
"role": "AI Dev/Inference",
|
||||
"operatingSystem": "Pop!_OS",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "9"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"machineName": "Immich Mini PC",
|
||||
"ipAddress": "10.0.0.30",
|
||||
"role": "Photo Server",
|
||||
"operatingSystem": "Linux",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "10"
|
||||
}
|
||||
}
|
||||
],
|
||||
"software": { "vms": [] },
|
||||
"hardware": {
|
||||
"cpu": "Unknown",
|
||||
"ram": "Unknown",
|
||||
"networkPorts": 1,
|
||||
"networkPortSpeedGbps": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"name": "Gigabit Switch",
|
||||
"ipAddress": "unknown",
|
||||
"type": "Network Switch",
|
||||
"notes": "Main 24-port switch. Wireless access point uplinks through this switch.",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "1",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "ProxRouter",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "2",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "ProxRouter",
|
||||
"port": "eth2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "3",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Asustor NAS",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "4",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Home Assistant Green",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "5",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Plex Server",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "6",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Win11 N100",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "7",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Win11 Backblaze NAS",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "8",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "TrueNAS Host",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "9",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "AI Server",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "10",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Immich Mini PC",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "11",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "HDHomeRun",
|
||||
"port": "eth0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"portName": "12",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "13",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "14",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "15",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "16",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "17",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "18",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "19",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "20",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "21",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "22",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "23",
|
||||
"speedGbps": 1
|
||||
},
|
||||
{
|
||||
"portName": "24",
|
||||
"speedGbps": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "WAN Uplink",
|
||||
"ipAddress": "unknown",
|
||||
"type": "ISP/WAN",
|
||||
"notes": "Upstream internet link (modem/ONT/ISP path).",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "wan",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "ProxRouter",
|
||||
"port": "eth3"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HDHomeRun",
|
||||
"ipAddress": "10.0.0.2",
|
||||
"type": "TV Tuner",
|
||||
"ports": [
|
||||
{
|
||||
"portName": "eth0",
|
||||
"speedGbps": 1,
|
||||
"connectedTo": {
|
||||
"device": "Gigabit Switch",
|
||||
"port": "11"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "3DS",
|
||||
"ipAddress": "10.0.0.17",
|
||||
"type": "Handheld Console",
|
||||
"notes": "Wi-Fi only."
|
||||
},
|
||||
{
|
||||
"name": "2DS",
|
||||
"ipAddress": "10.0.0.18",
|
||||
"type": "Handheld Console",
|
||||
"notes": "Wi-Fi only."
|
||||
},
|
||||
{
|
||||
"name": "Nintendo Switch",
|
||||
"ipAddress": "10.0.0.19",
|
||||
"type": "Gaming Console",
|
||||
"notes": "Wi-Fi only."
|
||||
},
|
||||
{
|
||||
"name": "NanoKVM Lite",
|
||||
"ipAddress": "10.0.0.26",
|
||||
"type": "KVM Device",
|
||||
"notes": "Port link not yet modeled."
|
||||
},
|
||||
{
|
||||
"name": "UPS Pi",
|
||||
"ipAddress": "10.0.0.27",
|
||||
"type": "Power Monitoring Device",
|
||||
"notes": "Port link not yet modeled."
|
||||
},
|
||||
{
|
||||
"name": "Waveshare",
|
||||
"ipAddress": "10.0.0.28",
|
||||
"type": "Peripheral Device",
|
||||
"notes": "Port link not yet modeled."
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user