Browse Source

Updated schema

Tim Jones 1 month ago
parent
commit
d8ff5508b6

+ 4 - 2
RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs

@@ -137,7 +137,8 @@ public sealed class YamlResourceCollection(
 
             var rootToSave = new YamlRoot {
                 Version = RackPeekConfigMigrationDeserializer.ListOfMigrations.Count,
-                Resources = resourceCollection.Resources
+                Resources = resourceCollection.Resources,
+                Connections = resourceCollection.Connections
             };
 
             await SaveRootAsync(rootToSave);
@@ -286,7 +287,8 @@ public sealed class YamlResourceCollection(
             // Always write current schema version when app writes the file.
             var root = new YamlRoot {
                 Version = _currentSchemaVersion,
-                Resources = resourceCollection.Resources
+                Resources = resourceCollection.Resources,
+                Connections = resourceCollection.Connections
             };
 
             await SaveRootAsync(root);

+ 5 - 1
RackPeek.Domain/Resources/AccessPoints/AccessPoint.cs

@@ -1,7 +1,11 @@
+using RackPeek.Domain.Resources.Servers;
+using RackPeek.Domain.Resources.SubResources;
+
 namespace RackPeek.Domain.Resources.AccessPoints;
 
-public class AccessPoint : Hardware.Hardware {
+public class AccessPoint : Hardware.Hardware, IPortResource {
     public const string KindLabel = "AccessPoint";
     public string? Model { get; set; }
     public double? Speed { get; set; }
+    public List<Port>? Ports { get; set; }
 }

+ 253 - 419
RackPeek.Web.Viewer/wwwroot/config/config.yaml

@@ -1,513 +1,347 @@
+version: 3
 resources:
-  # ------------------------
-  # Servers
-  # ------------------------
   - kind: Server
-    name: proxmox-node01
+    ram:
+      size: 128
+      mts: 3200
+    ipmi: true
     cpus:
       - model: AMD EPYC 7302P
         cores: 16
         threads: 32
-    ram:
-      size: 128gb
-      mts: 3200
     drives:
       - type: ssd
-        size: 1tb
+        size: 1024
       - type: ssd
-        size: 1tb
-    nics:
+        size: 1024
+    ports:
       - type: rj45
-        speed: 1gb
-        ports: 2
+        speed: 1
+        count: 2
       - type: sfp+
-        speed: 10gb
-        ports: 2
-    ipmi: true
-
+        speed: 10
+        count: 2
+    name: proxmox-node01
+    tags:
+      - prod
+      - infra
+    labels:
+      install-date: 2023-02-11
+      rack-unit: U10
+    notes: |-
+      # Proxmox main node
+      - 400w idle
+      - 1200w load
   - kind: Server
-    name: proxmox-node02
+    ram:
+      size: 96
+      mts: 2666
+    ipmi: true
     cpus:
       - model: Intel Xeon Silver 4210
         cores: 10
         threads: 20
-    ram:
-      size: 96gb
-      mts: 2666
     drives:
       - type: ssd
-        size: 1tb
+        size: 1024
       - type: hdd
-        size: 4tb
-    nics:
+        size: 4096
+    ports:
       - type: rj45
-        speed: 1gb
-        ports: 2
+        speed: 1
+        count: 2
       - type: sfp+
-        speed: 10gb
-        ports: 1
-    ipmi: true
-
+        speed: 10
+        count: 1
+    name: proxmox-node02
+    tags:
+      - prod
+      - infra
+    labels:
+      install-date: 2023-02-12
+      rack-unit: U11
   - kind: Server
-    name: truenas-storage
+    ram:
+      size: 64
+      mts: 2666
+    ipmi: true
     cpus:
       - model: Intel Xeon E-2236
         cores: 6
         threads: 12
-    ram:
-      size: 64gb
-      mts: 2666
     drives:
       - type: hdd
-        size: 8tb
+        size: 8192
       - type: hdd
-        size: 8tb
+        size: 8192
       - type: hdd
-        size: 8tb
+        size: 8192
       - type: hdd
-        size: 8tb
-    nics:
+        size: 8192
+    ports:
       - type: rj45
-        speed: 1gb
-        ports: 1
+        speed: 1
+        count: 1
       - type: sfp+
-        speed: 10gb
-        ports: 1
-    ipmi: true
-
-  # ------------------------
-  # Network
-  # ------------------------
+        speed: 10
+        count: 1
+    name: truenas-storage
+    tags:
+      - prod
+    labels:
+      install-date: 2022-08-20
+      rack-unit: U12
   - kind: Firewall
-    name: pfsense-fw
     model: Netgate-6100
+    managed: true
+    poe: false
     ports:
       - type: rj45
-        speed: 1gb
+        speed: 1
         count: 4
       - type: sfp+
-        speed: 10gb
+        speed: 10
         count: 2
-    managed: true
-    poe: false
-
+    name: pfsense-fw
+    tags:
+      - infra
+    labels:
+      install-date: 2022-03-10
   - kind: Router
-    name: core-router
     model: Ubiquiti-ER-4
+    managed: true
+    poe: false
     ports:
       - type: rj45
-        speed: 1gb
+        speed: 1
         count: 4
       - type: sfp
-        speed: 10gb
+        speed: 10
         count: 1
-    managed: true
-    poe: false
-
+    name: core-router
+    tags:
+      - infra
+    labels:
+      install-date: 2022-03-01
   - kind: Switch
-    name: core-switch
     model: UniFi-USW-Enterprise-24
+    managed: true
+    poe: true
     ports:
       - type: rj45
-        speed: 1gb
+        speed: 1
         count: 12
       - type: rj45
-        speed: 2.5gb
+        speed: 2.5
         count: 8
       - type: sfp+
-        speed: 10gb
+        speed: 10
         count: 4
-    managed: true
-    poe: true
-
+    name: core-switch
+    tags:
+      - infra
+    labels:
+      rack-unit: U15
   - kind: Switch
-    name: access-switch
     model: UniFi-USW-16-PoE
+    managed: true
+    poe: true
     ports:
       - type: rj45
-        speed: 1gb
+        speed: 1
         count: 16
       - type: sfp
-        speed: 1gb
+        speed: 1
         count: 2
-    managed: true
-    poe: true
-
+    name: access-switch
+    tags:
+      - infra
+    labels:
+      rack-unit: U16
   - kind: AccessPoint
-    name: lounge-ap
     model: UniFi-U6-Pro
-    speed: 2.5gb
-
-  # ------------------------
-  # Power
-  # ------------------------
-  - kind: Ups
-    name: rack-ups
-    model: APC-SmartUPS-2200
-    va: 2200
-
-  # ------------------------
-  # Desktops
-  # ------------------------
-  - kind: Desktop
-    name: workstation-linux
-    cpus:
-      - model: AMD Ryzen 9 5900X
-        cores: 12
-        threads: 24
-    ram:
-      size: 64gb
-      mts: 3600
-    drives:
-      - type: ssd
-        size: 1tb
-      - type: ssd
-        size: 2tb
-    nics:
-      - type: rj45
-        speed: 1gb
-        ports: 1
-    gpus:
-      - model: NVIDIA RTX 3080
-        vram: 10gb
-
-  - kind: Desktop
-    name: gaming-pc
-    cpus:
-      - model: Intel Core i7-12700K
-        cores: 12
-        threads: 20
-    ram:
-      size: 32gb
-      mts: 3200
-    drives:
-      - type: ssd
-        size: 1tb
-    nics:
+    speed: 2.5
+    ports:
       - type: rj45
-        speed: 1gb
-        ports: 1
-    gpus:
-      - model: NVIDIA RTX 3070
-        vram: 8gb
-
-  # ------------------------
-  # Laptop
-  # ------------------------
-  - kind: Laptop
-    name: dev-laptop
-    cpus:
-      - model: Intel Core i7-1260P
-        cores: 12
-        threads: 16
-    ram:
-      size: 32gb
-      mts: 5200
-    drives:
-      - type: ssd
-        size: 1tb
-  # --------------------------------------------------
-  # Smart Home
-  # --------------------------------------------------
+        speed: 2.5
+        count: 1
+    name: lounge-ap
+    labels:
+      install-date: 2023-06-05
+      service-at: lounge
+  - kind: System
+    type: Hypervisor
+    os: proxmox
+    cores: 16
+    ram: 128
+    ip: 10.0.20.10
+    name: proxmox-cluster-node01
+    runsOn:
+      - proxmox-node01
+  - kind: System
+    type: Hypervisor
+    os: proxmox
+    cores: 10
+    ram: 96
+    ip: 10.0.20.11
+    name: proxmox-cluster-node02
+    runsOn:
+      - proxmox-node02
+  - kind: System
+    type: VM
+    os: hassos
+    cores: 2
+    ram: 4
+    ip: 192.168.0.10
+    name: vm-home-assistant
+    runsOn:
+      - proxmox-cluster-node01
+  - kind: System
+    type: VM
+    os: ubuntu-22.04
+    cores: 4
+    ram: 8
+    ip: 192.168.0.20
+    name: vm-media-server
+    runsOn:
+      - proxmox-cluster-node02
+  - kind: System
+    type: VM
+    os: debian-12
+    cores: 2
+    ram: 4
+    ip: 192.168.0.40
+    name: vm-monitoring
+    runsOn:
+      - proxmox-cluster-node01
+  - kind: System
+    type: container
+    os: alpine
+    cores: 1
+    ram: 1
+    ip: 192.168.0.41
+    name: ctr-grafana
+    runsOn:
+      - vm-monitoring
   - kind: Service
-    name: home-assistant
     network:
-      ip: 192.168.0.10
       port: 8123
       protocol: TCP
       url: http://homeassistant.lan:8123
-    runsOn: vm-home-assistant
-
-  # --------------------------------------------------
-  # Media & Photos
-  # --------------------------------------------------
+    name: home-assistant
+    labels:
+      service-at: home
+    runsOn:
+      - vm-home-assistant
   - kind: Service
-    name: plex
     network:
-      ip: 192.168.0.20
       port: 32400
       protocol: TCP
       url: http://plex.lan:32400
-    runsOn: vm-media-server
-
+    name: plex
+    tags:
+      - media
+    runsOn:
+      - vm-media-server
   - kind: Service
-    name: jellyfin
     network:
-      ip: 192.168.0.21
       port: 8096
       protocol: TCP
       url: http://jellyfin.lan:8096
-    runsOn: vm-media-server
-
+    name: jellyfin
+    tags:
+      - media
+    runsOn:
+      - vm-media-server
   - kind: Service
-    name: immich
     network:
-      ip: 192.168.0.22
       port: 8080
       protocol: TCP
       url: http://immich.lan:8080
-    runsOn: vm-media-server
-
-  # --------------------------------------------------
-  # Storage & Backup
-  # --------------------------------------------------
-  - kind: Service
-    name: truenas-webui
-    network:
-      ip: 192.168.0.30
-      port: 443
-      protocol: TCP
-      url: https://truenas.lan
-    runsOn: truenas-core-os
-
-  - kind: Service
-    name: minio
-    network:
-      ip: 192.168.0.31
-      port: 9000
-      protocol: TCP
-      url: http://minio.lan:9000
-    runsOn: vm-media-server
-
-  # --------------------------------------------------
-  # Monitoring & Ops
-  # --------------------------------------------------
-  - kind: Service
-    name: prometheus
-    network:
-      ip: 192.168.0.40
-      port: 9090
-      protocol: TCP
-      url: http://prometheus.lan:9090
-    runsOn: vm-monitoring
-
+    name: immich
+    tags:
+      - media
+    runsOn:
+      - vm-media-server
   - kind: Service
-    name: grafana
     network:
-      ip: 192.168.0.41
       port: 3000
       protocol: TCP
       url: http://grafana.lan:3000
-    runsOn: vm-monitoring
-
-  - kind: Service
-    name: alertmanager
-    network:
-      ip: 192.168.0.42
-      port: 9093
-      protocol: TCP
-      url: http://alertmanager.lan:9093
-    runsOn: vm-monitoring
-
-  # --------------------------------------------------
-  # Dev & Internal Tools
-  # --------------------------------------------------
-  - kind: Service
-    name: gitea
-    network:
-      ip: 192.168.0.50
-      port: 3001
-      protocol: TCP
-      url: http://git.lan:3001
-    runsOn: vm-monitoring
-
-  - kind: Service
-    name: docker-registry
-    network:
-      ip: 192.168.0.51
-      port: 5000
-      protocol: TCP
-      url: http://registry.lan:5000
-    runsOn: vm-monitoring
-
-  - kind: Service
-    name: portainer
-    network:
-      ip: 192.168.0.52
-      port: 9000
-      protocol: TCP
-      url: http://portainer.lan:9000
-    runsOn: vm-monitoring
-
-  # --------------------------------------------------
-  # Network Services
-  # --------------------------------------------------
-  - kind: Service
-    name: pihole
-    network:
-      ip: 192.168.0.53
-      port: 80
-      protocol: TCP
-      url: http://pihole.lan
-    runsOn: vm-monitoring
-
-  - kind: Service
-    name: firewall-webui
-    network:
-      ip: 192.168.0.1
-      port: 443
-      protocol: TCP
-      url: https://firewall.lan
-    runsOn: firewall-os
-
+    name: grafana
+    tags:
+      - containers
+    runsOn:
+      - ctr-grafana
   - kind: Service
-    name: router-webui
     network:
-      ip: 192.168.0.254
-      port: 443
+      port: 9090
       protocol: TCP
-      url: https://router.lan
-    runsOn: router-os
-  # --------------------------------------------------
-  # Hypervisors (Bare Metal)
-  # --------------------------------------------------
-  - kind: System
-    type: Hypervisor
-    name: proxmox-cluster-node01
-    os: proxmox
-    cores: 16
-    ram: 128gb
-    drives:
-      - size: 1tb
-      - size: 1tb
-    runsOn: proxmox-node01
-
-  - kind: System
-    type: Hypervisor
-    name: proxmox-cluster-node02
-    os: proxmox
-    cores: 10
-    ram: 96gb
-    drives:
-      - size: 1tb
-      - size: 4tb
-    runsOn: proxmox-node02
-
-  # --------------------------------------------------
-  # Storage OS (Bare Metal)
-  # --------------------------------------------------
-  - kind: System
-    type: Baremetal
-    name: truenas-core-os
-    os: truenas
-    cores: 6
-    ram: 64gb
-    drives:
-      - size: 8tb
-      - size: 8tb
-      - size: 8tb
-      - size: 8tb
-    runsOn: truenas-storage
-
-  # --------------------------------------------------
-  # IPMI / BMC Management
-  # --------------------------------------------------
-  - kind: System
-    type: Baremetal
-    name: ipmi-proxmox-node01
-    os: idrac
-    cores: 1
-    ram: 1gb
-    runsOn: proxmox-node01
-
-  - kind: System
-    type: Baremetal
-    name: ipmi-proxmox-node02
-    os: ipmi
-    cores: 1
-    ram: 1gb
-    runsOn: proxmox-node02
-
-  - kind: System
-    type: Baremetal
-    name: ipmi-truenas-storage
-    os: ipmi
-    cores: 1
-    ram: 1gb
-    runsOn: truenas-storage
-
-  # --------------------------------------------------
-  # Core Network Systems
-  # --------------------------------------------------
-  - kind: System
-    type: Baremetal
-    name: firewall-os
-    os: pfsense
-    cores: 4
-    ram: 8gb
-    drives:
-      - size: 32gb
-    runsOn: pfsense-fw
-
-  - kind: System
-    type: Baremetal
-    name: router-os
-    os: edgeos
-    cores: 4
-    ram: 4gb
-    drives:
-      - size: 4gb
-    runsOn: core-router
-
-  - kind: System
-    type: Baremetal
-    name: unifi-core-switch-os
-    os: unifi-os
-    cores: 2
-    ram: 2gb
-    drives:
-      - size: 8gb
-    runsOn: core-switch
-
-  - kind: System
-    type: Baremetal
-    name: unifi-access-switch-os
-    os: unifi-os
-    cores: 2
-    ram: 2gb
-    drives:
-      - size: 8gb
-    runsOn: access-switch
-
-  - kind: System
-    type: Baremetal
-    name: unifi-lounge-ap-os
-    os: unifi-firmware
-    cores: 2
-    ram: 1gb
-    drives:
-      - size: 4gb
-    runsOn: lounge-ap
-
-  # --------------------------------------------------
-  # Virtual Machines
-  # --------------------------------------------------
-  - kind: System
-    type: VM
-    name: vm-home-assistant
-    os: hassos
-    cores: 2
-    ram: 4gb
-    drives:
-      - size: 64gb
-    runsOn: proxmox-node01
-
-  - kind: System
-    type: VM
-    name: vm-media-server
-    os: ubuntu-22.04
-    cores: 4
-    ram: 8gb
-    drives:
-      - size: 500gb
-    runsOn: proxmox-node02
-
-  - kind: System
-    type: VM
-    name: vm-monitoring
-    os: debian-12
-    cores: 2
-    ram: 4gb
-    drives:
-      - size: 64gb
-    runsOn: proxmox-node01
+      url: http://prometheus.lan:9090
+    name: prometheus
+    runsOn:
+      - vm-monitoring
+connections:
+  - a:
+      resource: core-router
+      portGroup: 0
+      portIndex: 0
+    b:
+      resource: pfsense-fw
+      portGroup: 0
+      portIndex: 0
+    label: wan-link
+  - a:
+      resource: pfsense-fw
+      portGroup: 1
+      portIndex: 0
+    b:
+      resource: core-switch
+      portGroup: 2
+      portIndex: 0
+    label: firewall-lan
+  - a:
+      resource: core-switch
+      portGroup: 2
+      portIndex: 1
+    b:
+      resource: access-switch
+      portGroup: 1
+      portIndex: 0
+    label: switch-uplink
+  - a:
+      resource: proxmox-node01
+      portGroup: 1
+      portIndex: 0
+    b:
+      resource: core-switch
+      portGroup: 2
+      portIndex: 2
+    label: node01-10g
+  - a:
+      resource: proxmox-node02
+      portGroup: 1
+      portIndex: 0
+    b:
+      resource: core-switch
+      portGroup: 2
+      portIndex: 3
+    label: node02-10g
+  - a:
+      resource: truenas-storage
+      portGroup: 1
+      portIndex: 0
+    b:
+      resource: core-switch
+      portGroup: 0
+      portIndex: 4
+    label: storage-link
+  - a:
+      resource: lounge-ap
+      portGroup: 0
+      portIndex: 0
+    b:
+      resource: access-switch
+      portGroup: 0
+      portIndex: 1
+    label: wifi-uplink

+ 155 - 271
RackPeek.Web.Viewer/wwwroot/schemas/v3/schema.v3.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://timmoth.github.io/RackPeek/schemas/v2/schema.v2.json",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v3/schema.v3.json",
   "title": "RackPeek Infrastructure Specification",
   "type": "object",
   "additionalProperties": false,
@@ -11,13 +11,19 @@
   "properties": {
     "version": {
       "type": "integer",
-      "const": 2
+      "const": 3
     },
     "resources": {
       "type": "array",
       "items": {
         "$ref": "#/$defs/resource"
       }
+    },
+    "connections": {
+      "type": ["array", "null"],
+      "items": {
+        "$ref": "#/$defs/connection"
+      }
     }
   },
   "$defs": {
@@ -27,16 +33,15 @@
         "type": "string"
       }
     },
+
     "runsOn": {
-      "type": [
-        "array",
-        "null"
-      ],
+      "type": ["array", "null"],
       "items": {
         "type": "string",
         "minLength": 1
       }
     },
+
     "resourceBase": {
       "type": "object",
       "required": [
@@ -69,55 +74,76 @@
           ]
         },
         "runsOn": {
-          "type": [
-            "array",
-            "null"
-          ],
-          "items": {
-            "type": "string"
-          }
+          "$ref": "#/$defs/runsOn"
         }
       }
     },
+
     "resource": {
       "oneOf": [
-        {
-          "$ref": "#/$defs/server"
-        },
-        {
-          "$ref": "#/$defs/firewall"
-        },
-        {
-          "$ref": "#/$defs/router"
-        },
-        {
-          "$ref": "#/$defs/switch"
-        },
-        {
-          "$ref": "#/$defs/accessPoint"
+        { "$ref": "#/$defs/server" },
+        { "$ref": "#/$defs/firewall" },
+        { "$ref": "#/$defs/router" },
+        { "$ref": "#/$defs/switch" },
+        { "$ref": "#/$defs/accessPoint" },
+        { "$ref": "#/$defs/ups" },
+        { "$ref": "#/$defs/desktop" },
+        { "$ref": "#/$defs/laptop" },
+        { "$ref": "#/$defs/service" },
+        { "$ref": "#/$defs/system" }
+      ]
+    },
+
+    "portReference": {
+      "type": "object",
+      "required": [
+        "resource",
+        "portGroup",
+        "portIndex"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "resource": {
+          "type": "string",
+          "minLength": 1
         },
-        {
-          "$ref": "#/$defs/ups"
+        "portGroup": {
+          "type": "integer",
+          "minimum": 0
         },
-        {
-          "$ref": "#/$defs/desktop"
+        "portIndex": {
+          "type": "integer",
+          "minimum": 0
+        }
+      }
+    },
+
+    "connection": {
+      "type": "object",
+      "required": [
+        "a",
+        "b"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "a": {
+          "$ref": "#/$defs/portReference"
         },
-        {
-          "$ref": "#/$defs/laptop"
+        "b": {
+          "$ref": "#/$defs/portReference"
         },
-        {
-          "$ref": "#/$defs/service"
+        "label": {
+          "type": ["string", "null"]
         },
-        {
-          "$ref": "#/$defs/system"
+        "notes": {
+          "type": ["string", "null"]
         }
-      ]
+      }
     },
+
     "ram": {
       "type": "object",
-      "required": [
-        "size"
-      ],
+      "required": ["size"],
       "additionalProperties": false,
       "properties": {
         "size": {
@@ -130,28 +156,20 @@
         }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
-        "model": {
-          "type": "string"
-        },
-        "cores": {
-          "type": "integer",
-          "minimum": 1
-        },
-        "threads": {
-          "type": "integer",
-          "minimum": 1
-        }
+        "model": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
-      "required": [
-        "size"
-      ],
+      "required": ["size"],
       "additionalProperties": false,
       "properties": {
         "type": {
@@ -173,21 +191,19 @@
         }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
-        "model": {
-          "type": "string"
-        },
-        "vram": {
-          "type": "number",
-          "minimum": 0
-        }
+        "model": { "type": "string" },
+        "vram": { "type": "number", "minimum": 0 }
       }
     },
-    "nic": {
+
+    "port": {
       "type": "object",
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": {
@@ -212,41 +228,16 @@
           "type": "number",
           "minimum": 0
         },
-        "ports": {
-          "type": "integer",
-          "minimum": 1
-        }
-      }
-    },
-    "port": {
-      "type": "object",
-      "required": [
-        "type",
-        "speed",
-        "count"
-      ],
-      "additionalProperties": false,
-      "properties": {
-        "type": {
-          "type": "string"
-        },
-        "speed": {
-          "type": "number",
-          "minimum": 0
-        },
         "count": {
           "type": "integer",
           "minimum": 1
         }
       }
     },
+
     "network": {
       "type": "object",
-      "required": [
-        "ip",
-        "port",
-        "protocol"
-      ],
+      "required": ["ip", "port", "protocol"],
       "additionalProperties": false,
       "properties": {
         "ip": {
@@ -260,10 +251,7 @@
         },
         "protocol": {
           "type": "string",
-          "enum": [
-            "TCP",
-            "UDP"
-          ]
+          "enum": ["TCP", "UDP"]
         },
         "url": {
           "type": "string",
@@ -271,301 +259,205 @@
         }
       }
     },
+
     "server": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Server"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
-            "ipmi": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Server" },
+            "ram": { "$ref": "#/$defs/ram" },
+            "ipmi": { "type": "boolean" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             },
             "gpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/gpu"
-              }
+              "items": { "$ref": "#/$defs/gpu" }
             },
-            "nics": {
+            "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/nic"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "desktop": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Desktop"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
+            "kind": { "const": "Desktop" },
+            "ram": { "$ref": "#/$defs/ram" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             },
             "gpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/gpu"
-              }
+              "items": { "$ref": "#/$defs/gpu" }
             },
-            "nics": {
+            "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/nic"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "laptop": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Laptop"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
+            "kind": { "const": "Laptop" },
+            "ram": { "$ref": "#/$defs/ram" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "firewall": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Firewall"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Firewall" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "router": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Router"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Router" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "switch": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Switch"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Switch" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "accessPoint": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "AccessPoint"
-            },
-            "model": {
-              "type": "string"
-            },
-            "speed": {
-              "type": "number",
-              "minimum": 0
+            "kind": { "const": "AccessPoint" },
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 },
+            "ports": {
+              "type": "array",
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "ups": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Ups"
-            },
-            "model": {
-              "type": "string"
-            },
-            "va": {
-              "type": "integer",
-              "minimum": 1
-            }
+            "kind": { "const": "Ups" },
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "service": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "network"
-          ],
+          "required": ["network"],
           "properties": {
-            "kind": {
-              "const": "Service"
-            },
-            "network": {
-              "$ref": "#/$defs/network"
-            }
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "system": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "required": [
@@ -575,9 +467,7 @@
             "ram"
           ],
           "properties": {
-            "kind": {
-              "const": "System"
-            },
+            "kind": { "const": "System" },
             "type": {
               "type": "string",
               "enum": [
@@ -595,12 +485,8 @@
                 "other"
               ]
             },
-            "ip": {
-              "type": "string"
-            },
-            "os": {
-              "type": "string"
-            },
+            "ip": { "type": "string" },
+            "os": { "type": "string" },
             "cores": {
               "type": "integer",
               "minimum": 1
@@ -611,9 +497,7 @@
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             }
           }
         }

+ 150 - 249
RackPeek.Web/wwwroot/schemas/v3/schema.v3.json

@@ -18,6 +18,12 @@
       "items": {
         "$ref": "#/$defs/resource"
       }
+    },
+    "connections": {
+      "type": ["array", "null"],
+      "items": {
+        "$ref": "#/$defs/connection"
+      }
     }
   },
   "$defs": {
@@ -27,16 +33,15 @@
         "type": "string"
       }
     },
+
     "runsOn": {
-      "type": [
-        "array",
-        "null"
-      ],
+      "type": ["array", "null"],
       "items": {
         "type": "string",
         "minLength": 1
       }
     },
+
     "resourceBase": {
       "type": "object",
       "required": [
@@ -69,55 +74,76 @@
           ]
         },
         "runsOn": {
-          "type": [
-            "array",
-            "null"
-          ],
-          "items": {
-            "type": "string"
-          }
+          "$ref": "#/$defs/runsOn"
         }
       }
     },
+
     "resource": {
       "oneOf": [
-        {
-          "$ref": "#/$defs/server"
-        },
-        {
-          "$ref": "#/$defs/firewall"
-        },
-        {
-          "$ref": "#/$defs/router"
-        },
-        {
-          "$ref": "#/$defs/switch"
-        },
-        {
-          "$ref": "#/$defs/accessPoint"
+        { "$ref": "#/$defs/server" },
+        { "$ref": "#/$defs/firewall" },
+        { "$ref": "#/$defs/router" },
+        { "$ref": "#/$defs/switch" },
+        { "$ref": "#/$defs/accessPoint" },
+        { "$ref": "#/$defs/ups" },
+        { "$ref": "#/$defs/desktop" },
+        { "$ref": "#/$defs/laptop" },
+        { "$ref": "#/$defs/service" },
+        { "$ref": "#/$defs/system" }
+      ]
+    },
+
+    "portReference": {
+      "type": "object",
+      "required": [
+        "resource",
+        "portGroup",
+        "portIndex"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "resource": {
+          "type": "string",
+          "minLength": 1
         },
-        {
-          "$ref": "#/$defs/ups"
+        "portGroup": {
+          "type": "integer",
+          "minimum": 0
         },
-        {
-          "$ref": "#/$defs/desktop"
+        "portIndex": {
+          "type": "integer",
+          "minimum": 0
+        }
+      }
+    },
+
+    "connection": {
+      "type": "object",
+      "required": [
+        "a",
+        "b"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "a": {
+          "$ref": "#/$defs/portReference"
         },
-        {
-          "$ref": "#/$defs/laptop"
+        "b": {
+          "$ref": "#/$defs/portReference"
         },
-        {
-          "$ref": "#/$defs/service"
+        "label": {
+          "type": ["string", "null"]
         },
-        {
-          "$ref": "#/$defs/system"
+        "notes": {
+          "type": ["string", "null"]
         }
-      ]
+      }
     },
+
     "ram": {
       "type": "object",
-      "required": [
-        "size"
-      ],
+      "required": ["size"],
       "additionalProperties": false,
       "properties": {
         "size": {
@@ -130,28 +156,20 @@
         }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
-        "model": {
-          "type": "string"
-        },
-        "cores": {
-          "type": "integer",
-          "minimum": 1
-        },
-        "threads": {
-          "type": "integer",
-          "minimum": 1
-        }
+        "model": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
-      "required": [
-        "size"
-      ],
+      "required": ["size"],
       "additionalProperties": false,
       "properties": {
         "type": {
@@ -173,26 +191,19 @@
         }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
-        "model": {
-          "type": "string"
-        },
-        "vram": {
-          "type": "number",
-          "minimum": 0
-        }
+        "model": { "type": "string" },
+        "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "port": {
       "type": "object",
-      "required": [
-        "type",
-        "speed",
-        "count"
-      ],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": {
@@ -223,13 +234,10 @@
         }
       }
     },
+
     "network": {
       "type": "object",
-      "required": [
-        "ip",
-        "port",
-        "protocol"
-      ],
+      "required": ["ip", "port", "protocol"],
       "additionalProperties": false,
       "properties": {
         "ip": {
@@ -243,10 +251,7 @@
         },
         "protocol": {
           "type": "string",
-          "enum": [
-            "TCP",
-            "UDP"
-          ]
+          "enum": ["TCP", "UDP"]
         },
         "url": {
           "type": "string",
@@ -254,301 +259,205 @@
         }
       }
     },
+
     "server": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Server"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
-            "ipmi": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Server" },
+            "ram": { "$ref": "#/$defs/ram" },
+            "ipmi": { "type": "boolean" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             },
             "gpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/gpu"
-              }
+              "items": { "$ref": "#/$defs/gpu" }
             },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "desktop": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Desktop"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
+            "kind": { "const": "Desktop" },
+            "ram": { "$ref": "#/$defs/ram" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             },
             "gpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/gpu"
-              }
+              "items": { "$ref": "#/$defs/gpu" }
             },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "laptop": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Laptop"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
+            "kind": { "const": "Laptop" },
+            "ram": { "$ref": "#/$defs/ram" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "firewall": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Firewall"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Firewall" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "router": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Router"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Router" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "switch": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Switch"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Switch" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "accessPoint": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "AccessPoint"
-            },
-            "model": {
-              "type": "string"
-            },
-            "speed": {
-              "type": "number",
-              "minimum": 0
+            "kind": { "const": "AccessPoint" },
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 },
+            "ports": {
+              "type": "array",
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "ups": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Ups"
-            },
-            "model": {
-              "type": "string"
-            },
-            "va": {
-              "type": "integer",
-              "minimum": 1
-            }
+            "kind": { "const": "Ups" },
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "service": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "network"
-          ],
+          "required": ["network"],
           "properties": {
-            "kind": {
-              "const": "Service"
-            },
-            "network": {
-              "$ref": "#/$defs/network"
-            }
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "system": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "required": [
@@ -558,9 +467,7 @@
             "ram"
           ],
           "properties": {
-            "kind": {
-              "const": "System"
-            },
+            "kind": { "const": "System" },
             "type": {
               "type": "string",
               "enum": [
@@ -578,12 +485,8 @@
                 "other"
               ]
             },
-            "ip": {
-              "type": "string"
-            },
-            "os": {
-              "type": "string"
-            },
+            "ip": { "type": "string" },
+            "os": { "type": "string" },
             "cores": {
               "type": "integer",
               "minimum": 1
@@ -594,9 +497,7 @@
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             }
           }
         }

+ 7 - 0
Shared.Rcl/AccessPoints/AccessPointCardComponent.razor

@@ -1,4 +1,5 @@
 @using RackPeek.Domain.Resources.AccessPoints
+@using Shared.Rcl.Hardware
 @inject UpdateAccessPointUseCase UpdateUseCase
 @inject IDeleteResourceUseCase<AccessPoint> DeleteUseCase
 @inject IRenameResourceUseCase<AccessPoint> RenameUseCase
@@ -103,6 +104,12 @@
                 </div>
             }
         </div>
+        
+        <!-- NICs -->
+        <PortGroupEditor T="AccessPoint"
+                         Resource="AccessPoint"
+                         OnResourceChanged="r => AccessPoint = r" 
+                         TestIdPrefix="accesspoint-ports"/>
 
         <ResourceTagEditor Resource="AccessPoint"
                            TestIdPrefix="accesspoint"/>

+ 5 - 1
Tests/TestConfigs/v3/05-accesspoint.yaml

@@ -8,4 +8,8 @@ resources:
     speed: 2.5
     runsOn:
       - rack-a1
-      - rack-a2
+      - rack-a2
+    ports:
+      - type: rj45
+        speed: 1
+        count: 1

+ 68 - 0
Tests/TestConfigs/v3/11-demo-config.yaml

@@ -452,3 +452,71 @@ resources:
     name: vm-monitoring
     runsOn:
       - proxmox-node01
+  - kind: System
+    type: VM
+    os: test
+    cores: 1
+    ram: 1
+    name: test-system
+    runsOn:
+      - proxmox-node01
+  - kind: Service
+    name: test-service
+    network:
+      ip: 192.168.0.250
+      port: 8080
+      protocol: TCP
+    runsOn:
+      - test-system
+  - kind: Service
+    name: test-service-no-host
+    network:
+      ip: 192.168.0.251
+      port: 8080
+      protocol: TCP
+  - kind: Service
+    name: test-ha-service
+    network:
+      ip: 192.168.0.252
+      port: 8080
+      protocol: TCP
+    runsOn:
+      - test-system
+      - proxmox-cluster-node01
+  - kind: AccessPoint
+    name: lounge-ap
+    model: UniFi-U6-Pro
+    speed: 2.5
+    ports:
+      - type: rj45
+        speed: 2.5
+        count: 1
+connections:
+  - a:
+      resource: core-router
+      portGroup: 0
+      portIndex: 0
+    b:
+      resource: pfsense-fw
+      portGroup: 0
+      portIndex: 0
+
+  - a:
+      resource: pfsense-fw
+      portGroup: 1
+      portIndex: 0
+    b:
+      resource: core-switch
+      portGroup: 2
+      portIndex: 0
+
+  - a:
+      resource: core-switch
+      portGroup: 2
+      portIndex: 1
+    b:
+      resource: access-switch
+      portGroup: 1
+      portIndex: 0
+    label: router-firewall
+    notes: internal uplink

+ 150 - 249
Tests/schemas/schema.v3.json

@@ -18,6 +18,12 @@
       "items": {
         "$ref": "#/$defs/resource"
       }
+    },
+    "connections": {
+      "type": ["array", "null"],
+      "items": {
+        "$ref": "#/$defs/connection"
+      }
     }
   },
   "$defs": {
@@ -27,16 +33,15 @@
         "type": "string"
       }
     },
+
     "runsOn": {
-      "type": [
-        "array",
-        "null"
-      ],
+      "type": ["array", "null"],
       "items": {
         "type": "string",
         "minLength": 1
       }
     },
+
     "resourceBase": {
       "type": "object",
       "required": [
@@ -69,55 +74,76 @@
           ]
         },
         "runsOn": {
-          "type": [
-            "array",
-            "null"
-          ],
-          "items": {
-            "type": "string"
-          }
+          "$ref": "#/$defs/runsOn"
         }
       }
     },
+
     "resource": {
       "oneOf": [
-        {
-          "$ref": "#/$defs/server"
-        },
-        {
-          "$ref": "#/$defs/firewall"
-        },
-        {
-          "$ref": "#/$defs/router"
-        },
-        {
-          "$ref": "#/$defs/switch"
-        },
-        {
-          "$ref": "#/$defs/accessPoint"
+        { "$ref": "#/$defs/server" },
+        { "$ref": "#/$defs/firewall" },
+        { "$ref": "#/$defs/router" },
+        { "$ref": "#/$defs/switch" },
+        { "$ref": "#/$defs/accessPoint" },
+        { "$ref": "#/$defs/ups" },
+        { "$ref": "#/$defs/desktop" },
+        { "$ref": "#/$defs/laptop" },
+        { "$ref": "#/$defs/service" },
+        { "$ref": "#/$defs/system" }
+      ]
+    },
+
+    "portReference": {
+      "type": "object",
+      "required": [
+        "resource",
+        "portGroup",
+        "portIndex"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "resource": {
+          "type": "string",
+          "minLength": 1
         },
-        {
-          "$ref": "#/$defs/ups"
+        "portGroup": {
+          "type": "integer",
+          "minimum": 0
         },
-        {
-          "$ref": "#/$defs/desktop"
+        "portIndex": {
+          "type": "integer",
+          "minimum": 0
+        }
+      }
+    },
+
+    "connection": {
+      "type": "object",
+      "required": [
+        "a",
+        "b"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "a": {
+          "$ref": "#/$defs/portReference"
         },
-        {
-          "$ref": "#/$defs/laptop"
+        "b": {
+          "$ref": "#/$defs/portReference"
         },
-        {
-          "$ref": "#/$defs/service"
+        "label": {
+          "type": ["string", "null"]
         },
-        {
-          "$ref": "#/$defs/system"
+        "notes": {
+          "type": ["string", "null"]
         }
-      ]
+      }
     },
+
     "ram": {
       "type": "object",
-      "required": [
-        "size"
-      ],
+      "required": ["size"],
       "additionalProperties": false,
       "properties": {
         "size": {
@@ -130,28 +156,20 @@
         }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
-        "model": {
-          "type": "string"
-        },
-        "cores": {
-          "type": "integer",
-          "minimum": 1
-        },
-        "threads": {
-          "type": "integer",
-          "minimum": 1
-        }
+        "model": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
-      "required": [
-        "size"
-      ],
+      "required": ["size"],
       "additionalProperties": false,
       "properties": {
         "type": {
@@ -173,26 +191,19 @@
         }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
-        "model": {
-          "type": "string"
-        },
-        "vram": {
-          "type": "number",
-          "minimum": 0
-        }
+        "model": { "type": "string" },
+        "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "port": {
       "type": "object",
-      "required": [
-        "type",
-        "speed",
-        "count"
-      ],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": {
@@ -223,13 +234,10 @@
         }
       }
     },
+
     "network": {
       "type": "object",
-      "required": [
-        "ip",
-        "port",
-        "protocol"
-      ],
+      "required": ["ip", "port", "protocol"],
       "additionalProperties": false,
       "properties": {
         "ip": {
@@ -243,10 +251,7 @@
         },
         "protocol": {
           "type": "string",
-          "enum": [
-            "TCP",
-            "UDP"
-          ]
+          "enum": ["TCP", "UDP"]
         },
         "url": {
           "type": "string",
@@ -254,301 +259,205 @@
         }
       }
     },
+
     "server": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Server"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
-            "ipmi": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Server" },
+            "ram": { "$ref": "#/$defs/ram" },
+            "ipmi": { "type": "boolean" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             },
             "gpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/gpu"
-              }
+              "items": { "$ref": "#/$defs/gpu" }
             },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "desktop": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Desktop"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
+            "kind": { "const": "Desktop" },
+            "ram": { "$ref": "#/$defs/ram" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             },
             "gpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/gpu"
-              }
+              "items": { "$ref": "#/$defs/gpu" }
             },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "laptop": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Laptop"
-            },
-            "ram": {
-              "$ref": "#/$defs/ram"
-            },
+            "kind": { "const": "Laptop" },
+            "ram": { "$ref": "#/$defs/ram" },
             "cpus": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/cpu"
-              }
+              "items": { "$ref": "#/$defs/cpu" }
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "firewall": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Firewall"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Firewall" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "router": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Router"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Router" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "switch": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "ports"
-          ],
+          "required": ["ports"],
           "properties": {
-            "kind": {
-              "const": "Switch"
-            },
-            "model": {
-              "type": "string"
-            },
-            "managed": {
-              "type": "boolean"
-            },
-            "poe": {
-              "type": "boolean"
-            },
+            "kind": { "const": "Switch" },
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
             "ports": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/port"
-              }
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "accessPoint": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "AccessPoint"
-            },
-            "model": {
-              "type": "string"
-            },
-            "speed": {
-              "type": "number",
-              "minimum": 0
+            "kind": { "const": "AccessPoint" },
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 },
+            "ports": {
+              "type": "array",
+              "items": { "$ref": "#/$defs/port" }
             }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "ups": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "properties": {
-            "kind": {
-              "const": "Ups"
-            },
-            "model": {
-              "type": "string"
-            },
-            "va": {
-              "type": "integer",
-              "minimum": 1
-            }
+            "kind": { "const": "Ups" },
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "service": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
-          "required": [
-            "network"
-          ],
+          "required": ["network"],
           "properties": {
-            "kind": {
-              "const": "Service"
-            },
-            "network": {
-              "$ref": "#/$defs/network"
-            }
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
           }
         }
       ],
       "unevaluatedProperties": false
     },
+
     "system": {
       "allOf": [
-        {
-          "$ref": "#/$defs/resourceBase"
-        },
+        { "$ref": "#/$defs/resourceBase" },
         {
           "type": "object",
           "required": [
@@ -558,9 +467,7 @@
             "ram"
           ],
           "properties": {
-            "kind": {
-              "const": "System"
-            },
+            "kind": { "const": "System" },
             "type": {
               "type": "string",
               "enum": [
@@ -578,12 +485,8 @@
                 "other"
               ]
             },
-            "ip": {
-              "type": "string"
-            },
-            "os": {
-              "type": "string"
-            },
+            "ip": { "type": "string" },
+            "os": { "type": "string" },
             "cores": {
               "type": "integer",
               "minimum": 1
@@ -594,9 +497,7 @@
             },
             "drives": {
               "type": "array",
-              "items": {
-                "$ref": "#/$defs/drive"
-              }
+              "items": { "$ref": "#/$defs/drive" }
             }
           }
         }