diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2781986 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.git +.github +.svelte-kit +.vscode +node_modules +.pnpm-store +.DS_Store +Thumbs.db +coverage +dist +.env +.env.* +data/network.json diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..d3aeb35 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,56 @@ +name: Docker + +on: + pull_request: + branches: + - main + push: + branches: + - main + tags: + - "v*.*.*" + +env: + IMAGE_NAME: jcreek23/open-network-diagram + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: docker.io/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha,prefix=sha- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build (PR) / Build+Push (push) + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: ${{ github.event_name == 'push' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 3b462cb..e2cbe7a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ Thumbs.db !.env.example !.env.test +# User-provided Docker data +data/network.json + # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6dca253 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +ENV PNPM_HOME=/pnpm +ENV PATH=$PNPM_HOME:$PATH + +RUN corepack enable + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile --store-dir /pnpm/store + +COPY . . + +ENV DEPLOY_TARGET=docker +RUN pnpm run build:docker + +FROM nginxinc/nginx-unprivileged:1.27-alpine AS runner + +USER root + +RUN apk add --no-cache libcap && \ + setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx + +COPY nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/build /usr/share/nginx/html + +RUN mkdir -p /usr/share/nginx/html/data && \ + chown -R 101:101 /usr/share/nginx/html /var/cache/nginx /var/run /etc/nginx/conf.d + +USER 101 + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ + CMD wget -qO- http://127.0.0.1/ > /dev/null || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/data/network.json.example b/data/network.json.example new file mode 100644 index 0000000..7620d12 --- /dev/null +++ b/data/network.json.example @@ -0,0 +1,54 @@ +{ + "machines": [ + { + "machineName": "Example Host", + "ipAddress": "192.168.1.10", + "role": "Hypervisor", + "operatingSystem": "Linux", + "ports": [ + { + "portName": "eth0", + "speedGbps": 1, + "connectedTo": { + "device": "Example Switch", + "port": "1" + } + } + ], + "software": { + "vms": [ + { + "name": "Example VM", + "role": "Service", + "ipAddress": "192.168.1.20" + } + ] + }, + "hardware": { + "cpu": "CPU model", + "ram": "16GB", + "networkPorts": 1, + "networkPortSpeedGbps": 1, + "gpu": "Optional GPU" + } + } + ], + "devices": [ + { + "name": "Example Switch", + "ipAddress": "192.168.1.2", + "type": "Network Switch", + "notes": "Optional notes", + "ports": [ + { + "portName": "1", + "speedGbps": 1, + "connectedTo": { + "device": "Example Host", + "port": "eth0" + } + } + ] + } + ] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d4f567c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + open-network-diagram: + build: + context: . + dockerfile: Dockerfile + image: open-network-diagram:local + ports: + - "8080:80" + volumes: + - ./data:/usr/share/nginx/html/data:ro + restart: unless-stopped diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..6d3f25b --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location /data/ { + try_files $uri =404; + add_header Cache-Control "no-cache"; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/package.json b/package.json index 0d9979f..ec7bcf1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "vite dev", "build": "vite build", + "build:docker": "DEPLOY_TARGET=docker vite build", + "build:netlify": "DEPLOY_TARGET=netlify vite build", "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -19,6 +21,7 @@ "@eslint/js": "^8.57.1", "@sveltejs/adapter-auto": "^4.0.0", "@sveltejs/adapter-netlify": "^5.0.0", + "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/typography": "^0.5.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9ef4af..b834361 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: '@sveltejs/adapter-netlify': specifier: ^5.0.0 version: 5.0.0(@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))) + '@sveltejs/adapter-static': + specifier: ^3.0.10 + version: 3.0.10(@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))) '@sveltejs/kit': specifier: ^2.16.0 version: 2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) @@ -595,6 +598,11 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 + '@sveltejs/adapter-static@3.0.10': + resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + '@sveltejs/kit@2.20.4': resolution: {integrity: sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q==} engines: {node: '>=18.13'} @@ -2420,6 +2428,10 @@ snapshots: esbuild: 0.24.2 set-cookie-parser: 2.7.1 + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))': + dependencies: + '@sveltejs/kit': 2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) + '@sveltejs/kit@2.20.4(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2))': dependencies: '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.8)(vite@6.2.5(jiti@2.4.2)(lightningcss@1.29.2)) diff --git a/static/data/network.json b/static/data/network.json deleted file mode 100644 index a6a6f8c..0000000 --- a/static/data/network.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "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." - } - ] -} diff --git a/svelte.config.js b/svelte.config.js index 57aec4b..49b4fa4 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,6 +1,15 @@ -import adapter from '@sveltejs/adapter-netlify'; +import netlifyAdapter from '@sveltejs/adapter-netlify'; +import staticAdapter from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +const deployTarget = (process.env.DEPLOY_TARGET ?? 'docker').toLowerCase(); +const adapter = deployTarget === 'netlify' + ? netlifyAdapter() + : staticAdapter({ + fallback: 'index.html', + strict: false + }); + /** @type {import('@sveltejs/kit').Config} */ const config = { // Consult https://svelte.dev/docs/kit/integrations @@ -8,10 +17,7 @@ const config = { 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() + adapter } };