feat(*): Add initial cytoscape version

This commit is contained in:
Josh Creek
2025-04-13 22:24:43 +01:00
parent cd0883bc3e
commit d3b24ef764
22 changed files with 3135 additions and 43 deletions
+23
View File
@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+6
View File
@@ -0,0 +1,6 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
+15
View File
@@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}
+26 -43
View File
@@ -12,7 +12,7 @@
**JSON-based network structure** (editable in UI or manually)
**Interactive network visualisation**
**Persistent storage of network configurations**
**Lightweight Svelte + Express.js**
**Lightweight Svelte**
Use it to **document your home lab, office network, or cloud infrastructure** with an easy-to-use web interface.
@@ -54,29 +54,16 @@ cd open-network-diagram
### **2️⃣ Install Dependencies**
```bash
cd frontend
npm install
cd ../backend
npm install
pnpm install
```
### **3️⃣ Run in Development Mode**
- **Frontend (Svelte)**
```bash
pnpm run dev
```
```bash
cd frontend
npm run dev
```
- Runs at `http://localhost:5173`
- **Backend (Express.js)**
```bash
cd backend
node server.js
```
- Runs at `http://localhost:3000`
- Runs at `http://localhost:5173`
---
@@ -84,12 +71,8 @@ npm install
```
open-network-diagram/
│── backend/ # Express.js backend (handles JSON storage)
│ ├── server.js # Express API
│ ├── package.json # Backend dependencies
│── frontend/ # Svelte frontend
│ ├── src/ # Svelte components
│ ├── package.json # Frontend dependencies
│ ├── package.json # Dependencies
│── Dockerfile # Docker setup
│── .github/workflows/ # CI/CD pipelines
│── README.md # Documentation
@@ -124,25 +107,25 @@ Define your network using **`network.json`**:
```json
{
"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" }
]
},
"hardware": {
"cpu": "Intel N100",
"ram": "8GB",
"networkPorts": 4
}
}
]
"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" }
]
},
"hardware": {
"cpu": "Intel N100",
"ram": "8GB",
"networkPorts": 4
}
}
]
}
```
+37
View File
@@ -0,0 +1,37 @@
import prettier from 'eslint-config-prettier';
import js from '@eslint/js';
import { includeIgnoreFile } from '@eslint/compat';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default ts.config(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
prettier,
...svelte.configs.prettier,
{
languageOptions: {
globals: { ...globals.browser, ...globals.node }
},
rules: { 'no-undef': 'off' }
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
ignores: ['eslint.config.js', 'svelte.config.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
}
}
);
+49
View File
@@ -0,0 +1,49 @@
{
"name": "open-network-diagram",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@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-plugin-svelte": "^3.0.0",
"globals": "^16.0.0",
"prettier": "^3.4.2",
"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-eslint": "^8.20.0",
"vite": "^6.2.5"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"
]
},
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
"dependencies": {
"cytoscape": "^3.31.2",
"cytoscape-dagre": "^2.5.0"
}
}
+2526
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
+13
View File
@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+324
View File
@@ -0,0 +1,324 @@
<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);
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);
});
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>
</main>
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+38
View File
@@ -0,0 +1,38 @@
interface Hardware {
cpu: string;
ram: string;
networkPorts: number;
networkPortSpeedGbps?: number;
gpu?: string;
}
interface VM {
name: string;
role: string;
ipAddress: string;
}
interface Software {
vms: VM[];
}
interface Machine {
machineName: string;
ipAddress: string;
role: string;
operatingSystem: string;
software: Software;
hardware: Hardware;
}
interface NetworkDevice {
name: string;
ipAddress: string;
type: string;
notes?: string;
}
export interface NetworkData {
machines: Machine[];
devices: NetworkDevice[];
}
+7
View File
@@ -0,0 +1,7 @@
<script lang="ts">
import '../app.css';
let { children } = $props();
</script>
{@render children()}
+7
View File
@@ -0,0 +1,7 @@
<script lang="ts">
import NetworkDiagram from '../lib/components/NetworkDiagram.svelte';
</script>
<main>
<NetworkDiagram />
</main>
+1
View File
@@ -0,0 +1 @@
export const ssr = false;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+18
View File
@@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;
+19
View File
@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
+7
View File
@@ -0,0 +1,7 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()]
});