@isothrum/editor 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +8 -0
- package/PUBLISHING.md +93 -0
- package/README.md +106 -2
- package/desktop-editor/editor.css +11 -0
- package/desktop-editor/index.html +14 -0
- package/desktop-editor/main.tsx +26 -0
- package/dist/desktop/editor.css +1 -0
- package/dist/desktop/editor.js +181 -0
- package/dist/desktop/fonts/geist-cyrillic-ext-wght-normal-RO72NYA2.woff2 +0 -0
- package/dist/desktop/fonts/geist-cyrillic-wght-normal-CK2XY27K.woff2 +0 -0
- package/dist/desktop/fonts/geist-latin-ext-wght-normal-XEQ56JUF.woff2 +0 -0
- package/dist/desktop/fonts/geist-latin-wght-normal-T72VVCUW.woff2 +0 -0
- package/dist/desktop/fonts/geist-mono-cyrillic-ext-wght-normal-QXKVIKFD.woff2 +0 -0
- package/dist/desktop/fonts/geist-mono-cyrillic-wght-normal-GOIAQAGU.woff2 +0 -0
- package/dist/desktop/fonts/geist-mono-latin-ext-wght-normal-HU3ZAWN6.woff2 +0 -0
- package/dist/desktop/fonts/geist-mono-latin-wght-normal-CJNQKXUG.woff2 +0 -0
- package/dist/desktop/fonts/geist-mono-symbols2-wght-normal-WZJJVLQO.woff2 +0 -0
- package/dist/desktop/fonts/geist-mono-vietnamese-wght-normal-KW5NM6YV.woff2 +0 -0
- package/dist/desktop/fonts/geist-vietnamese-wght-normal-Y2R7JZKV.woff2 +0 -0
- package/dist/desktop/fonts/shantell-sans-cyrillic-ext-wght-normal-ILPQWRJJ.woff2 +0 -0
- package/dist/desktop/fonts/shantell-sans-cyrillic-wght-normal-RKC6NDPM.woff2 +0 -0
- package/dist/desktop/fonts/shantell-sans-latin-ext-wght-normal-SNKHG3LM.woff2 +0 -0
- package/dist/desktop/fonts/shantell-sans-latin-wght-normal-7CHHGC3N.woff2 +0 -0
- package/dist/desktop/fonts/shantell-sans-vietnamese-wght-normal-IPFAQSBA.woff2 +0 -0
- package/dist/desktop/index.html +14 -0
- package/dist/desktop/styles.css +2 -0
- package/dist/desktop/wasm/isothrum_wasm.d.ts +60 -0
- package/dist/desktop/wasm/isothrum_wasm.js +312 -0
- package/dist/desktop/wasm/isothrum_wasm_bg.wasm +0 -0
- package/dist/index.js +7059 -0
- package/dist/index.js.map +7 -0
- package/dist/model.js +745 -0
- package/dist/model.js.map +7 -0
- package/dist/types/components/tool/canvas/cable-edge.d.ts +4 -0
- package/dist/types/components/tool/canvas/contexts.d.ts +12 -0
- package/dist/types/components/tool/canvas/device-icon.d.ts +17 -0
- package/dist/types/components/tool/canvas/inline-text.d.ts +15 -0
- package/dist/types/components/tool/canvas/lan-node.d.ts +8 -0
- package/dist/types/components/tool/canvas/machine-addresses.d.ts +14 -0
- package/dist/types/components/tool/canvas/machine-node.d.ts +4 -0
- package/dist/types/components/tool/canvas/note-node.d.ts +7 -0
- package/dist/types/components/tool/canvas/port-led.d.ts +10 -0
- package/dist/types/components/tool/canvas/router-node.d.ts +8 -0
- package/dist/types/components/tool/canvas/sketch.d.ts +50 -0
- package/dist/types/components/tool/canvas/status-dot.d.ts +5 -0
- package/dist/types/components/tool/canvas/switch-node.d.ts +8 -0
- package/dist/types/components/tool/canvas/use-measured-ports.d.ts +7 -0
- package/dist/types/components/tool/context-menus.d.ts +5 -0
- package/dist/types/components/tool/device-block.d.ts +12 -0
- package/dist/types/components/tool/editor-menu.d.ts +28 -0
- package/dist/types/components/tool/export/copy-button.d.ts +9 -0
- package/dist/types/components/tool/export/export-panel.d.ts +17 -0
- package/dist/types/components/tool/export/target-list.d.ts +15 -0
- package/dist/types/components/tool/floating-window.d.ts +13 -0
- package/dist/types/components/tool/flow-canvas.d.ts +6 -0
- package/dist/types/components/tool/hooks/use-cabling.d.ts +23 -0
- package/dist/types/components/tool/hooks/use-captures.d.ts +9 -0
- package/dist/types/components/tool/hooks/use-clipboard.d.ts +12 -0
- package/dist/types/components/tool/hooks/use-context-menus.d.ts +25 -0
- package/dist/types/components/tool/hooks/use-design.d.ts +10 -0
- package/dist/types/components/tool/hooks/use-desktop.d.ts +50 -0
- package/dist/types/components/tool/hooks/use-devices.d.ts +17 -0
- package/dist/types/components/tool/hooks/use-downloads.d.ts +9 -0
- package/dist/types/components/tool/hooks/use-editor.d.ts +609 -0
- package/dist/types/components/tool/hooks/use-engine.d.ts +9 -0
- package/dist/types/components/tool/hooks/use-history.d.ts +9 -0
- package/dist/types/components/tool/hooks/use-lab-actions.d.ts +25 -0
- package/dist/types/components/tool/hooks/use-lab-run.d.ts +25 -0
- package/dist/types/components/tool/hooks/use-lab.d.ts +18 -0
- package/dist/types/components/tool/hooks/use-lan-carry.d.ts +8 -0
- package/dist/types/components/tool/hooks/use-machine-actions.d.ts +26 -0
- package/dist/types/components/tool/hooks/use-placement.d.ts +27 -0
- package/dist/types/components/tool/hooks/use-project.d.ts +25 -0
- package/dist/types/components/tool/hooks/use-projects.d.ts +34 -0
- package/dist/types/components/tool/hooks/use-shared-link.d.ts +23 -0
- package/dist/types/components/tool/hooks/use-toast.d.ts +2 -0
- package/dist/types/components/tool/hooks/use-tools.d.ts +17 -0
- package/dist/types/components/tool/lab-canvas.d.ts +18 -0
- package/dist/types/components/tool/overlays.d.ts +5 -0
- package/dist/types/components/tool/projects-list.d.ts +9 -0
- package/dist/types/components/tool/text.d.ts +418 -0
- package/dist/types/components/tool/toolbar.d.ts +5 -0
- package/dist/types/components/tool/top-bars.d.ts +10 -0
- package/dist/types/components/tool/windows/conditions-fields.d.ts +6 -0
- package/dist/types/components/tool/windows/editor-windows.d.ts +5 -0
- package/dist/types/components/tool/windows/fields.d.ts +4 -0
- package/dist/types/components/tool/windows/lab-status.d.ts +6 -0
- package/dist/types/components/tool/windows/lab-windows.d.ts +13 -0
- package/dist/types/components/tool/windows/link-controls.d.ts +21 -0
- package/dist/types/components/tool/windows/machine-controls.d.ts +16 -0
- package/dist/types/components/tool/windows/machine-inspector.d.ts +11 -0
- package/dist/types/components/tool/windows/runs-fields.d.ts +18 -0
- package/dist/types/components/tool/windows/selected-windows.d.ts +21 -0
- package/dist/types/components/ui/button.d.ts +8 -0
- package/dist/types/components/ui/dropdown-menu.d.ts +21 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/lib/auth.d.ts +38 -0
- package/dist/types/lib/brand.d.ts +19 -0
- package/dist/types/lib/i18n.d.ts +19 -0
- package/dist/types/lib/tool/blocks.d.ts +35 -0
- package/dist/types/lib/tool/cables.d.ts +48 -0
- package/dist/types/lib/tool/clone.d.ts +12 -0
- package/dist/types/lib/tool/conditions.d.ts +31 -0
- package/dist/types/lib/tool/create.d.ts +22 -0
- package/dist/types/lib/tool/desktop.d.ts +63 -0
- package/dist/types/lib/tool/draft.d.ts +20 -0
- package/dist/types/lib/tool/engine.d.ts +33 -0
- package/dist/types/lib/tool/examples.d.ts +11 -0
- package/dist/types/lib/tool/export-targets.d.ts +21 -0
- package/dist/types/lib/tool/facing.d.ts +9 -0
- package/dist/types/lib/tool/geometry.d.ts +36 -0
- package/dist/types/lib/tool/gns3-cables.d.ts +17 -0
- package/dist/types/lib/tool/gns3-import.d.ts +15 -0
- package/dist/types/lib/tool/gns3-parts.d.ts +25 -0
- package/dist/types/lib/tool/issues.d.ts +12 -0
- package/dist/types/lib/tool/layout.d.ts +52 -0
- package/dist/types/lib/tool/live.d.ts +21 -0
- package/dist/types/lib/tool/logos.d.ts +7 -0
- package/dist/types/lib/tool/model.d.ts +173 -0
- package/dist/types/lib/tool/os.d.ts +20 -0
- package/dist/types/lib/tool/pins.d.ts +5 -0
- package/dist/types/lib/tool/placement.d.ts +21 -0
- package/dist/types/lib/tool/ports.d.ts +21 -0
- package/dist/types/lib/tool/presets.d.ts +21 -0
- package/dist/types/lib/tool/projects-api.d.ts +37 -0
- package/dist/types/lib/tool/router-ports.d.ts +22 -0
- package/dist/types/lib/tool/selection.d.ts +19 -0
- package/dist/types/lib/tool/storage.d.ts +25 -0
- package/dist/types/lib/tool/switch-edit.d.ts +17 -0
- package/dist/types/lib/tool/text/de.d.ts +2 -0
- package/dist/types/lib/tool/text/en.d.ts +429 -0
- package/dist/types/lib/tool/text/es.d.ts +2 -0
- package/dist/types/lib/tool/text/fr.d.ts +2 -0
- package/dist/types/lib/tool/text/index.d.ts +419 -0
- package/dist/types/lib/tool/topology.d.ts +2 -0
- package/dist/types/lib/tool/vlans.d.ts +24 -0
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/types/model.d.ts +13 -0
- package/package.json +88 -4
- package/scripts/build-desktop-editor.mjs +43 -0
- package/scripts/build-lib.mjs +38 -0
- package/src/components/tool/canvas/cable-edge.tsx +112 -0
- package/src/components/tool/canvas/contexts.ts +18 -0
- package/src/components/tool/canvas/device-icon.tsx +105 -0
- package/src/components/tool/canvas/inline-text.tsx +69 -0
- package/src/components/tool/canvas/lan-node.tsx +148 -0
- package/src/components/tool/canvas/machine-addresses.tsx +117 -0
- package/src/components/tool/canvas/machine-node.tsx +114 -0
- package/src/components/tool/canvas/note-node.tsx +109 -0
- package/src/components/tool/canvas/port-led.tsx +21 -0
- package/src/components/tool/canvas/router-node.tsx +114 -0
- package/src/components/tool/canvas/sketch.tsx +130 -0
- package/src/components/tool/canvas/status-dot.tsx +29 -0
- package/src/components/tool/canvas/switch-node.tsx +143 -0
- package/src/components/tool/canvas/use-measured-ports.ts +25 -0
- package/src/components/tool/context-menus.tsx +142 -0
- package/src/components/tool/device-block.tsx +107 -0
- package/src/components/tool/editor-menu.tsx +191 -0
- package/src/components/tool/export/copy-button.tsx +45 -0
- package/src/components/tool/export/export-panel.tsx +127 -0
- package/src/components/tool/export/target-list.tsx +87 -0
- package/src/components/tool/floating-window.tsx +69 -0
- package/src/components/tool/flow-canvas.tsx +138 -0
- package/src/components/tool/hooks/use-cabling.ts +66 -0
- package/src/components/tool/hooks/use-captures.ts +24 -0
- package/src/components/tool/hooks/use-clipboard.ts +54 -0
- package/src/components/tool/hooks/use-context-menus.ts +37 -0
- package/src/components/tool/hooks/use-design.ts +24 -0
- package/src/components/tool/hooks/use-desktop.ts +100 -0
- package/src/components/tool/hooks/use-devices.ts +58 -0
- package/src/components/tool/hooks/use-downloads.ts +42 -0
- package/src/components/tool/hooks/use-editor.ts +114 -0
- package/src/components/tool/hooks/use-engine.ts +29 -0
- package/src/components/tool/hooks/use-history.ts +107 -0
- package/src/components/tool/hooks/use-lab-actions.ts +57 -0
- package/src/components/tool/hooks/use-lab-run.ts +61 -0
- package/src/components/tool/hooks/use-lab.ts +32 -0
- package/src/components/tool/hooks/use-lan-carry.ts +43 -0
- package/src/components/tool/hooks/use-machine-actions.ts +57 -0
- package/src/components/tool/hooks/use-placement.ts +80 -0
- package/src/components/tool/hooks/use-project.ts +112 -0
- package/src/components/tool/hooks/use-projects.ts +85 -0
- package/src/components/tool/hooks/use-shared-link.ts +64 -0
- package/src/components/tool/hooks/use-toast.ts +16 -0
- package/src/components/tool/hooks/use-tools.ts +60 -0
- package/src/components/tool/lab-canvas.tsx +96 -0
- package/src/components/tool/overlays.tsx +104 -0
- package/src/components/tool/projects-list.tsx +96 -0
- package/src/components/tool/text.ts +9 -0
- package/src/components/tool/toolbar.tsx +106 -0
- package/src/components/tool/top-bars.tsx +90 -0
- package/src/components/tool/windows/conditions-fields.tsx +43 -0
- package/src/components/tool/windows/editor-windows.tsx +73 -0
- package/src/components/tool/windows/fields.ts +6 -0
- package/src/components/tool/windows/lab-status.tsx +68 -0
- package/src/components/tool/windows/lab-windows.tsx +130 -0
- package/src/components/tool/windows/link-controls.tsx +69 -0
- package/src/components/tool/windows/machine-controls.tsx +116 -0
- package/src/components/tool/windows/machine-inspector.tsx +83 -0
- package/src/components/tool/windows/runs-fields.tsx +127 -0
- package/src/components/tool/windows/selected-windows.tsx +147 -0
- package/src/components/ui/button.tsx +59 -0
- package/src/components/ui/dropdown-menu.tsx +185 -0
- package/src/index.ts +21 -0
- package/src/lib/auth.tsx +43 -0
- package/src/lib/brand.tsx +46 -0
- package/src/lib/i18n.ts +53 -0
- package/src/lib/tool/blocks.test.ts +64 -0
- package/src/lib/tool/blocks.ts +78 -0
- package/src/lib/tool/cable-wiring.test.ts +74 -0
- package/src/lib/tool/cables.test.ts +75 -0
- package/src/lib/tool/cables.ts +215 -0
- package/src/lib/tool/clone.test.ts +54 -0
- package/src/lib/tool/clone.ts +48 -0
- package/src/lib/tool/conditions.test.ts +170 -0
- package/src/lib/tool/conditions.ts +132 -0
- package/src/lib/tool/create.test.ts +67 -0
- package/src/lib/tool/create.ts +83 -0
- package/src/lib/tool/desktop.test.ts +59 -0
- package/src/lib/tool/desktop.ts +99 -0
- package/src/lib/tool/draft.test.ts +150 -0
- package/src/lib/tool/draft.ts +109 -0
- package/src/lib/tool/engine.test.ts +39 -0
- package/src/lib/tool/engine.ts +58 -0
- package/src/lib/tool/examples.test.ts +35 -0
- package/src/lib/tool/examples.ts +71 -0
- package/src/lib/tool/export-targets.test.ts +47 -0
- package/src/lib/tool/export-targets.ts +84 -0
- package/src/lib/tool/facing.test.ts +72 -0
- package/src/lib/tool/facing.ts +100 -0
- package/src/lib/tool/geometry.test.ts +80 -0
- package/src/lib/tool/geometry.ts +109 -0
- package/src/lib/tool/gns3-cables.test.ts +47 -0
- package/src/lib/tool/gns3-cables.ts +81 -0
- package/src/lib/tool/gns3-import.test.ts +102 -0
- package/src/lib/tool/gns3-import.ts +198 -0
- package/src/lib/tool/gns3-layout.test.ts +62 -0
- package/src/lib/tool/gns3-parts.test.ts +47 -0
- package/src/lib/tool/gns3-parts.ts +68 -0
- package/src/lib/tool/issues.test.ts +35 -0
- package/src/lib/tool/issues.ts +60 -0
- package/src/lib/tool/layout.test.ts +128 -0
- package/src/lib/tool/layout.ts +229 -0
- package/src/lib/tool/live.test.ts +46 -0
- package/src/lib/tool/live.ts +49 -0
- package/src/lib/tool/logos.test.ts +30 -0
- package/src/lib/tool/logos.ts +99 -0
- package/src/lib/tool/model.test.ts +128 -0
- package/src/lib/tool/model.ts +238 -0
- package/src/lib/tool/os.test.ts +45 -0
- package/src/lib/tool/os.ts +57 -0
- package/src/lib/tool/parity.test.ts +145 -0
- package/src/lib/tool/pins.test.ts +24 -0
- package/src/lib/tool/pins.ts +11 -0
- package/src/lib/tool/placement.test.ts +79 -0
- package/src/lib/tool/placement.ts +60 -0
- package/src/lib/tool/ports.test.ts +39 -0
- package/src/lib/tool/ports.ts +48 -0
- package/src/lib/tool/presets.test.ts +50 -0
- package/src/lib/tool/presets.ts +77 -0
- package/src/lib/tool/projects-api.test.ts +59 -0
- package/src/lib/tool/projects-api.ts +47 -0
- package/src/lib/tool/router-ports.test.ts +63 -0
- package/src/lib/tool/router-ports.ts +88 -0
- package/src/lib/tool/selection.test.ts +44 -0
- package/src/lib/tool/selection.ts +34 -0
- package/src/lib/tool/storage.test.ts +123 -0
- package/src/lib/tool/storage.ts +127 -0
- package/src/lib/tool/switch-edit.test.ts +49 -0
- package/src/lib/tool/switch-edit.ts +38 -0
- package/src/lib/tool/text/de.ts +358 -0
- package/src/lib/tool/text/en.ts +371 -0
- package/src/lib/tool/text/es.ts +358 -0
- package/src/lib/tool/text/fr.ts +358 -0
- package/src/lib/tool/text/index.ts +21 -0
- package/src/lib/tool/text/text.test.ts +33 -0
- package/src/lib/tool/topology.test.ts +108 -0
- package/src/lib/tool/topology.ts +116 -0
- package/src/lib/tool/vlans.test.ts +64 -0
- package/src/lib/tool/vlans.ts +79 -0
- package/src/lib/utils.ts +6 -0
- package/src/model.ts +13 -0
- package/src/test/arbitrary.ts +41 -0
- package/src/test/engine.ts +48 -0
- package/src/test/lab.ts +101 -0
- package/styles/globals.css +258 -0
- package/styles/theme.css +192 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +22 -0
- package/wasm/isothrum_wasm.d.ts +60 -0
- package/wasm/isothrum_wasm.js +312 -0
- package/wasm/isothrum_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/tool/model.ts", "../src/lib/tool/geometry.ts", "../src/lib/tool/vlans.ts", "../src/lib/tool/cables.ts", "../src/lib/tool/conditions.ts", "../src/lib/tool/ports.ts", "../src/lib/tool/layout.ts", "../src/lib/tool/topology.ts", "../src/lib/tool/create.ts", "../src/lib/tool/os.ts", "../src/lib/tool/examples.ts", "../src/lib/tool/engine.ts", "../src/lib/tool/export-targets.ts", "../src/lib/tool/issues.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Edge, Node, XYPosition } from \"@xyflow/react\";\n\n/**\n * The lab as drawn on the canvas: machines, LANs, switches and notes are nodes, cables are edges\n * between two ports. `toTopology` translates it into the topology JSON the engine reads\n * (`isothrum_compiler::Topology`), which it compiles to Isoloom's isoloom.yml.\n */\n\nexport type IconId = \"container\" | \"server\" | \"router\" | \"shield\" | \"monitor\" | \"laptop\";\n\n/** The Cisco OSes a container node can be (Isoloom's `app.appliance`). */\nexport type Appliance = \"cisco-iol\" | \"cisco-iol-l2\" | \"cisco-vios\" | \"cisco-vios-l2\" | \"cisco-csr1000v\" | \"cisco-c8000v\" | \"cisco-dynamips\";\n\n/** Each Cisco OS: its usual vrnetlab image (none for Dynamips: Isoloom builds the emulator). */\nexport const APPLIANCES: { id: Appliance; label: string; image: string; switch?: boolean; kvm?: boolean }[] = [\n { id: \"cisco-iol\", label: \"IOL (IOS on Linux)\", image: \"vrnetlab/cisco_iol:17.12.01\" },\n { id: \"cisco-iol-l2\", label: \"IOL-L2 (switch)\", image: \"vrnetlab/cisco_iol:L2-17.12.01\", switch: true },\n { id: \"cisco-vios\", label: \"IOSv\", image: \"vrnetlab/cisco_vios:15.9.3M6\", kvm: true },\n { id: \"cisco-vios-l2\", label: \"IOSvL2 (switch)\", image: \"vrnetlab/cisco_viosl2:15.2\", switch: true, kvm: true },\n { id: \"cisco-csr1000v\", label: \"CSR1000v (IOS XE)\", image: \"vrnetlab/cisco_csr1000v:17.03.08\", kvm: true },\n { id: \"cisco-c8000v\", label: \"Catalyst 8000v (IOS XE)\", image: \"vrnetlab/cisco_c8000v:17.15.01a\", kvm: true },\n { id: \"cisco-dynamips\", label: \"Dynamips 7200 (classic IOS)\", image: \"\" },\n];\n\n/** The OS's name for the `k`th data interface (from 1), as Isoloom numbers them. */\nexport function applianceInterface(kind: Appliance, k: number): string {\n switch (kind) {\n case \"cisco-iol\":\n case \"cisco-iol-l2\":\n return `Ethernet${String(Math.floor(k / 4))}/${String(k % 4)}`;\n case \"cisco-vios\":\n case \"cisco-vios-l2\":\n return `GigabitEthernet0/${String(k)}`;\n case \"cisco-csr1000v\":\n case \"cisco-c8000v\":\n return `GigabitEthernet${String(k + 1)}`;\n case \"cisco-dynamips\":\n return k === 1 ? \"FastEthernet0/0\" : `FastEthernet${String(Math.floor(k / 2))}/${String(k % 2)}`;\n }\n}\n\nexport type MachineData = {\n name: string;\n /** \"router\": an automatic router, not a machine (its networks reach each other). */\n kind: \"container\" | \"vm\" | \"router\";\n icon: IconId;\n /** Container image (`kind: container`): Isoloom's `app.image`. */\n image: string;\n /** The OS, as Isoloom writes it (`debian@12`, see `OS_CHOICES`) (`kind: vm`): Isoloom's `os`. */\n os: string;\n /** Provisioning steps, one per line (`kind: vm`): Isoloom's `app.steps`. */\n provision: string;\n /** Service ports, comma-separated, each a port and what it speaks when it isn't TCP (`80, 53/udp`): see `readPorts`. */\n ports: string;\n /** The machine the user lands on. */\n access: boolean;\n /**\n * A container with no service of its own (a stock Linux image), kept running idle as a\n * machine to work from. Unset: guessed from the image (see `idleByDefault`).\n */\n idle?: boolean;\n /** A network OS in its container, wired as its image expects (Cisco IOL, built with vrnetlab). */\n appliance?: Appliance;\n /** The appliance's own configuration (IOS commands), added to the one Isoloom generates. */\n config?: string;\n /** Addresses fixed by hand: LAN name -> last octet. Others take the next free one. */\n pins?: Record<string, number>;\n};\n\n/** Stock Linux images: a shell that exits at once, so they need to be kept running idle. */\nconst STOCK_IMAGES = [\"alpine\", \"ubuntu\", \"debian\", \"kali-rolling\", \"fedora\", \"rockylinux\", \"almalinux\", \"centos\", \"busybox\", \"archlinux\", \"netshoot\"];\n\n/** An image's name: its last path segment, without the tag, in lowercase. */\nexport function imageName(image: string): string {\n return image.trim().split(\":\")[0].split(\"/\").pop()?.toLowerCase() ?? \"\";\n}\n\nexport function idleByDefault(image: string): boolean {\n return STOCK_IMAGES.includes(imageName(image));\n}\n\n/** How a cable or a LAN behaves, as a slow or lossy link: one-way delay and its jitter (ms),\n * loss (%), bandwidth (Mbit/s). Isoloom applies it with `tc netem` (its `tc`). */\nexport type LinkConditions = {\n delay?: number;\n jitter?: number;\n loss?: number;\n rate?: number;\n};\n\n/** The two ends of a cable: `aPort` on the source machine, `bPort` on the target. */\nexport type CableData = {\n aPort: string;\n bPort: string;\n /** How the cable behaves (between two machines; a cable to a LAN takes the LAN's). */\n tc?: LinkConditions;\n /** Drawing only, worked out from what the cable is plugged into (never saved). */\n color?: string;\n};\n\n/** A LAN (or VLAN) drawn as an area: machines inside it are on it. */\nexport type LanData = {\n name: string;\n cidr: string;\n internet: boolean;\n /** How the LAN behaves (not on a switch's VLAN yet). */\n tc?: LinkConditions;\n /** Only its label is drawn: the LAN, its machines and their addresses are unchanged. */\n hidden?: boolean;\n};\n\n/** A VLAN on a switch: `vlan<id>`, with its block. VLANs are lab-wide: `vlan10` is the same\n * network on every switch, and a LAN area named `vlan10` is that VLAN too. */\ntype Vlan = {\n id: number;\n cidr: string;\n};\n\n/** A switch port: a VLAN (access), \"trunk\" (every VLAN of the switch), or nothing. */\nexport type PortMode = number | \"trunk\" | null;\n\n/**\n * A switch: access ports, each in a VLAN (or none), and one trunk port that carries every VLAN\n * of the switch. Not a machine: it is the network fabric, so it never appears in isoloom.yml;\n * its VLANs do, as networks, and a machine cabled to a port joins the port's VLAN.\n */\ntype SwitchData = {\n name: string;\n vlans: Vlan[];\n ports: PortMode[];\n};\n\n/** A note on the canvas: text, a box or an ellipse. Drawing only: never in isoloom.yml. */\nexport type NoteShape = \"text\" | \"rect\" | \"ellipse\";\nexport const NOTE_COLORS = [\"yellow\", \"red\", \"blue\", \"green\", \"violet\"] as const;\ntype NoteData = {\n text: string;\n shape: NoteShape;\n color: (typeof NOTE_COLORS)[number];\n};\n\nexport type MachineNode = Node<MachineData, \"machine\">;\nexport type LanNode = Node<LanData, \"lan\">;\nexport type SwitchNode = Node<SwitchData, \"switch\">;\nexport type NoteNode = Node<NoteData, \"note\">;\nexport type LabNode = MachineNode | LanNode | SwitchNode | NoteNode;\nexport type CableEdge = Edge<CableData, \"cable\">;\n\nexport const isMachine = (n: LabNode): n is MachineNode => n.type === \"machine\";\n/**\n * A router you configure (FRR as a container, or a VM): drawn as a faceplate with interfaces,\n * like a switch. The automatic router (kind \"router\") has no interfaces to show.\n */\nexport const isRealRouter = (n: LabNode): n is MachineNode => isMachine(n) && n.data.icon === \"router\" && n.data.kind !== \"router\";\nexport const ROUTER_PORTS = 4;\nexport const ROUTER_W = 168;\nexport const ROUTER_H = 74;\nexport const routerPort = (i: number) => `eth${String(i)}`;\n\n/** Routers and firewalls: one connection point, and they snap onto a LAN's edge. */\nexport const isAppliance = (n: LabNode): n is MachineNode => isMachine(n) && (n.data.icon === \"router\" || n.data.icon === \"shield\");\nexport const isLan = (n: LabNode): n is LanNode => n.type === \"lan\";\nexport const isSwitch = (n: LabNode): n is SwitchNode => n.type === \"switch\";\nexport const isNote = (n: LabNode): n is NoteNode => n.type === \"note\";\n/** A node's name as lists show it (a note has none). */\nexport const nodeName = (n: LabNode): string => (isNote(n) ? \"note\" : n.data.name);\n\nexport const SWITCH_PORTS = 8;\n/** A switch's faceplate on the canvas (see `SwitchNodeView`). */\nexport const SWITCH_W = 306;\nexport const SWITCH_H = 74;\nexport const TRUNK = \"trunk\";\n\n/** A machine's box on the canvas (see `MachineNodeView`). */\nexport const MACHINE_W = 120;\nexport const MACHINE_H = 116;\nexport const LAN_W = 420;\nexport const LAN_H = 300;\n\n/** How big a node is drawn: what snapping and \"is it inside the LAN\" go by. */\nexport function sizeOf(n: LabNode): { w: number; h: number } {\n if (n.type === \"lan\") return { w: n.width ?? LAN_W, h: n.height ?? LAN_H };\n if (n.type === \"switch\") return { w: SWITCH_W, h: SWITCH_H };\n if (n.type === \"note\") return { w: n.width ?? 200, h: n.height ?? 90 };\n if (n.data.icon === \"router\" && n.data.kind !== \"router\") return { w: ROUTER_W, h: ROUTER_H };\n return { w: MACHINE_W, h: MACHINE_H };\n}\n\n/** A node's center on the canvas, by its drawn size (measured by React Flow, or `fallback`). */\nexport function centerOf(n: LabNode, fallback: { w: number; h: number } = { w: MACHINE_W, h: MACHINE_H }): XYPosition {\n return {\n x: n.position.x + (n.width ?? n.measured?.width ?? fallback.w) / 2,\n y: n.position.y + (n.height ?? n.measured?.height ?? fallback.h) / 2,\n };\n}\n\n/**\n * How a LAN behaves on the canvas, as an Excalidraw frame: it is moved and selected by its label\n * only, so a click or a drag inside it reaches the canvas (placing a device, drawing a selection\n * box). Not saved: applied to every LAN when it is created or loaded.\n */\nexport const LAN_BEHAVIOR = { dragHandle: \".lan-handle\", className: \"pointer-events-none\" } as const;\n\n/** Which side of a LAN an appliance sits on, when it is snapped onto one. */\nexport type Side = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/** The canvas's look (see `DesignContext`): hand-drawn, or crisp for a report. */\nexport type Design = \"sketch\" | \"business\";\nexport const DESIGNS: Design[] = [\"sketch\", \"business\"];\n\nexport type Draft = {\n name: string;\n nodes: LabNode[];\n edges: CableEdge[];\n design?: Design;\n};\n\n/** Machine names are Isoloom keys: lowercase letters, digits and dashes. */\nexport function cleanName(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9-]/g, \"\");\n}\n\nexport function uid(prefix: string): string {\n return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`;\n}\n\n/** The first `<prefix><n>` no machine or LAN uses yet. */\nexport function freeName(prefix: string, nodes: LabNode[]): string {\n const taken = new Set(nodes.map(nodeName));\n for (let i = 1; ; i++) {\n if (!taken.has(`${prefix}${String(i)}`)) return `${prefix}${String(i)}`;\n }\n}\n\n/** Every node unselected: what placing or pasting something does to the rest. */\nexport function unselected<T extends LabNode | CableEdge>(items: T[]): T[] {\n return items.map((n) => ({ ...n, selected: false }));\n}\n", "import type { XYPosition } from \"@xyflow/react\";\nimport { LAN_H, LAN_W, MACHINE_H, MACHINE_W, isAppliance, isLan, sizeOf, type LabNode, type LanNode, type MachineNode, type Side } from \"./model\";\n\n/** Where things sit on the canvas: LAN areas, their borders, and what snaps onto them. */\n\n/** How close (canvas units) an appliance's center must come to a LAN's border to snap onto it. */\nconst SNAP = 36;\n/** Exactly on the border, give or take rounding. */\nconst ON = 0.5;\n\ninterface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** The area a LAN covers on the canvas. */\nexport function lanRect(l: LanNode): Rect {\n return { x: l.position.x, y: l.position.y, w: l.width ?? LAN_W, h: l.height ?? LAN_H };\n}\n\n/** Whether `p` lies within `r`, borders included, give or take `slack`. */\nexport function within(r: Rect, p: XYPosition, slack = 0): boolean {\n return p.x >= r.x - slack && p.x <= r.x + r.w + slack && p.y >= r.y - slack && p.y <= r.y + r.h + slack;\n}\n\n/** A point on one of a rectangle's borders, and how far `p` is from it. */\ninterface BorderPoint {\n d: number;\n x: number;\n y: number;\n}\n\n/** The points of `r`'s borders facing `p` (top, bottom, left, right), where `p` is level with them. */\nfunction borderPoints(r: Rect, p: XYPosition): BorderPoint[] {\n const alongX = p.x >= r.x && p.x <= r.x + r.w;\n const alongY = p.y >= r.y && p.y <= r.y + r.h;\n const out: BorderPoint[] = [];\n if (alongX) out.push({ d: Math.abs(p.y - r.y), x: p.x, y: r.y }, { d: Math.abs(p.y - (r.y + r.h)), x: p.x, y: r.y + r.h });\n if (alongY) out.push({ d: Math.abs(p.x - r.x), x: r.x, y: p.y }, { d: Math.abs(p.x - (r.x + r.w)), x: r.x + r.w, y: p.y });\n return out;\n}\n\n/**\n * Where an appliance at `position` should go: centered on the nearest LAN border within reach\n * (half inside, half outside), or where it is when no border is near.\n */\nexport function snapToLanEdge(position: XYPosition, lans: LanNode[], size: { w: number; h: number } = { w: MACHINE_W, h: MACHINE_H }): XYPosition {\n const center = { x: position.x + size.w / 2, y: position.y + size.h / 2 };\n let best: BorderPoint | null = null;\n for (const l of lans) {\n for (const c of borderPoints(lanRect(l), center)) if (c.d <= SNAP && (!best || c.d < best.d)) best = c;\n }\n return best ? { x: best.x - size.w / 2, y: best.y - size.h / 2 } : position;\n}\n\n/** The side of `r` that `p` lies on, if any (top, bottom, left, right, in that order). */\nfunction sideOf(r: Rect, p: XYPosition): Side | null {\n const alongX = p.x >= r.x && p.x <= r.x + r.w;\n const alongY = p.y >= r.y && p.y <= r.y + r.h;\n if (alongX && Math.abs(p.y - r.y) < ON) return \"top\";\n if (alongX && Math.abs(p.y - (r.y + r.h)) < ON) return \"bottom\";\n if (alongY && Math.abs(p.x - r.x) < ON) return \"left\";\n if (alongY && Math.abs(p.x - (r.x + r.w)) < ON) return \"right\";\n return null;\n}\n\n/** A node's center, by the size it is drawn at (see `sizeOf`). */\nfunction drawnCenter(m: LabNode): XYPosition {\n const { w, h } = sizeOf(m);\n return { x: m.position.x + w / 2, y: m.position.y + h / 2 };\n}\n\n/** The LAN border an appliance sits on, if any: the side of the LAN, so its port faces out. */\nexport function sideOnLan(m: MachineNode, lans: LanNode[]): Side | null {\n const c = drawnCenter(m);\n for (const l of lans) {\n const side = sideOf(lanRect(l), c);\n if (side) return side;\n }\n return null;\n}\n\n/** The LAN an appliance sits on the border of, and which border: its inside and outside. */\nexport function lanUnder(m: MachineNode, lans: LanNode[]): { lan: LanNode; side: Side } | null {\n for (const l of lans) {\n const side = sideOnLan(m, [l]);\n if (side) return { lan: l, side };\n }\n return null;\n}\n\n/** Whether a point is inside a LAN's area (not on its border). */\nexport function insideLan(p: XYPosition, l: LanNode): boolean {\n const r = lanRect(l);\n return p.x > r.x + ON && p.x < r.x + r.w - ON && p.y > r.y + ON && p.y < r.y + r.h - ON;\n}\n\n/**\n * The LANs a machine sits in: its center is inside the area (or on its border). A hidden LAN's\n * area isn't drawn, so it doesn't take machines by position, except routers and firewalls on\n * its edge; machines join it through its switch's ports or a cable.\n */\nexport function lansAround(m: MachineNode, nodes: LabNode[]): LanNode[] {\n const c = drawnCenter(m);\n const onEdge = isAppliance(m) ? lanUnder(m, nodes.filter(isLan))?.lan.id : undefined;\n return nodes.filter(isLan).filter((l) => (!l.data.hidden || l.id === onEdge) && within(lanRect(l), c));\n}\n", "import { TRUNK, isSwitch, type CableEdge, type LabNode, type SwitchNode } from \"./model\";\n\n/** VLANs and switch ports. VLANs are lab-wide: `vlan10` is the same network on every switch. */\n\nexport const vlanName = (id: number) => `vlan${String(id)}`;\n\n/** A VLAN's color, from its number, so `vlan10` has the same color on every switch and area. */\nconst VLAN_COLORS = [\"#7048e8\", \"#2f9e44\", \"#1971c2\", \"#e8590c\", \"#e03131\", \"#0c8599\", \"#c2255c\", \"#5c940d\"];\nexport function vlanColor(id: number): string {\n return VLAN_COLORS[Math.abs(Math.round(id / 10)) % VLAN_COLORS.length];\n}\n\n/** A trunk port's look: striped with the colors of the VLANs it carries (muted when none). */\nexport function trunkStripes(vlans: { id: number }[]): string {\n if (!vlans.length) return \"var(--muted)\";\n const stripes = vlans.map((v, i) => `${vlanColor(v.id)} ${String(i * 5)}px ${String((i + 1) * 5)}px`).join(\", \");\n return `repeating-linear-gradient(135deg, ${stripes})`;\n}\n\n/** The VLAN number in a LAN's name (`vlan10` -> 10), when the LAN is a VLAN. */\nexport function vlanOf(lanName: string): number | null {\n const m = /^vlan(\\d{1,4})$/.exec(lanName);\n return m ? Number(m[1]) : null;\n}\n\n/** The port a switch handle stands for: `p3` -> its index, `trunk` -> the trunk. */\nfunction switchPort(handle: string | null | undefined): number | typeof TRUNK | null {\n if (handle === TRUNK) return TRUNK;\n const m = handle ? /^p(\\d+)$/.exec(handle) : null;\n return m ? Number(m[1]) - 1 : null;\n}\n\n/** Whether a switch handle is a trunk: the T port, or any port set to \"trunk\". */\nexport function isTrunkPort(sw: SwitchNode, handle: string | null | undefined): boolean {\n const port = switchPort(handle);\n return port === TRUNK || (typeof port === \"number\" && sw.data.ports[port] === TRUNK);\n}\n\n/** The VLAN of an access port, or null (a trunk, an unassigned port, not a port). */\nexport function accessVlan(sw: SwitchNode, handle: string | null | undefined): number | null {\n const port = switchPort(handle);\n const mode = typeof port === \"number\" ? sw.data.ports[port] : null;\n return typeof mode === \"number\" ? mode : null;\n}\n\n/** A port's label next to its cable: 1, 2\u2026 or T. */\nexport function switchPortLabel(handle: string | null | undefined): string {\n const p = switchPort(handle);\n if (p === TRUNK) return \"T\";\n return p === null ? \"\" : String(p + 1);\n}\n\n/**\n * The LAN each switch's VLANs belong to in isoloom.yml: switches joined by a cable, or sharing a\n * VLAN number (VLANs are lab-wide here), are one LAN, named after the first. Returns a switch's\n * LAN name by its id.\n */\nexport function switchLans(nodes: LabNode[], edges: CableEdge[]): (switchId: string) => string {\n const switches = nodes.filter(isSwitch);\n const group = new Map(switches.map((s) => [s.id, s.id]));\n const root = (id: string): string => {\n const up = group.get(id);\n return up === undefined || up === id ? id : root(up);\n };\n const unite = (a: string, b: string) => group.set(root(a), root(b));\n for (const e of edges) if (group.has(e.source) && group.has(e.target)) unite(e.source, e.target);\n const firstWith = new Map<number, string>();\n for (const sw of switches) {\n for (const v of sw.data.vlans) {\n const other = firstWith.get(v.id);\n if (other) unite(sw.id, other);\n else firstWith.set(v.id, sw.id);\n }\n }\n return (id: string) => switches.find((s) => root(s.id) === root(id))?.data.name ?? \"\";\n}\n\n/** The name Isoloom gives a trunk's interface: its LAN's, within Linux's limit (`sw1`, then `sw1.10`). */\nexport const trunkInterface = (lan: string) => lan.slice(0, 10).replace(/-+$/, \"\");\n", "import type { Connection } from \"@xyflow/react\";\nimport { sideOnLan } from \"./geometry\";\nimport { isAppliance, isLan, isMachine, isRealRouter, isSwitch, routerPort, uid, type CableEdge, type LabNode, type LanNode, type MachineNode } from \"./model\";\nimport { accessVlan, switchPortLabel, vlanName } from \"./vlans\";\n\n/** Cables: where they plug in, what their ends are called, and which network each one is. */\n\n/** The handle a cable uses on a machine: an appliance's single port, or a machine's top/bottom. */\nfunction machineHandle(m: MachineNode, old: string | null | undefined): string {\n if (isAppliance(m)) return \"port\";\n return old === \"bottom\" ? \"bottom\" : \"top\";\n}\n\nconst isPort = (h: string | null | undefined): h is string => !!h && /^eth\\d+$/.test(h);\n\n/** The ports each real router's cables already plug into. */\nfunction takenPorts(edges: CableEdge[], byId: Map<string, LabNode>): Map<string, Set<string>> {\n const taken = new Map<string, Set<string>>();\n for (const e of edges) {\n for (const [id, h] of [\n [e.source, e.sourceHandle],\n [e.target, e.targetHandle],\n ] as const) {\n const n = byId.get(id);\n if (n && isRealRouter(n) && isPort(h)) taken.set(id, (taken.get(id) ?? new Set()).add(h));\n }\n }\n return taken;\n}\n\n/**\n * Cables saved with the old handles (one on each side of every machine) move onto the current\n * ones: an appliance's single port, the top or bottom of other machines. LAN handles stay.\n */\nexport function migrateEdges(edges: CableEdge[], nodes: LabNode[]): CableEdge[] {\n const byId = new Map(nodes.map((n) => [n.id, n]));\n // A real router's cables plug into its ports: one that isn't on a port yet takes a free one.\n const taken = takenPorts(edges, byId);\n const freePort = (id: string) => {\n const used = taken.get(id) ?? new Set<string>();\n for (let i = 0; i < 64; i++) {\n if (!used.has(routerPort(i))) {\n used.add(routerPort(i));\n taken.set(id, used);\n return routerPort(i);\n }\n }\n return routerPort(0);\n };\n const fix = (id: string, h: string | null | undefined) => {\n const n = byId.get(id);\n if (n && isRealRouter(n)) return isPort(h) ? h : freePort(id);\n return n && isMachine(n) ? machineHandle(n, h) : h;\n };\n return edges.map((e) => ({ ...e, sourceHandle: fix(e.source, e.sourceHandle), targetHandle: fix(e.target, e.targetHandle) }));\n}\n\n/** The ports a machine's cables already use. */\nfunction usedPorts(nodeId: string, edges: CableEdge[]): string[] {\n return edges.flatMap((e) => [...(e.source === nodeId && e.data ? [e.data.aPort] : []), ...(e.target === nodeId && e.data ? [e.data.bPort] : [])]);\n}\n\n/**\n * The first free `eth<n>` on a machine, from `first`: an appliance on a LAN's edge uses eth0 on\n * that LAN, so its cables start at eth1.\n */\nexport function nextPort(nodeId: string, edges: CableEdge[], first = 0): string {\n const used = new Set(usedPorts(nodeId, edges));\n for (let i = first; ; i++) {\n if (!used.has(`eth${String(i)}`)) return `eth${String(i)}`;\n }\n}\n\n/** Whether a cable runs between two named machines: a link of its own in isoloom.yml. */\nfunction isLink(e: CableEdge, byId: Map<string, LabNode>): boolean {\n const x = byId.get(e.source);\n const y = byId.get(e.target);\n return !!x && !!y && isMachine(x) && isMachine(y) && !!x.data.name && !!y.data.name && !!e.data;\n}\n\n/** The network a cable on a switch port carries: the access port's VLAN (a trunk carries several). */\nfunction switchNetwork(edge: CableEdge, byId: Map<string, LabNode>): string | null | undefined {\n for (const [sw, handle] of [\n [byId.get(edge.source), edge.sourceHandle],\n [byId.get(edge.target), edge.targetHandle],\n ] as const) {\n if (!sw || !isSwitch(sw)) continue;\n const vlan = accessVlan(sw, handle);\n return vlan !== null ? vlanName(vlan) : null;\n }\n return undefined;\n}\n\n/**\n * The Isoloom network a cable became (`link-<n>`): cables between two machines are numbered in\n * the order `toTopology` lists them. A cable to a LAN is that LAN.\n */\nexport function networkOfCable(edgeId: string, nodes: LabNode[], edges: CableEdge[]): string | null {\n const byId = new Map(nodes.map((n) => [n.id, n]));\n const edge = edges.find((e) => e.id === edgeId);\n if (!edge) return null;\n const a = byId.get(edge.source);\n const b = byId.get(edge.target);\n if (a && isLan(a)) return a.data.name;\n if (b && isLan(b)) return b.data.name;\n const onSwitch = switchNetwork(edge, byId);\n if (onSwitch !== undefined) return onSwitch;\n const i = edges.filter((e) => isLink(e, byId)).findIndex((e) => e.id === edgeId);\n return i < 0 ? null : `link-${String(i)}`;\n}\n\n/** The names of a cable's ends that are machines (a LAN or a switch has no port to cut). */\nexport function machineEnds(edge: CableEdge, nodes: LabNode[]): string[] {\n return [edge.source, edge.target].flatMap((id) => {\n const n = nodes.find((x) => x.id === id);\n return n && isMachine(n) ? [n.data.name] : [];\n });\n}\n\n/** What a cable's ends can plug into, by node id. */\nexport interface LabIndex {\n lanIds: Set<string>;\n switchIds: Set<string>;\n routerIds: Set<string>;\n}\n\n/** Whether a cable end on `node` takes a port: a switch's or a real router's. */\nconst ported = (index: LabIndex, node: string) => index.switchIds.has(node) || index.routerIds.has(node);\n\n/** Whether a switch or router port already has a cable (other than `ignore`, the one moving). */\nfunction portTaken(index: LabIndex, edges: CableEdge[], ignore: string | undefined, node: string, handle: string | null | undefined): boolean {\n return (\n ported(index, node) &&\n edges.some((e) => e.id !== ignore && ((e.source === node && e.sourceHandle === handle) || (e.target === node && e.targetHandle === handle)))\n );\n}\n\n/** A cable's two ends, as React Flow gives them (a connection being drawn, or a cable). */\ninterface Ends {\n source: string;\n target: string;\n sourceHandle?: string | null;\n targetHandle?: string | null;\n}\n\n/**\n * Machine to machine, LAN or port. Never LAN to LAN or switch to LAN, and a switch or router\n * port takes one cable, as on real gear. `ignore`: the cable being moved, whose ends don't count.\n */\nexport function canConnect(c: Ends, index: LabIndex, edges: CableEdge[], ignore?: string): boolean {\n if (c.source === c.target) return false;\n const area = (id: string) => index.lanIds.has(id) || index.switchIds.has(id);\n const bothSwitches = index.switchIds.has(c.source) && index.switchIds.has(c.target);\n if (area(c.source) && area(c.target) && !bothSwitches) return false;\n return !portTaken(index, edges, ignore, c.source, c.sourceHandle) && !portTaken(index, edges, ignore, c.target, c.targetHandle);\n}\n\n/** An appliance on a LAN's edge already uses eth0 there. */\nexport function onLanEdge(id: string, nodes: LabNode[], lans: LanNode[]): boolean {\n const n = nodes.find((x) => x.id === id);\n return !!n && isAppliance(n) && sideOnLan(n, lans) !== null;\n}\n\n/**\n * The label of a cable's end on `node`: the switch port, the router's port, nothing on a LAN,\n * or else `machinePort()` (the machine's next free port, or the one it keeps).\n */\nfunction endLabel(index: LabIndex, node: string, handle: string | null | undefined, machinePort: () => string): string {\n if (index.lanIds.has(node)) return \"\";\n if (index.switchIds.has(node)) return switchPortLabel(handle);\n if (index.routerIds.has(node)) return handle ?? \"\";\n return machinePort();\n}\n\n/** A new cable for a connection, its ends labelled (`first(node)`: the first port to give). */\nexport function newCable(c: Connection, index: LabIndex, edges: CableEdge[], first: (node: string) => number): CableEdge {\n return {\n id: uid(\"c\"),\n type: \"cable\",\n source: c.source,\n sourceHandle: c.sourceHandle,\n target: c.target,\n targetHandle: c.targetHandle,\n // A LAN has no ports: the cable's end on it stays unnamed.\n data: {\n aPort: endLabel(index, c.source, c.sourceHandle, () => nextPort(c.source, edges, first(c.source))),\n bPort: endLabel(index, c.target, c.targetHandle, () => nextPort(c.target, edges, first(c.target))),\n },\n };\n}\n\n/**\n * The cables after `old` moved to `c`. A moved end gets the label of where it lands: the switch\n * port, nothing on a LAN, or the machine's next free port. An end that stays put keeps its label\n * (same machine: same port; where on its box the cable attaches is only drawing).\n */\nexport function reconnected(edges: CableEdge[], old: CableEdge, c: Connection, index: LabIndex, first: (node: string) => number): CableEdge[] {\n const others = edges.filter((e) => e.id !== old.id);\n const label = (node: string, handle: string | null, keep: string, same: boolean) => endLabel(index, node, handle, () => (same ? keep : nextPort(node, others, first(node))));\n return edges.map((e) =>\n e.id !== old.id || !e.data\n ? e\n : {\n ...e,\n source: c.source,\n sourceHandle: c.sourceHandle,\n target: c.target,\n targetHandle: c.targetHandle,\n data: {\n aPort: label(c.source, c.sourceHandle, e.data.aPort, c.source === e.source),\n bPort: label(c.target, c.targetHandle, e.data.bPort, c.target === e.target),\n },\n },\n );\n}\n", "import type { LinkConditions } from \"./model\";\n\n/** Isoloom's `tc` for a slow or lossy link, as the engine's topology takes it. */\nexport interface Tc {\n delay?: string;\n jitter?: string;\n loss?: number;\n rate?: string;\n}\n\n/** Whether a field holds a number: every finite one is written, for Isoloom to judge (it reports a loss above 100%, or a jitter without a delay). */\nconst isSet = (n: number | undefined): n is number => typeof n === \"number\" && Number.isFinite(n);\n\n/** Whether `n` is a whole number, give or take what floating point loses in a multiplication. */\nconst whole = (n: number) => Math.abs(n - Math.round(n)) < 1e-9;\n\n/** A rate (Mbit/s) as `tc` writes one: whole Mbit/s, else whole kbit/s, else bits. */\nfunction rateText(rate: number): string {\n if (Number.isInteger(rate)) return `${String(rate)}mbit`;\n if (whole(rate * 1000)) return `${String(Math.round(rate * 1000))}kbit`;\n return `${String(rate * 1_000_000)}bit`;\n}\n\n/** The conditions as Isoloom's `tc` takes them, or `undefined` when none is set. */\nexport function tcOf(c: LinkConditions | undefined): Tc | undefined {\n if (!c) return undefined;\n const tc = {\n ...(isSet(c.delay) ? { delay: `${String(c.delay)}ms` } : {}),\n ...(isSet(c.jitter) ? { jitter: `${String(c.jitter)}ms` } : {}),\n ...(isSet(c.loss) ? { loss: c.loss } : {}),\n ...(isSet(c.rate) ? { rate: rateText(c.rate) } : {}),\n };\n return Object.keys(tc).length ? tc : undefined;\n}\n\n/** A short summary for the canvas: `40 ms \u00B15 \u00B7 0.5% \u00B7 10 Mbit/s`. */\nexport function conditionsLabel(c: LinkConditions | undefined): string {\n if (!tcOf(c) || !c) return \"\";\n const parts: string[] = [];\n const jitter = isSet(c.jitter) ? ` \u00B1${String(c.jitter)}` : \"\";\n if (isSet(c.delay)) parts.push(`${String(c.delay)} ms${jitter}`);\n else if (jitter) parts.push(`${jitter.trim()} ms`);\n if (isSet(c.loss)) parts.push(`${String(c.loss)}%`);\n if (isSet(c.rate)) parts.push(`${String(c.rate)} Mbit/s`);\n return parts.join(\" \u00B7 \");\n}\n\n/** The `tc` fields written as a number and a unit: two times and a rate. */\ntype MeasuredField = \"delay\" | \"jitter\" | \"rate\";\n\n/** A `tc` value the editor can't read: left out of the drawing and reported, never guessed. */\nexport interface TcProblem {\n field: MeasuredField;\n value: string;\n}\n\n/** A `tc` read back: the editor's numbers (none when nothing is set), and each value it couldn't read. */\nexport interface ReadConditions {\n conditions: LinkConditions | undefined;\n problems: TcProblem[];\n}\n\n/** A unit as `[times, per]`: a value in it is `value * times / per` in the editor's unit. */\ntype Scale = readonly [times: number, per: number];\n\n/** The time units Isoloom's `tc` takes, to milliseconds. */\nconst TIME: ReadonlyMap<string, Scale> = new Map([\n [\"us\", [1, 1000]],\n [\"ms\", [1, 1]],\n [\"s\", [1000, 1]],\n]);\n\n/** The rate units Isoloom's `tc` takes, to Mbit/s: `bps` counts bytes. A rate has its unit: Isoloom refuses a bare number. */\nconst RATE: ReadonlyMap<string, Scale> = new Map([\n [\"bit\", [1, 1_000_000]],\n [\"kbit\", [1, 1000]],\n [\"mbit\", [1, 1]],\n [\"gbit\", [1000, 1]],\n [\"bps\", [8, 1_000_000]],\n [\"kbps\", [8, 1000]],\n [\"mbps\", [8, 1]],\n [\"gbps\", [8000, 1]],\n]);\n\nconst UNITS: Record<MeasuredField, ReadonlyMap<string, Scale>> = { delay: TIME, jitter: TIME, rate: RATE };\n\n/**\n * A number and the letters after it, split as Isoloom splits a `tc` value: the letters at the end\n * are the unit, and what comes before is a number as Rust reads one (a sign, digits with or\n * without a point, an exponent: `40`, `-0.5`, `.5`, `5.`, `1E-3`).\n */\nconst MEASURE = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)([A-Za-z]*)$/;\n\n/** `40ms` -> 40, `500us` -> 0.5, `1gbit` -> 1000: in the editor's unit, or undefined when `v` isn't a number and one of `units` (Isoloom's, case and all), or too large to be one. */\nfunction measure(v: string, units: ReadonlyMap<string, Scale>): number | undefined {\n const m = MEASURE.exec(v);\n const scale = m ? units.get(m[2]) : undefined;\n if (!m || !scale) return undefined;\n const n = (Number(m[1]) * scale[0]) / scale[1];\n return Number.isFinite(n) ? n : undefined;\n}\n\n/** Isoloom's `tc` back to the editor's numbers: the inverse of `tcOf`, every value it can't read reported. */\nexport function conditionsOf(tc: Tc | undefined): ReadConditions {\n const conditions: LinkConditions = {};\n const problems: TcProblem[] = [];\n for (const field of [\"delay\", \"jitter\", \"rate\"] as const) {\n const value = tc?.[field];\n if (value === undefined) continue;\n const n = measure(value, UNITS[field]);\n if (n === undefined) problems.push({ field, value });\n else conditions[field] = n;\n }\n if (tc?.loss !== undefined) conditions.loss = tc.loss;\n return { conditions: Object.keys(conditions).length ? conditions : undefined, problems };\n}\n\n/** What each field takes, as the fix a problem names. */\nconst EXPECTED: Record<MeasuredField, string> = { delay: \"a time like 40ms, 500us or 1s\", jitter: \"a time like 40ms, 500us or 1s\", rate: \"a rate like 10mbit or 512kbit\" };\n\n/** A problem in words, for the `tc` at path `at` in the topology: where, the value, the form it takes. */\nexport function tcProblemText(at: string, p: TcProblem): string {\n return `${at}.${p.field}: \\`${p.value}\\` isn't ${EXPECTED[p.field]}; the drawing leaves it out`;\n}\n\n/** The conditions a field edit leaves: a positive number, or the field cleared (none when all are). */\nexport function withCondition(value: LinkConditions | undefined, key: keyof LinkConditions, raw: string): LinkConditions | undefined {\n const n = raw === \"\" ? undefined : Number(raw);\n const next = { ...value, [key]: n !== undefined && Number.isFinite(n) && n > 0 ? n : undefined };\n // Every field it has, even one a stored lab carries that the editor doesn't know.\n return Object.values(next as Record<string, number | undefined>).some((v) => v !== undefined) ? next : undefined;\n}\n", "/**\n * A machine's ports field (`80, 443/https, 53/udp`): each entry a port, 1 to 65535, and what it\n * speaks when it isn't plain TCP: Isoloom's port `protocol`. An entry Isoloom couldn't take is\n * kept out of the topology and reported (see `canvasIssues`), never guessed.\n */\n\n/** What a port speaks: TCP (the default), UDP, or TCP that speaks HTTP, HTTPS or TLS. */\nexport const PORT_PROTOCOLS = [\"tcp\", \"udp\", \"http\", \"https\", \"tls\"] as const;\ntype PortProtocol = (typeof PORT_PROTOCOLS)[number];\n\n/** A port as the engine's topology takes it (`protocol` left out for TCP). */\nexport interface Service {\n port: number;\n protocol?: PortProtocol;\n}\n\nconst ENTRY = /^(\\d{1,5})(?:\\/([a-z]+))?$/;\n\nconst isProtocol = (p: string): p is PortProtocol => PORT_PROTOCOLS.some((x) => x === p);\n\n/** One entry as a port, or null when Isoloom couldn't take it. */\nfunction service(entry: string): Service | null {\n const m = ENTRY.exec(entry);\n if (!m) return null;\n // An entry without its protocol is TCP.\n const [, digits, protocol = \"tcp\"] = m;\n const port = Number(digits);\n if (port < 1 || port > 65535) return null;\n if (protocol === \"tcp\") return { port };\n return isProtocol(protocol) ? { port, protocol } : null;\n}\n\n/** The ports field read: its ports, and each entry it couldn't read. */\nexport function readPorts(text: string): { services: Service[]; bad: string[] } {\n const services: Service[] = [];\n const bad: string[] = [];\n for (const entry of text.split(\",\").map((e) => e.trim()).filter(Boolean)) {\n const s = service(entry);\n if (s) services.push(s);\n else bad.push(entry);\n }\n return { services, bad };\n}\n\n/** Ports as the field writes them: `80, 53/udp`. */\nexport function portsText(services: readonly Service[]): string {\n return services.map((s) => (s.protocol && s.protocol !== \"tcp\" ? `${String(s.port)}/${s.protocol}` : String(s.port))).join(\", \");\n}\n", "/**\n * A lab given as the engine's topology (what an assistant writes through the MCP server), laid\n * out on the canvas: LANs side by side with their machines in a grid inside, automatic routers\n * under the LANs they join (cabled to each), the machines on no LAN in a row below, and the\n * cables between machines. The inverse of `toTopology`, for labs nobody drew.\n */\n\nimport { migrateEdges, nextPort } from \"./cables\";\nimport { conditionsOf, tcProblemText, type Tc } from \"./conditions\";\nimport { portsText, type Service } from \"./ports\";\nimport {\n LAN_BEHAVIOR,\n MACHINE_W,\n centerOf,\n cleanName,\n uid,\n type CableEdge,\n type Draft,\n type IconId,\n type LabNode,\n type LanNode,\n type LinkConditions,\n type MachineData,\n type MachineNode,\n type Side,\n} from \"./model\";\n\ntype TopologyNode = {\n kind: { container?: { image: string; idle?: boolean }; vm?: { os: string; provision?: string[] }; router?: object };\n access?: boolean;\n services?: Service[];\n networks?: Record<string, number | null>;\n};\n\nexport interface Topology {\n name: string;\n nodes: Record<string, TopologyNode>;\n networks?: Record<string, { cidr: string; internet?: boolean; tc?: Tc }>;\n links?: { a: { node: string; port: string }; b: { node: string; port: string }; tc?: Tc }[];\n}\n\nconst COLS = 3;\nconst CELL_W = 170;\nconst CELL_H = 175;\nconst GAP = 160;\n\nfunction icon(kind: TopologyNode[\"kind\"], access: boolean): IconId {\n if (kind.router) return \"router\";\n if (kind.vm) return access ? \"laptop\" : \"monitor\";\n const image = kind.container?.image ?? \"\";\n return /nginx|httpd|apache|caddy|traefik|postgres|mysql|mariadb|redis|mongo|bind|dnsmasq|vsftpd|postfix/.test(image) ? \"server\" : \"container\";\n}\n\n/** What the layout builds: the nodes and cables so far, the LANs and machines by topology name,\n * and what in the topology the drawing can't keep. */\ninterface Layout {\n topo: Topology;\n nodes: LabNode[];\n edges: CableEdge[];\n lans: Map<string, LanNode>;\n machines: Map<string, MachineNode>;\n problems: string[];\n}\n\n/** A topology laid out: the draft, and each value in the topology the drawing leaves out. */\nexport interface LaidOut {\n draft: Draft;\n problems: string[];\n}\n\n/** The editor's numbers for the `tc` at path `at`, each value it can't read kept as a problem. */\nfunction conditionsAt(layout: Layout, at: string, tc: Tc | undefined): LinkConditions | undefined {\n const { conditions, problems } = conditionsOf(tc);\n layout.problems.push(...problems.map((p) => tcProblemText(at, p)));\n return conditions;\n}\n\n/** The LANs among a machine's networks. */\nconst lansOf = (layout: Layout, n: TopologyNode) => Object.keys(n.networks ?? {}).filter((name) => layout.lans.has(name));\n\n/** Each LAN's machines: those that sit in it (their first LAN); routers join theirs by cables. */\nfunction residents(topo: Topology): Map<string, string[]> {\n const lanNames = Object.keys(topo.networks ?? {});\n const inside = new Map<string, string[]>(lanNames.map((l) => [l, []]));\n for (const [name, n] of Object.entries(topo.nodes)) {\n const first = Object.keys(n.networks ?? {}).find((l) => lanNames.includes(l));\n if (!n.kind.router && first) inside.get(first)?.push(name);\n }\n return inside;\n}\n\n/** The LANs, in a row, each sized for the grid of machines inside it. */\nfunction placeLans(layout: Layout, inside: Map<string, string[]>): void {\n let x = 0;\n for (const [name, net] of Object.entries(layout.topo.networks ?? {})) {\n const count = Math.max(1, inside.get(name)?.length ?? 0);\n const w = Math.max(340, Math.min(COLS, count) * CELL_W + 80);\n const h = Math.max(240, Math.ceil(count / COLS) * CELL_H + 90);\n const lan: LanNode = {\n ...LAN_BEHAVIOR,\n id: uid(\"l\"),\n type: \"lan\",\n position: { x, y: 0 },\n width: w,\n height: h,\n data: { name: cleanName(name), cidr: net.cidr, internet: net.internet ?? true, tc: conditionsAt(layout, `networks.${name}.tc`, net.tc) },\n };\n layout.lans.set(name, lan);\n layout.nodes.push(lan);\n x += w + GAP;\n }\n}\n\n/** A machine from its topology entry, at `position`. */\nfunction addMachine(layout: Layout, name: string, n: TopologyNode, position: { x: number; y: number }): void {\n const access = !!n.access;\n const data: MachineData = {\n name: cleanName(name),\n kind: n.kind.router ? \"router\" : n.kind.vm ? \"vm\" : \"container\",\n icon: icon(n.kind, access),\n image: n.kind.container?.image ?? \"\",\n os: n.kind.vm?.os ?? \"\",\n provision: (n.kind.vm?.provision ?? []).join(\"\\n\"),\n ports: portsText(n.services ?? []),\n access,\n ...(n.kind.container ? { idle: n.kind.container.idle } : {}),\n };\n const pins = Object.fromEntries(Object.entries(n.networks ?? {}).filter((e): e is [string, number] => typeof e[1] === \"number\"));\n if (Object.keys(pins).length) data.pins = pins;\n const m: MachineNode = { id: uid(\"m\"), type: \"machine\", position, data };\n layout.machines.set(name, m);\n layout.nodes.push(m);\n}\n\n/** Machines inside their first LAN, in a grid; routers under the LANs they join; the rest below. */\nfunction placeMachines(layout: Layout, inside: Map<string, string[]>): void {\n for (const [lanName, names] of inside) {\n const lan = layout.lans.get(lanName);\n if (!lan) continue;\n names.forEach((name, i) => {\n const at = { x: lan.position.x + 40 + (i % COLS) * CELL_W, y: lan.position.y + 70 + Math.floor(i / COLS) * CELL_H };\n addMachine(layout, name, layout.topo.nodes[name], at);\n });\n }\n const lanBottom = Math.max(0, ...[...layout.lans.values()].map((l) => l.height ?? 0));\n let routerX = 0;\n let looseX = 0;\n for (const [name, n] of Object.entries(layout.topo.nodes)) {\n if (layout.machines.has(name)) continue;\n const joined = lansOf(layout, n).flatMap((l) => layout.lans.get(l) ?? []);\n if (n.kind.router && joined.length) {\n const center = joined.reduce((s, l) => s + l.position.x + (l.width ?? 0) / 2, 0) / joined.length;\n routerX = Math.max(routerX, center - MACHINE_W / 2);\n addMachine(layout, name, n, { x: routerX, y: lanBottom + 110 });\n routerX += MACHINE_W + 60;\n } else {\n addMachine(layout, name, n, { x: looseX, y: lanBottom + 110 + CELL_H + 60 });\n looseX += CELL_W;\n }\n }\n}\n\n/** The side of a LAN that faces a machine, relative to the LAN's own proportions. */\nfunction side(lan: LanNode, m: MachineNode): Side {\n const d = { x: centerOf(m).x - centerOf(lan).x, y: centerOf(m).y - centerOf(lan).y };\n if (Math.abs(d.x) / (lan.width ?? 1) > Math.abs(d.y) / (lan.height ?? 1)) return d.x < 0 ? \"left\" : \"right\";\n return d.y < 0 ? \"top\" : \"bottom\";\n}\n\n/** Where a cable leaves a machine: an appliance's port, or the top or bottom facing the other end. */\nfunction handle(m: MachineNode, other: LabNode): string {\n if (m.data.icon === \"router\" || m.data.icon === \"shield\") return \"port\";\n return centerOf(other).y < centerOf(m).y ? \"top\" : \"bottom\";\n}\n\n/** A cable from each machine to each LAN it joins besides the one it sits in. */\nfunction cableToLans(layout: Layout): void {\n for (const [name, n] of Object.entries(layout.topo.nodes)) {\n const m = layout.machines.get(name);\n if (!m) continue;\n const skip = n.kind.router ? undefined : lansOf(layout, n)[0];\n for (const l of lansOf(layout, n).filter((x) => x !== skip)) {\n const lan = layout.lans.get(l);\n if (!lan) continue;\n layout.edges.push({\n id: uid(\"c\"),\n type: \"cable\",\n source: m.id,\n sourceHandle: handle(m, lan),\n target: lan.id,\n targetHandle: side(lan, m),\n data: { aPort: nextPort(m.id, layout.edges), bPort: \"\" },\n });\n }\n }\n}\n\n/** The links between machines, each with its ports and conditions. */\nfunction cableLinks(layout: Layout): void {\n for (const [i, link] of (layout.topo.links ?? []).entries()) {\n const a = layout.machines.get(link.a.node);\n const b = layout.machines.get(link.b.node);\n if (!a || !b) continue;\n layout.edges.push({\n id: uid(\"c\"),\n type: \"cable\",\n source: a.id,\n sourceHandle: handle(a, b),\n target: b.id,\n targetHandle: handle(b, a),\n data: {\n aPort: link.a.port || nextPort(a.id, layout.edges),\n bPort: link.b.port || nextPort(b.id, layout.edges),\n tc: conditionsAt(layout, `links[${String(i)}].tc`, link.tc),\n },\n });\n }\n}\n\nexport function draftFromTopology(topo: Topology): LaidOut {\n const layout: Layout = { topo, nodes: [], edges: [], lans: new Map(), machines: new Map(), problems: [] };\n const inside = residents(topo);\n placeLans(layout, inside);\n placeMachines(layout, inside);\n cableToLans(layout);\n cableLinks(layout);\n const draft = { name: cleanName(topo.name) || \"lab\", nodes: layout.nodes, edges: migrateEdges(layout.edges, layout.nodes) };\n return { draft, problems: layout.problems };\n}\n", "import { tcOf, type Tc } from \"./conditions\";\nimport { lansAround } from \"./geometry\";\nimport { idleByDefault, isLan, isMachine, isSwitch, type CableEdge, type LabNode, type MachineData } from \"./model\";\nimport { readPorts, type Service } from \"./ports\";\nimport { accessVlan, isTrunkPort, switchLans, vlanName } from \"./vlans\";\n\n/** The lab as the engine's topology JSON (`isothrum_compiler::Topology`). */\n\n/** Machine id -> the LANs it is on. */\ntype Membership = Map<string, Set<string>>;\n\nfunction join(membership: Membership, machine: string, lan: string): void {\n membership.set(machine, (membership.get(machine) ?? new Set()).add(lan));\n}\n\n/** A machine on a switch port joins the port's VLAN; on the trunk, every VLAN of the switch. */\nfunction joinSwitchPorts(membership: Membership, e: CableEdge, byId: Map<string, LabNode>): void {\n const a = byId.get(e.source);\n const b = byId.get(e.target);\n for (const [m, sw, handle] of [\n [a, b, e.targetHandle],\n [b, a, e.sourceHandle],\n ] as const) {\n if (!m || !sw || !isMachine(m) || !isSwitch(sw)) continue;\n if (isTrunkPort(sw, handle)) {\n for (const v of sw.data.vlans) join(membership, m.id, vlanName(v.id));\n } else {\n const vlan = accessVlan(sw, handle);\n if (vlan !== null) join(membership, m.id, vlanName(vlan));\n }\n }\n}\n\n/** A machine is on a LAN when it sits inside its area, or when a cable runs from it to the area. */\nfunction membershipOf(nodes: LabNode[], edges: CableEdge[], byId: Map<string, LabNode>): Membership {\n const membership: Membership = new Map();\n for (const n of nodes.filter(isMachine)) for (const l of lansAround(n, nodes)) join(membership, n.id, l.data.name);\n for (const e of edges) {\n const a = byId.get(e.source);\n const b = byId.get(e.target);\n if (a && b && isMachine(a) && isLan(b)) join(membership, a.id, b.data.name);\n if (a && b && isLan(a) && isMachine(b)) join(membership, b.id, a.data.name);\n joinSwitchPorts(membership, e, byId);\n }\n return membership;\n}\n\n/** What a machine runs, as the engine's `kind`. */\nfunction kindOf(m: MachineData) {\n if (m.kind === \"router\") return { router: {} };\n if (m.kind === \"container\") {\n return {\n container: m.appliance\n ? { image: m.image.trim(), appliance: m.appliance, ...(m.config?.trim() ? { config: m.config } : {}) }\n : { image: m.image.trim(), idle: m.idle ?? idleByDefault(m.image) },\n };\n }\n const provision = m.provision\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n return { vm: { os: m.os, provision } };\n}\n\n/** The ports in a machine's field that Isoloom takes (the others are reported: `canvasIssues`). */\nconst servicesOf = (m: MachineData): Service[] => readPorts(m.ports).services;\n\n/** Cables between two machines; a cable to a LAN is a membership, not a link. */\nfunction linksOf(edges: CableEdge[], byId: Map<string, LabNode>) {\n return edges.flatMap((e) => {\n const a = byId.get(e.source);\n const b = byId.get(e.target);\n if (!a || !b || !isMachine(a) || !isMachine(b) || !a.data.name || !b.data.name || !e.data) return [];\n return [{ a: { node: a.data.name, port: e.data.aPort.trim() }, b: { node: b.data.name, port: e.data.bPort.trim() }, tc: tcOf(e.data.tc) }];\n });\n}\n\ninterface Net {\n cidr: string;\n internet: boolean;\n tc?: Tc;\n switch?: string;\n vlan?: number;\n}\n\n/** The LAN areas, and each switch's VLANs, which nest under it in isoloom.yml (`sw1.vlan10`). */\nfunction networksOf(nodes: LabNode[], edges: CableEdge[]): Record<string, Net> {\n const lans = nodes.filter(isLan).filter((l) => l.data.name);\n const networks: Record<string, Net> = Object.fromEntries(\n lans.map((l) => [l.data.name, { cidr: l.data.cidr.trim(), internet: l.data.internet, tc: tcOf(l.data.tc) }]),\n );\n const nameOfGroup = switchLans(nodes, edges);\n for (const sw of nodes.filter(isSwitch)) {\n for (const v of sw.data.vlans) {\n const net = (networks[vlanName(v.id)] as Net | undefined) ?? { cidr: v.cidr.trim(), internet: true };\n networks[vlanName(v.id)] = { ...net, tc: undefined, switch: nameOfGroup(sw.id), vlan: v.id };\n }\n }\n return networks;\n}\n\nexport function toTopology(name: string, nodes: LabNode[], edges: CableEdge[]): string {\n const byId = new Map(nodes.map((n) => [n.id, n]));\n const membership = membershipOf(nodes, edges, byId);\n const machines = Object.fromEntries(\n nodes\n .filter(isMachine)\n .filter((n) => n.data.name)\n .map(({ id, data: m }) => {\n const networks = Object.fromEntries([...(membership.get(id) ?? [])].map((lan) => [lan, m.pins?.[lan] ?? null]));\n return [m.name, { kind: kindOf(m), access: m.access, services: servicesOf(m), networks }];\n }),\n );\n const links = linksOf(edges, byId);\n return JSON.stringify({ name, nodes: machines, networks: networksOf(nodes, edges), links });\n}\n", "import type { XYPosition } from \"@xyflow/react\";\nimport { LAN_BEHAVIOR, LAN_H, LAN_W, freeName, isLan, uid, type LabNode, type LanData, type LanNode, type MachineData, type MachineNode, type NoteNode, type SwitchNode } from \"./model\";\nimport type { Preset } from \"./presets\";\n\n/** New nodes, as the toolbar places them. */\n\nexport function newNote(position: XYPosition): NoteNode {\n return { id: uid(\"n\"), type: \"note\", position, width: 200, height: 90, selected: true, data: { text: \"\", shape: \"text\", color: \"yellow\" } };\n}\n\n/** vlan10 and vlan20: a new switch's VLANs (ports 1\u20134 and 5\u20138, both on the trunk). */\nexport function twoVlans(): SwitchNode[\"data\"] {\n return {\n name: \"\",\n vlans: [\n { id: 10, cidr: \"10.10.10.0/24\" },\n { id: 20, cidr: \"10.10.20.0/24\" },\n ],\n ports: [10, 10, 10, 10, 20, 20, 20, 20],\n };\n}\n\n/** A new switch with vlan10 on ports 1\u20134, vlan20 on ports 5\u20138, both on the trunk. */\nexport function newSwitch(center: XYPosition, nodes: LabNode[]): SwitchNode {\n return {\n id: uid(\"s\"),\n type: \"switch\",\n position: { x: center.x - 170, y: center.y - 40 },\n data: { ...twoVlans(), name: freeName(\"sw\", nodes) },\n };\n}\n\nexport function newMachine(preset: Preset, position: XYPosition, nodes: LabNode[]): MachineNode {\n const name = freeName(preset.prefix, nodes);\n const data: MachineData = { ...preset.data, name };\n // Isoloom needs a VM's provisioning steps, except on the access machine (the runner supplies it).\n if (data.kind === \"vm\" && !data.access && !data.provision) {\n data.provision = `provision/${name}.${data.os.startsWith(\"windows\") ? \"ps1\" : \"sh\"}`;\n }\n return { id: uid(\"m\"), type: \"machine\", position, data };\n}\n\n/** The third number of each LAN's 10.10.<n>.0/24 block. */\nexport function usedThirds(nodes: LabNode[]): Set<number> {\n return new Set(\n nodes\n .filter(isLan)\n .map((n) => /^10\\.10\\.(\\d+)\\./.exec(n.data.cidr)?.[1])\n .filter(Boolean)\n .map(Number),\n );\n}\n\n/** A new LAN centered on `center`, with the next free 10.10.<n>.0/24. */\nexport function newLan(center: XYPosition, nodes: LabNode[]): LanNode {\n const used = usedThirds(nodes);\n let third = 1;\n while (used.has(third)) third++;\n return {\n ...LAN_BEHAVIOR,\n id: uid(\"l\"),\n type: \"lan\",\n position: { x: center.x - LAN_W / 2, y: center.y - LAN_H / 2 },\n width: LAN_W,\n height: LAN_H,\n data: { name: freeName(\"lan\", nodes), cidr: `10.10.${String(third)}.0/24`, internet: true },\n };\n}\n\n/**\n * A LAN that hangs under a device placed at `center` (a router or a switch): the device sits on\n * its top edge, toward the right, so the LAN's label (top left) stays clear and clickable.\n */\nexport function lanBelow(center: XYPosition, nodes: LabNode[], data?: Partial<LanData>, size = { w: LAN_W, h: LAN_H }): LanNode {\n const lan = newLan({ x: center.x, y: center.y + size.h / 2 }, nodes);\n return {\n ...lan,\n width: size.w,\n height: size.h,\n position: { x: center.x - size.w * 0.74, y: center.y },\n data: { ...lan.data, ...data },\n };\n}\n", "import type { Appliance } from \"./model\";\n\n/**\n * The OSes a machine can be, as Isoloom writes them (`<family>@<version>`): exactly Isoloom's list,\n * which its engine gives (`choices_json`, checked by the tests). A machine of one of them is its\n * OS, set up by its provisioning steps; each target decides whether it runs as a VM or as a\n * container built from the OS's image.\n */\nexport const OS_CHOICES = [\n \"debian@11\",\n \"debian@12\",\n \"debian@13\",\n \"ubuntu@14.04\",\n \"ubuntu@16.04\",\n \"ubuntu@18.04\",\n \"ubuntu@20.04\",\n \"ubuntu@22.04\",\n \"ubuntu@24.04\",\n \"rocky@9\",\n \"alma@9\",\n \"alpine@3.19\",\n \"kali@rolling\",\n \"fedora@42\",\n \"centos@7\",\n \"windows@10\",\n \"windows@11\",\n \"windows-server@2008r2\",\n \"windows-server@2012r2\",\n \"windows-server@2016\",\n \"windows-server@2019\",\n \"windows-server@2022\",\n \"windows-server@2025\",\n];\n\n/** The OS a new VM gets. */\nexport const DEFAULT_OS = \"debian@12\";\n\n/**\n * An OS as a lab stored before Isoloom's new format named it (its image-table name: `debian-12`,\n * `almalinux-9`, `kali`, `windows-server-2022`), in today's spelling; any other value as it is.\n */\nexport function currentOs(os: string): string {\n if (os === \"kali\") return \"kali@rolling\";\n const m = /^([a-z-]+?)-(\\d[\\da-z.]*)$/.exec(os);\n if (!m || os.includes(\"@\")) return os;\n const family = m[1] === \"almalinux\" ? \"alma\" : m[1];\n return `${family}@${m[2]}`;\n}\n\n/** The Cisco OSes stored under the id the editor gave them before Isoloom's (`cisco-viosl2`). */\nconst OLD_APPLIANCES: ReadonlyMap<string, Appliance> = new Map([[\"cisco-viosl2\", \"cisco-vios-l2\"]]);\n\n/** A machine as a stored lab or device holds it, in today's spelling: its OS and its Cisco OS. */\nexport function currentMachine<M extends { os: string; appliance?: Appliance }>(m: M): M {\n const appliance = m.appliance === undefined ? undefined : (OLD_APPLIANCES.get(m.appliance) ?? m.appliance);\n return { ...m, os: currentOs(m.os), ...(appliance === undefined ? {} : { appliance }) };\n}\n", "import { twoVlans } from \"./create\";\nimport { DEFAULT_OS } from \"./os\";\nimport { LAN_BEHAVIOR, MACHINE_H, MACHINE_W, type CableEdge, type Draft, type LabNode, type LanData, type LanNode, type MachineData, type MachineNode } from \"./model\";\n\n/** The labs the menu loads: one to start from, and one with VLANs. */\n\nconst base = { image: \"\", os: \"\", provision: \"\", ports: \"\", access: false };\n\nconst machine = (id: string, position: { x: number; y: number }, data: MachineData): MachineNode => ({ id, type: \"machine\", position, data });\n\nconst lan = (id: string, area: { x: number; y: number; w: number; h: number }, data: LanData): LanNode => ({\n ...LAN_BEHAVIOR,\n id,\n type: \"lan\",\n position: { x: area.x, y: area.y },\n width: area.w,\n height: area.h,\n data,\n});\n\n/** A cable from `source` to `target`, its handles and port labels as [source's, target's]. */\nconst cable = (id: string, [source, target]: [string, string], [sh, th]: [string, string], [aPort, bPort]: [string, string]): CableEdge => ({\n id,\n type: \"cable\",\n source,\n sourceHandle: sh,\n target,\n targetHandle: th,\n data: { aPort, bPort },\n});\n\n/**\n * A starting lab: an office LAN with a workstation, a servers LAN with a web server and a\n * database, and a router cabled to both LANs.\n */\nexport function exampleLab(): Draft {\n const nodes: LabNode[] = [\n // LANs first, so they are drawn under the machines.\n lan(\"l-office\", { x: -40, y: 40, w: 280, h: 260 }, { name: \"office\", cidr: \"10.10.1.0/24\", internet: true }),\n lan(\"l-servers\", { x: 560, y: -80, w: 300, h: 500 }, { name: \"servers\", cidr: \"10.10.2.0/24\", internet: true }),\n machine(\"m-user\", { x: 40, y: 110 }, { ...base, name: \"user\", kind: \"vm\", icon: \"laptop\", os: DEFAULT_OS, access: true }),\n // On the office LAN's right edge: a two-network appliance, office on one side, its port out.\n machine(\"m-router\", { x: 240 - MACHINE_W / 2, y: 170 - MACHINE_H / 2 }, { ...base, name: \"router\", kind: \"router\", icon: \"router\" }),\n machine(\"m-web\", { x: 650, y: 0 }, { ...base, name: \"web\", kind: \"container\", icon: \"server\", image: \"nginx:1.27-alpine\", ports: \"80\" }),\n machine(\"m-db\", { x: 650, y: 240 }, { ...base, name: \"db\", kind: \"vm\", icon: \"monitor\", os: DEFAULT_OS, provision: \"provision/db.sh\", ports: \"5432\" }),\n ];\n const edges = [cable(\"c-1\", [\"m-router\", \"l-servers\"], [\"port\", \"left\"], [\"eth1\", \"\"])];\n return { name: \"office-lab\", nodes, edges };\n}\n\n/**\n * The VLAN example: a switch with vlan10 and vlan20, two PCs on vlan10, a server on vlan20, and a\n * router on the trunk (router-on-a-stick: one address on each VLAN).\n */\nexport function vlanExample(): Draft {\n const nodes: LabNode[] = [\n { id: \"s-sw1\", type: \"switch\", position: { x: 40, y: 0 }, data: { ...twoVlans(), name: \"sw1\" } },\n machine(\"m-router\", { x: 180, y: -230 }, { ...base, name: \"router\", kind: \"router\", icon: \"router\" }),\n machine(\"m-pc1\", { x: -140, y: 220 }, { ...base, name: \"pc1\", kind: \"vm\", icon: \"laptop\", os: DEFAULT_OS, access: true }),\n machine(\"m-pc2\", { x: 40, y: 220 }, { ...base, name: \"pc2\", kind: \"container\", icon: \"container\", image: \"alpine:3.20\" }),\n machine(\"m-srv\", { x: 300, y: 220 }, { ...base, name: \"srv\", kind: \"container\", icon: \"server\", image: \"nginx:1.27-alpine\", ports: \"80\" }),\n ];\n const onSwitch = (id: string, m: string, mh: string, port: string, labels: [string, string]) => cable(id, [m, \"s-sw1\"], [mh, port], labels);\n const edges = [\n onSwitch(\"c-1\", \"m-router\", \"port\", \"trunk\", [\"eth0\", \"T\"]),\n onSwitch(\"c-2\", \"m-pc1\", \"top\", \"p1\", [\"eth0\", \"1\"]),\n onSwitch(\"c-3\", \"m-pc2\", \"top\", \"p2\", [\"eth0\", \"2\"]),\n onSwitch(\"c-4\", \"m-srv\", \"top\", \"p5\", [\"eth0\", \"5\"]),\n ];\n return { name: \"vlan-lab\", nodes, edges };\n}\n", "import type { Addresses } from \"./router-ports\";\n\n/**\n * The engine's JavaScript surface, from `public/wasm/isothrum_wasm.js` (built from\n * `crates/isothrum-wasm`). Everything runs in the browser: nothing is sent to a server.\n */\nexport interface Engine {\n default: () => Promise<unknown>;\n plan_json: (topology: string) => string;\n bundle: (topology: string) => Uint8Array;\n /** The lab's own files (provisioning starters, appliance configurations), as JSON. */\n own_files_json: (topology: string) => string;\n bundle_target: (topology: string, targetDir: string) => Uint8Array;\n}\n\nexport interface GeneratedFile {\n path: string;\n contents: string;\n}\n\n/** What the engine makes of a lab: isoloom.yml and every target's files, or what is wrong. */\nexport interface Plan {\n ok: boolean;\n error: string | null;\n isoloom_yml: string | null;\n problems: string[];\n files: GeneratedFile[];\n skipped: string[];\n addresses: Addresses;\n}\n\n/** Served from `public/wasm/`; loaded at runtime, not bundled. */\nconst ENGINE_URL: string = \"/wasm/isothrum_wasm.js\";\n\n/** Loads and starts the engine. */\nexport async function loadEngine(): Promise<Engine> {\n const m = (await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ ENGINE_URL)) as Engine;\n await m.default();\n return m;\n}\n\n/** The plan as the engine writes it: isoloom.yml may be left out when there is none. */\ntype RawPlan = Omit<Plan, \"isoloom_yml\"> & { isoloom_yml?: string | null };\n\n/** The engine's plan for a topology; an engine crash becomes a plan with `error` (from `failed`). */\nexport function planOf(engine: Engine, topology: string, failed: (error: string) => string): Plan {\n try {\n const raw = JSON.parse(engine.plan_json(topology)) as RawPlan;\n return { ...raw, isoloom_yml: raw.isoloom_yml ?? null };\n } catch (e) {\n return { ok: false, error: failed(String(e)), isoloom_yml: null, problems: [], files: [], skipped: [], addresses: {} };\n }\n}\n\n/** The lab's own files (provisioning starters, appliance configurations). */\nexport function ownFiles(engine: Engine, topology: string): GeneratedFile[] {\n return JSON.parse(engine.own_files_json(topology)) as GeneratedFile[];\n}\n", "import type { GeneratedFile } from \"./engine\";\n\n/**\n * The Isoloom outputs in a lab's zip, as the Export window lists them: how each is named for\n * people, the one Isoloom command that starts it (from the unzipped folder), and the same thing\n * with the tool itself.\n */\n\n/** The words the names take (the editor's text). */\nexport interface TargetText {\n containersAndVms: string;\n oneVmPerMachine: string;\n dockerOnOneVm: string;\n}\n\nexport interface Target {\n dir: string;\n files: GeneratedFile[];\n name: string;\n isoloom: string | null;\n run: string;\n}\n\nconst CLOUDS: Partial<Record<string, string>> = {\n aws: \"AWS\",\n azure: \"Azure\",\n gcp: \"Google Cloud\",\n digitalocean: \"DigitalOcean\",\n linode: \"Linode\",\n oci: \"Oracle Cloud\",\n};\n\ntype Described = Pick<Target, \"name\" | \"isoloom\" | \"run\">;\n\n/** A cloud output (one folder per cloud): Docker on one VM, or one VM per machine. */\nfunction cloudTarget(target: string, cloud: string | undefined, terraform: string, t: TargetText): Described {\n const where = CLOUDS[cloud ?? \"\"] ?? String(cloud);\n if (target === \"cloud-docker\") return { name: `${where} (${t.dockerOnOneVm})`, isoloom: `isoloom run cloud-docker --cloud ${String(cloud)}`, run: terraform };\n return { name: `${where} (${t.oneVmPerMachine})`, isoloom: `isoloom run cloud-vm --cloud ${String(cloud)}`, run: terraform };\n}\n\nfunction describe(dir: string, t: TargetText): Described {\n const [target, cloud] = dir.split(\"/\") as [string, string | undefined];\n const terraform = `cd .isoloom/${dir} && terraform init && terraform apply`;\n switch (target) {\n case \"docker\":\n return { name: \"Docker Compose\", isoloom: \"isoloom run docker\", run: \"docker compose -f .isoloom/docker/compose.yml up -d\" };\n case \"hybrid\":\n return { name: `Vagrant (${t.containersAndVms})`, isoloom: \"isoloom run hybrid\", run: \"cd .isoloom/hybrid && vagrant up\" };\n case \"vagrant\":\n return { name: `Vagrant (${t.oneVmPerMachine})`, isoloom: \"isoloom run vagrant\", run: \"cd .isoloom/vagrant && vagrant up\" };\n case \"docker-vm\":\n return cloud === \"proxmox\"\n ? { name: `Proxmox (${t.dockerOnOneVm})`, isoloom: null, run: terraform }\n : { name: `Vagrant (${t.dockerOnOneVm})`, isoloom: \"isoloom run docker-vm\", run: \"cd .isoloom/docker-vm && vagrant up\" };\n case \"proxmox\":\n return { name: `Proxmox (${t.oneVmPerMachine})`, isoloom: \"isoloom run proxmox\", run: terraform };\n case \"kubernetes\":\n return { name: \"Kubernetes\", isoloom: \"isoloom run kubernetes\", run: \"kubectl kustomize --load-restrictor LoadRestrictionsNone .isoloom/kubernetes | kubectl apply -f -\" };\n case \"cloud-docker\":\n case \"cloud-vm\":\n return cloudTarget(target, cloud, terraform, t);\n default:\n return { name: dir, isoloom: null, run: \"\" };\n }\n}\n\n/** The folder a generated file belongs to: `<target>`, or `<target>/<cloud>` for a cloud output and docker-vm's Proxmox module. */\nfunction folderOf(path: string): string {\n const parts = path.replace(/^\\.isoloom\\//, \"\").split(\"/\");\n // Every other subfolder (checks/, \u2026) belongs to its target.\n const deeper = parts.length > 2 && (parts[0] === \"cloud-docker\" || parts[0] === \"cloud-vm\" || (parts[0] === \"docker-vm\" && parts[1] === \"proxmox\"));\n return deeper ? `${parts[0]}/${parts[1]}` : parts[0];\n}\n\n/** Generated files grouped by where they run: `.isoloom/<target>[/<cloud>]/\u2026`. */\nexport function groupByTarget(files: GeneratedFile[], t: TargetText): Target[] {\n const groups = new Map<string, GeneratedFile[]>();\n for (const f of files) {\n const dir = folderOf(f.path);\n groups.set(dir, [...(groups.get(dir) ?? []), f]);\n }\n return [...groups.entries()].map(([dir, fs]) => ({ dir, files: fs, ...describe(dir, t) }));\n}\n", "import { isLan, isMachine, isSwitch, type LabNode } from \"./model\";\nimport { readPorts } from \"./ports\";\nimport { vlanName } from \"./vlans\";\n\n/** The editor's words for what the canvas finds wrong (see the editor's text). */\nexport interface IssueText {\n vlanBlocks: (vlan: string, block: string, other: string, sw: string) => string;\n areaBlock: (lan: string, block: string, switches: string) => string;\n noName: Record<\"machine\" | \"lan\" | \"switch\", string>;\n sameName: Record<\"machine\" | \"lan\" | \"switch\", (name: string) => string>;\n /** A ports field entry Isoloom couldn't take: the machine and the entry. */\n badPort: (machine: string, entry: string) => string;\n}\n\n/** One VLAN, one block: every switch (and a LAN area of that name) must agree on it. */\nfunction blockIssues(nodes: LabNode[], text: IssueText): string[] {\n const issues: string[] = [];\n const blocks = new Map<string, string>();\n for (const sw of nodes.filter(isSwitch)) {\n for (const v of sw.data.vlans) {\n const name = vlanName(v.id);\n const seen = blocks.get(name);\n if (seen && seen !== v.cidr) issues.push(text.vlanBlocks(name, seen, v.cidr, sw.data.name));\n blocks.set(name, v.cidr);\n }\n }\n for (const l of nodes.filter(isLan)) {\n const seen = blocks.get(l.data.name);\n if (seen && seen !== l.data.cidr) issues.push(text.areaBlock(l.data.name, l.data.cidr, seen));\n }\n return issues;\n}\n\n/** Names Isoloom would reject (none) or merge (the same twice), per kind of node. */\nfunction nameIssues(nodes: LabNode[], text: IssueText): string[] {\n const issues: string[] = [];\n for (const [what, list] of [\n [\"machine\", nodes.filter(isMachine)],\n [\"lan\", nodes.filter(isLan)],\n [\"switch\", nodes.filter(isSwitch)],\n ] as const) {\n const seen = new Set<string>();\n for (const n of list) {\n if (!n.data.name) issues.push(text.noName[what]);\n else if (seen.has(n.data.name)) issues.push(text.sameName[what](n.data.name));\n seen.add(n.data.name);\n }\n }\n return issues;\n}\n\n/** Port entries Isoloom couldn't take (not a port, or not one of its protocols): left out of the topology, so said here. */\nfunction portIssues(nodes: LabNode[], text: IssueText): string[] {\n return nodes.filter(isMachine).flatMap((m) => readPorts(m.data.ports).bad.map((entry) => text.badPort(m.data.name, entry)));\n}\n\n/** What the canvas can tell before the engine runs: names Isoloom would reject or merge, ports it couldn't take. */\nexport function canvasIssues(nodes: LabNode[], text: IssueText): string[] {\n return [...blockIssues(nodes, text), ...nameIssues(nodes, text), ...portIssues(nodes, text)];\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["side", "handle", "lan", "machine", "lan", "handle"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type EdgeProps } from "@xyflow/react";
|
|
2
|
+
import type { CableEdge } from "../../../lib/tool/model.js";
|
|
3
|
+
/** A cable: a hand-drawn line with the port name next to each end, as in GNS3. */
|
|
4
|
+
export declare function CableEdgeView(p: EdgeProps<CableEdge>): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { LiveView } from "../../../lib/tool/live.js";
|
|
2
|
+
import type { Side } from "../../../lib/tool/model.js";
|
|
3
|
+
import type { RouterPorts } from "../../../lib/tool/router-ports.js";
|
|
4
|
+
/** What the canvas's nodes and cables read from the editor, without props through React Flow. */
|
|
5
|
+
/** Each machine's addresses (from the engine), by machine name: [network, IPv4][]. */
|
|
6
|
+
export declare const AddressesContext: import("react").Context<Partial<Record<string, [string, string][]>>>;
|
|
7
|
+
/** Each real router's interfaces, by node id. */
|
|
8
|
+
export declare const PortsContext: import("react").Context<Partial<Record<string, RouterPorts>>>;
|
|
9
|
+
/** The running lab as the desktop app reads it (null: not running, or in a browser). */
|
|
10
|
+
export declare const LiveContext: import("react").Context<LiveView | null>;
|
|
11
|
+
/** The LAN border each appliance sits on, by node id (so its port can face out), and the side each switch or router port faces, by `<id>:<port>`. */
|
|
12
|
+
export declare const SidesContext: import("react").Context<Partial<Record<string, Side>>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MachineData } from "../../../lib/tool/model.js";
|
|
2
|
+
/** What a machine is for, as people say it: its role in the lab. */
|
|
3
|
+
export declare function roleOf(m: Pick<MachineData, "kind" | "icon" | "image">): "router" | "firewall" | "workstation" | "server" | "desktop" | "host";
|
|
4
|
+
/** Business: a device's accent, by what it is (the pastels' strong counterparts). */
|
|
5
|
+
export declare function accentFor(m: Pick<MachineData, "kind" | "icon">): string;
|
|
6
|
+
/** Each kind of device has its pastel, as on a whiteboard. */
|
|
7
|
+
export declare function pastelFor(m: Pick<MachineData, "kind" | "icon">): string;
|
|
8
|
+
/**
|
|
9
|
+
* A device's picture: the logo of what it runs (its OS or image) when we know one, otherwise
|
|
10
|
+
* a generic icon. Routers and workstations keep their role icon, with the OS logo as a badge.
|
|
11
|
+
*/
|
|
12
|
+
export declare function DeviceIcon({ data, size, accent, }: {
|
|
13
|
+
data: Pick<MachineData, "kind" | "image" | "os" | "icon">;
|
|
14
|
+
size?: "sm" | "md";
|
|
15
|
+
/** Business: the tile takes this tint, its glyph this color. */
|
|
16
|
+
accent?: string;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface InlineTextProps {
|
|
2
|
+
value: string;
|
|
3
|
+
onCommit: (v: string) => void;
|
|
4
|
+
clean?: (v: string) => string;
|
|
5
|
+
className?: string;
|
|
6
|
+
label: string;
|
|
7
|
+
/** Clearing the field means something (e.g. "back to automatic"), so an empty value counts. */
|
|
8
|
+
allowEmpty?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Text edited in place, as in Excalidraw: double-click to edit, Enter or a click elsewhere to
|
|
12
|
+
* keep, Escape to drop. `nodrag` so typing never moves the node.
|
|
13
|
+
*/
|
|
14
|
+
export declare function InlineText({ value, onCommit, clean, className, label, allowEmpty }: InlineTextProps): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type NodeProps } from "@xyflow/react";
|
|
2
|
+
import { type LanNode } from "../../../lib/tool/model.js";
|
|
3
|
+
/**
|
|
4
|
+
* A LAN on the canvas: a sketched, dashed area behind the machines, with its name and block.
|
|
5
|
+
* Machines inside it are on it; a cable from a machine to its edge puts that machine on it too
|
|
6
|
+
* (how a router joins several LANs). Resizable from its corners when selected.
|
|
7
|
+
*/
|
|
8
|
+
export declare function LanNodeView({ id, data, selected, width, height }: NodeProps<LanNode>): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MachineData } from "../../../lib/tool/model.js";
|
|
2
|
+
/**
|
|
3
|
+
* Business: what an asset card says about a machine, in labelled rows: its addresses (two at
|
|
4
|
+
* most, then how many more), and what it runs (its image or OS).
|
|
5
|
+
*/
|
|
6
|
+
export declare function MachineFacts({ id, data }: {
|
|
7
|
+
id: string;
|
|
8
|
+
data: MachineData;
|
|
9
|
+
}): import("react").JSX.Element | null;
|
|
10
|
+
/** The machine's IPs, under its box, as on a network diagram. */
|
|
11
|
+
export declare function MachineAddresses({ id, data }: {
|
|
12
|
+
id: string;
|
|
13
|
+
data: MachineData;
|
|
14
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type NodeProps } from "@xyflow/react";
|
|
2
|
+
import { type MachineNode } from "../../../lib/tool/model.js";
|
|
3
|
+
/** A machine on the canvas: a hand-drawn box in its pastel, its logo and name, a socket per side. */
|
|
4
|
+
export declare function MachineNodeView(props: NodeProps<MachineNode>): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type NodeProps } from "@xyflow/react";
|
|
2
|
+
import { type NoteNode } from "../../../lib/tool/model.js";
|
|
3
|
+
/**
|
|
4
|
+
* A note: text on the canvas, bare or in a sketched box or ellipse, to explain the lab. It stays
|
|
5
|
+
* in the editor (the draft, share links) and never reaches isoloom.yml.
|
|
6
|
+
*/
|
|
7
|
+
export declare function NoteNodeView({ id, data, selected, width, height }: NodeProps<NoteNode>): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business: a socket's status light, in its VLAN's or network's color (a trunk's stripes), lit
|
|
3
|
+
* when something is assigned to the port; dark when nothing is.
|
|
4
|
+
*/
|
|
5
|
+
export declare function PortLed({ color }: {
|
|
6
|
+
color: string | null;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
/** The look of a port's square: business sockets are dark and recessed. */
|
|
9
|
+
export declare const PORT_BOX = "rounded-[3px] border border-foreground/40";
|
|
10
|
+
export declare const BUSINESS_SOCKET = "rounded-[2px] border-black/80 shadow-[inset_0_1px_3px_rgb(0_0_0/0.7)]";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type NodeProps } from "@xyflow/react";
|
|
2
|
+
import { type MachineNode } from "../../../lib/tool/model.js";
|
|
3
|
+
/**
|
|
4
|
+
* A router you configure, drawn like a switch: its interfaces eth0–eth3, each in the color of the
|
|
5
|
+
* network plugged into it, and beside it the list of interfaces with their addresses. A cable
|
|
6
|
+
* plugs into one port; on a LAN's edge, eth0 is that LAN.
|
|
7
|
+
*/
|
|
8
|
+
export declare function RouterFaceplate({ id, data, selected }: NodeProps<MachineNode>): import("react").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Design } from "../../../lib/tool/model.js";
|
|
2
|
+
/**
|
|
3
|
+
* The whiteboard look: boxes and cables drawn by rough.js (the library behind Excalidraw), kept
|
|
4
|
+
* gentle so the diagram stays clear. Each shape has a fixed seed, so its wobble doesn't change
|
|
5
|
+
* from one render to the next.
|
|
6
|
+
*/
|
|
7
|
+
export declare const gen: import("roughjs/bin/generator").RoughGenerator;
|
|
8
|
+
export declare const SKETCH: {
|
|
9
|
+
roughness: number;
|
|
10
|
+
bowing: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The canvas's look: the hand-drawn sketch (default), or business: crisp single lines, the plain
|
|
14
|
+
* sans, muted fills, for a diagram that goes into a report. The fonts and colors switch through
|
|
15
|
+
* CSS (`data-design` on the canvas); the strokes switch here.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DesignContext: import("react").Context<Design>;
|
|
18
|
+
/** The stroke options for this design (rough.js draws both: at roughness 0 a line is straight). */
|
|
19
|
+
export declare function useSketch(): {
|
|
20
|
+
roughness: number;
|
|
21
|
+
bowing: number;
|
|
22
|
+
};
|
|
23
|
+
/** Whether the canvas is in the business look. */
|
|
24
|
+
export declare function useBusiness(): boolean;
|
|
25
|
+
export declare function seedOf(id: string): number;
|
|
26
|
+
export type Drawable = ReturnType<typeof gen.rectangle>;
|
|
27
|
+
/** rough.js output as SVG paths; "F" and "S" stand for the fill and stroke we style with CSS. */
|
|
28
|
+
export declare function Sketch({ drawable, fill, stroke, opacity, dash, }: {
|
|
29
|
+
drawable: Drawable;
|
|
30
|
+
fill?: string;
|
|
31
|
+
stroke: string;
|
|
32
|
+
opacity?: number;
|
|
33
|
+
/** rough.js returns the path only: a dash pattern has to be applied here. */
|
|
34
|
+
dash?: string;
|
|
35
|
+
}): import("react").JSX.Element;
|
|
36
|
+
interface FrameProps {
|
|
37
|
+
drawable: Drawable;
|
|
38
|
+
fill: string;
|
|
39
|
+
selected: boolean;
|
|
40
|
+
dim?: boolean;
|
|
41
|
+
/** Business: network equipment, drawn as a dark faceplate instead of a card. */
|
|
42
|
+
hardware?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A node's frame. Sketch: the hand-drawn shape in its pastel. Business: a card, as in a network
|
|
46
|
+
* diagram tool: rounded corners, a hairline border, a soft shadow (or, for network equipment, a
|
|
47
|
+
* dark faceplate).
|
|
48
|
+
*/
|
|
49
|
+
export declare function Frame({ drawable, fill, selected, dim, hardware }: FrameProps): import("react").JSX.Element;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type NodeProps } from "@xyflow/react";
|
|
2
|
+
import { type SwitchNode } from "../../../lib/tool/model.js";
|
|
3
|
+
/**
|
|
4
|
+
* A switch, drawn as its faceplate: numbered access ports in their VLAN's color (click one to
|
|
5
|
+
* change its VLAN), and a trunk striped with every VLAN it carries. A cable plugs into a port.
|
|
6
|
+
* Its VLANs are listed under it: double-click to edit, + to add, × to remove.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SwitchNodeView({ id, data, selected }: NodeProps<SwitchNode>): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which way each port of node `id` faces (`<id>:<port>` in the sides), as one key; React Flow
|
|
3
|
+
* caches where handles are, so they are measured again when it changes.
|
|
4
|
+
*/
|
|
5
|
+
export declare function usePortSides(id: string): Partial<Record<string, import("../../../model").Side>>;
|
|
6
|
+
/** Measures node `id`'s handles again whenever `key` changes. */
|
|
7
|
+
export declare function useRemeasure(id: string, key: string | undefined): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Block } from "../../lib/tool/blocks.js";
|
|
2
|
+
/**
|
|
3
|
+
* A toolbar block: one button that places the kind picked last (drag it, or click then click
|
|
4
|
+
* the canvas), and an arrow listing every kind with what it is and its key.
|
|
5
|
+
*/
|
|
6
|
+
export declare function DeviceBlock({ block, choice, active, onPick, onForget }: {
|
|
7
|
+
block: Block;
|
|
8
|
+
choice: string | undefined;
|
|
9
|
+
active: string | null;
|
|
10
|
+
onPick: (tool: string) => void;
|
|
11
|
+
onForget: (id: string) => void;
|
|
12
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Design } from "../../lib/tool/model.js";
|
|
2
|
+
/** Signed in: saving to and opening from the user's projects. */
|
|
3
|
+
interface ProjectActions {
|
|
4
|
+
onSave: () => void;
|
|
5
|
+
onSaveAs: () => void;
|
|
6
|
+
onOpen: () => void;
|
|
7
|
+
}
|
|
8
|
+
interface EditorMenuProps {
|
|
9
|
+
lang: string;
|
|
10
|
+
onExample: () => void;
|
|
11
|
+
onVlanExample: () => void;
|
|
12
|
+
onShare: () => void;
|
|
13
|
+
onImportGns3: () => void;
|
|
14
|
+
design: Design;
|
|
15
|
+
onDesign: (d: Design) => void;
|
|
16
|
+
accounts: boolean;
|
|
17
|
+
projects: ProjectActions | null;
|
|
18
|
+
onClear: () => void;
|
|
19
|
+
onFit: () => void;
|
|
20
|
+
canClear: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Reports whether someone is signed in (mounted only where accounts exist). */
|
|
23
|
+
export declare function SessionWatch({ onChange }: {
|
|
24
|
+
onChange: (signedIn: boolean) => void;
|
|
25
|
+
}): null;
|
|
26
|
+
/** The editor's menu (top left): examples, projects, files, the look, the language. */
|
|
27
|
+
export declare function EditorMenu(p: EditorMenuProps): import("react").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function CopyButton({ text, label }: {
|
|
2
|
+
text: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
/** A command to copy, under its label. */
|
|
6
|
+
export declare function Command({ label, text }: {
|
|
7
|
+
label: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GeneratedFile } from "../../../lib/tool/engine.js";
|
|
2
|
+
interface ExportPanelProps {
|
|
3
|
+
labName: string;
|
|
4
|
+
onLabNameChange: (name: string) => void;
|
|
5
|
+
/** Containers and VMs together: only Vagrant can run it. */
|
|
6
|
+
mixed: boolean;
|
|
7
|
+
spec: string | null;
|
|
8
|
+
files: GeneratedFile[];
|
|
9
|
+
issues: string[];
|
|
10
|
+
onDownloadAll: () => void;
|
|
11
|
+
onDownloadSpec: () => void;
|
|
12
|
+
/** One target's zip: isoloom.yml and that target's folder. */
|
|
13
|
+
onDownloadTarget: (dir: string) => void;
|
|
14
|
+
}
|
|
15
|
+
/** What the Export window shows: the lab's name, the downloads, where it runs and how. */
|
|
16
|
+
export declare function ExportPanel(p: ExportPanelProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Target } from "../../../lib/tool/export-targets.js";
|
|
2
|
+
/** Which target and which file are open: kept by the Export window, so they stay open while it is. */
|
|
3
|
+
export interface Opened {
|
|
4
|
+
target: string | null;
|
|
5
|
+
setTarget: (dir: string | null) => void;
|
|
6
|
+
file: string | null;
|
|
7
|
+
setFile: (path: string | null) => void;
|
|
8
|
+
}
|
|
9
|
+
/** Every target the zip holds, one open at a time. */
|
|
10
|
+
export declare function TargetList({ targets, ready, opened, onDownloadTarget }: {
|
|
11
|
+
targets: Target[];
|
|
12
|
+
ready: boolean;
|
|
13
|
+
opened: Opened;
|
|
14
|
+
onDownloadTarget: (dir: string) => void;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A window floating over the canvas: drag it by its title bar, close it with the cross. It opens
|
|
4
|
+
* against the left or right edge and stays inside the screen while dragged.
|
|
5
|
+
*/
|
|
6
|
+
export declare function FloatingWindow({ title, side, width, onClose, children, className }: {
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
side: "left" | "right";
|
|
9
|
+
width: number;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "@xyflow/react/dist/style.css";
|
|
2
|
+
import type { EditorModel } from "./hooks/use-editor";
|
|
3
|
+
/** The canvas: React Flow with the lab's nodes and cables, in Excalidraw's two modes. */
|
|
4
|
+
export declare function FlowCanvas({ e }: {
|
|
5
|
+
e: EditorModel;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Connection, type NodeChange } from "@xyflow/react";
|
|
2
|
+
import { canConnect, type LabIndex } from "../../../lib/tool/cables.js";
|
|
3
|
+
import { type CableEdge, type LabNode, type LanNode } from "../../../lib/tool/model.js";
|
|
4
|
+
import type { Lab } from "./use-lab";
|
|
5
|
+
/** The ids of the LANs, switches and real routers, and the LANs themselves. */
|
|
6
|
+
export declare function useLabIndex(nodes: LabNode[]): LabIndex & {
|
|
7
|
+
lans: LanNode[];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Drawing and moving cables. Machine to machine, LAN or port; never LAN to LAN or switch to LAN;
|
|
11
|
+
* a switch or router port takes one cable. Cables can be moved: drag either end onto another
|
|
12
|
+
* machine, LAN or port, or onto the canvas to unplug it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useCabling(lab: Lab, index: LabIndex & {
|
|
15
|
+
lans: LanNode[];
|
|
16
|
+
}): {
|
|
17
|
+
onNodesChange: (changes: NodeChange<LabNode>[]) => void;
|
|
18
|
+
isValid: (c: Parameters<typeof canConnect>[0]) => boolean;
|
|
19
|
+
onConnect: (c: Connection) => void;
|
|
20
|
+
onReconnect: (old: CableEdge, c: Connection) => void;
|
|
21
|
+
onReconnectStart: (_: unknown, edge: CableEdge) => void;
|
|
22
|
+
onReconnectEnd: () => void;
|
|
23
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Invoke } from "../../../lib/tool/desktop.js";
|
|
2
|
+
/** Captures in progress (desktop): network -> the .pcap file, or null when Wireshark shows it live. */
|
|
3
|
+
export declare function useCaptures(invoke: Invoke | null, labName: string, onError: (error: string) => void): {
|
|
4
|
+
captures: Record<string, string | null>;
|
|
5
|
+
capture: (network: string) => Promise<void>;
|
|
6
|
+
stopCapture: (network: string) => Promise<void>;
|
|
7
|
+
clearCaptures: () => void;
|
|
8
|
+
};
|
|
9
|
+
export type Captures = ReturnType<typeof useCaptures>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Lab } from "./use-lab";
|
|
2
|
+
type DeleteNodes = (elements: {
|
|
3
|
+
nodes: {
|
|
4
|
+
id: string;
|
|
5
|
+
}[];
|
|
6
|
+
}) => unknown;
|
|
7
|
+
/**
|
|
8
|
+
* Copy, cut, paste and duplicate (Ctrl/⌘ C, X, V, D). Copies get new names and ids, a LAN its own
|
|
9
|
+
* block; LANs go first in the list so they stay under the machines.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useClipboard(lab: Lab, deleteElements: DeleteNodes): void;
|
|
12
|
+
export {};
|