@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.
Files changed (293) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/PUBLISHING.md +93 -0
  4. package/README.md +106 -2
  5. package/desktop-editor/editor.css +11 -0
  6. package/desktop-editor/index.html +14 -0
  7. package/desktop-editor/main.tsx +26 -0
  8. package/dist/desktop/editor.css +1 -0
  9. package/dist/desktop/editor.js +181 -0
  10. package/dist/desktop/fonts/geist-cyrillic-ext-wght-normal-RO72NYA2.woff2 +0 -0
  11. package/dist/desktop/fonts/geist-cyrillic-wght-normal-CK2XY27K.woff2 +0 -0
  12. package/dist/desktop/fonts/geist-latin-ext-wght-normal-XEQ56JUF.woff2 +0 -0
  13. package/dist/desktop/fonts/geist-latin-wght-normal-T72VVCUW.woff2 +0 -0
  14. package/dist/desktop/fonts/geist-mono-cyrillic-ext-wght-normal-QXKVIKFD.woff2 +0 -0
  15. package/dist/desktop/fonts/geist-mono-cyrillic-wght-normal-GOIAQAGU.woff2 +0 -0
  16. package/dist/desktop/fonts/geist-mono-latin-ext-wght-normal-HU3ZAWN6.woff2 +0 -0
  17. package/dist/desktop/fonts/geist-mono-latin-wght-normal-CJNQKXUG.woff2 +0 -0
  18. package/dist/desktop/fonts/geist-mono-symbols2-wght-normal-WZJJVLQO.woff2 +0 -0
  19. package/dist/desktop/fonts/geist-mono-vietnamese-wght-normal-KW5NM6YV.woff2 +0 -0
  20. package/dist/desktop/fonts/geist-vietnamese-wght-normal-Y2R7JZKV.woff2 +0 -0
  21. package/dist/desktop/fonts/shantell-sans-cyrillic-ext-wght-normal-ILPQWRJJ.woff2 +0 -0
  22. package/dist/desktop/fonts/shantell-sans-cyrillic-wght-normal-RKC6NDPM.woff2 +0 -0
  23. package/dist/desktop/fonts/shantell-sans-latin-ext-wght-normal-SNKHG3LM.woff2 +0 -0
  24. package/dist/desktop/fonts/shantell-sans-latin-wght-normal-7CHHGC3N.woff2 +0 -0
  25. package/dist/desktop/fonts/shantell-sans-vietnamese-wght-normal-IPFAQSBA.woff2 +0 -0
  26. package/dist/desktop/index.html +14 -0
  27. package/dist/desktop/styles.css +2 -0
  28. package/dist/desktop/wasm/isothrum_wasm.d.ts +60 -0
  29. package/dist/desktop/wasm/isothrum_wasm.js +312 -0
  30. package/dist/desktop/wasm/isothrum_wasm_bg.wasm +0 -0
  31. package/dist/index.js +7059 -0
  32. package/dist/index.js.map +7 -0
  33. package/dist/model.js +745 -0
  34. package/dist/model.js.map +7 -0
  35. package/dist/types/components/tool/canvas/cable-edge.d.ts +4 -0
  36. package/dist/types/components/tool/canvas/contexts.d.ts +12 -0
  37. package/dist/types/components/tool/canvas/device-icon.d.ts +17 -0
  38. package/dist/types/components/tool/canvas/inline-text.d.ts +15 -0
  39. package/dist/types/components/tool/canvas/lan-node.d.ts +8 -0
  40. package/dist/types/components/tool/canvas/machine-addresses.d.ts +14 -0
  41. package/dist/types/components/tool/canvas/machine-node.d.ts +4 -0
  42. package/dist/types/components/tool/canvas/note-node.d.ts +7 -0
  43. package/dist/types/components/tool/canvas/port-led.d.ts +10 -0
  44. package/dist/types/components/tool/canvas/router-node.d.ts +8 -0
  45. package/dist/types/components/tool/canvas/sketch.d.ts +50 -0
  46. package/dist/types/components/tool/canvas/status-dot.d.ts +5 -0
  47. package/dist/types/components/tool/canvas/switch-node.d.ts +8 -0
  48. package/dist/types/components/tool/canvas/use-measured-ports.d.ts +7 -0
  49. package/dist/types/components/tool/context-menus.d.ts +5 -0
  50. package/dist/types/components/tool/device-block.d.ts +12 -0
  51. package/dist/types/components/tool/editor-menu.d.ts +28 -0
  52. package/dist/types/components/tool/export/copy-button.d.ts +9 -0
  53. package/dist/types/components/tool/export/export-panel.d.ts +17 -0
  54. package/dist/types/components/tool/export/target-list.d.ts +15 -0
  55. package/dist/types/components/tool/floating-window.d.ts +13 -0
  56. package/dist/types/components/tool/flow-canvas.d.ts +6 -0
  57. package/dist/types/components/tool/hooks/use-cabling.d.ts +23 -0
  58. package/dist/types/components/tool/hooks/use-captures.d.ts +9 -0
  59. package/dist/types/components/tool/hooks/use-clipboard.d.ts +12 -0
  60. package/dist/types/components/tool/hooks/use-context-menus.d.ts +25 -0
  61. package/dist/types/components/tool/hooks/use-design.d.ts +10 -0
  62. package/dist/types/components/tool/hooks/use-desktop.d.ts +50 -0
  63. package/dist/types/components/tool/hooks/use-devices.d.ts +17 -0
  64. package/dist/types/components/tool/hooks/use-downloads.d.ts +9 -0
  65. package/dist/types/components/tool/hooks/use-editor.d.ts +609 -0
  66. package/dist/types/components/tool/hooks/use-engine.d.ts +9 -0
  67. package/dist/types/components/tool/hooks/use-history.d.ts +9 -0
  68. package/dist/types/components/tool/hooks/use-lab-actions.d.ts +25 -0
  69. package/dist/types/components/tool/hooks/use-lab-run.d.ts +25 -0
  70. package/dist/types/components/tool/hooks/use-lab.d.ts +18 -0
  71. package/dist/types/components/tool/hooks/use-lan-carry.d.ts +8 -0
  72. package/dist/types/components/tool/hooks/use-machine-actions.d.ts +26 -0
  73. package/dist/types/components/tool/hooks/use-placement.d.ts +27 -0
  74. package/dist/types/components/tool/hooks/use-project.d.ts +25 -0
  75. package/dist/types/components/tool/hooks/use-projects.d.ts +34 -0
  76. package/dist/types/components/tool/hooks/use-shared-link.d.ts +23 -0
  77. package/dist/types/components/tool/hooks/use-toast.d.ts +2 -0
  78. package/dist/types/components/tool/hooks/use-tools.d.ts +17 -0
  79. package/dist/types/components/tool/lab-canvas.d.ts +18 -0
  80. package/dist/types/components/tool/overlays.d.ts +5 -0
  81. package/dist/types/components/tool/projects-list.d.ts +9 -0
  82. package/dist/types/components/tool/text.d.ts +418 -0
  83. package/dist/types/components/tool/toolbar.d.ts +5 -0
  84. package/dist/types/components/tool/top-bars.d.ts +10 -0
  85. package/dist/types/components/tool/windows/conditions-fields.d.ts +6 -0
  86. package/dist/types/components/tool/windows/editor-windows.d.ts +5 -0
  87. package/dist/types/components/tool/windows/fields.d.ts +4 -0
  88. package/dist/types/components/tool/windows/lab-status.d.ts +6 -0
  89. package/dist/types/components/tool/windows/lab-windows.d.ts +13 -0
  90. package/dist/types/components/tool/windows/link-controls.d.ts +21 -0
  91. package/dist/types/components/tool/windows/machine-controls.d.ts +16 -0
  92. package/dist/types/components/tool/windows/machine-inspector.d.ts +11 -0
  93. package/dist/types/components/tool/windows/runs-fields.d.ts +18 -0
  94. package/dist/types/components/tool/windows/selected-windows.d.ts +21 -0
  95. package/dist/types/components/ui/button.d.ts +8 -0
  96. package/dist/types/components/ui/dropdown-menu.d.ts +21 -0
  97. package/dist/types/index.d.ts +7 -0
  98. package/dist/types/lib/auth.d.ts +38 -0
  99. package/dist/types/lib/brand.d.ts +19 -0
  100. package/dist/types/lib/i18n.d.ts +19 -0
  101. package/dist/types/lib/tool/blocks.d.ts +35 -0
  102. package/dist/types/lib/tool/cables.d.ts +48 -0
  103. package/dist/types/lib/tool/clone.d.ts +12 -0
  104. package/dist/types/lib/tool/conditions.d.ts +31 -0
  105. package/dist/types/lib/tool/create.d.ts +22 -0
  106. package/dist/types/lib/tool/desktop.d.ts +63 -0
  107. package/dist/types/lib/tool/draft.d.ts +20 -0
  108. package/dist/types/lib/tool/engine.d.ts +33 -0
  109. package/dist/types/lib/tool/examples.d.ts +11 -0
  110. package/dist/types/lib/tool/export-targets.d.ts +21 -0
  111. package/dist/types/lib/tool/facing.d.ts +9 -0
  112. package/dist/types/lib/tool/geometry.d.ts +36 -0
  113. package/dist/types/lib/tool/gns3-cables.d.ts +17 -0
  114. package/dist/types/lib/tool/gns3-import.d.ts +15 -0
  115. package/dist/types/lib/tool/gns3-parts.d.ts +25 -0
  116. package/dist/types/lib/tool/issues.d.ts +12 -0
  117. package/dist/types/lib/tool/layout.d.ts +52 -0
  118. package/dist/types/lib/tool/live.d.ts +21 -0
  119. package/dist/types/lib/tool/logos.d.ts +7 -0
  120. package/dist/types/lib/tool/model.d.ts +173 -0
  121. package/dist/types/lib/tool/os.d.ts +20 -0
  122. package/dist/types/lib/tool/pins.d.ts +5 -0
  123. package/dist/types/lib/tool/placement.d.ts +21 -0
  124. package/dist/types/lib/tool/ports.d.ts +21 -0
  125. package/dist/types/lib/tool/presets.d.ts +21 -0
  126. package/dist/types/lib/tool/projects-api.d.ts +37 -0
  127. package/dist/types/lib/tool/router-ports.d.ts +22 -0
  128. package/dist/types/lib/tool/selection.d.ts +19 -0
  129. package/dist/types/lib/tool/storage.d.ts +25 -0
  130. package/dist/types/lib/tool/switch-edit.d.ts +17 -0
  131. package/dist/types/lib/tool/text/de.d.ts +2 -0
  132. package/dist/types/lib/tool/text/en.d.ts +429 -0
  133. package/dist/types/lib/tool/text/es.d.ts +2 -0
  134. package/dist/types/lib/tool/text/fr.d.ts +2 -0
  135. package/dist/types/lib/tool/text/index.d.ts +419 -0
  136. package/dist/types/lib/tool/topology.d.ts +2 -0
  137. package/dist/types/lib/tool/vlans.d.ts +24 -0
  138. package/dist/types/lib/utils.d.ts +2 -0
  139. package/dist/types/model.d.ts +13 -0
  140. package/package.json +88 -4
  141. package/scripts/build-desktop-editor.mjs +43 -0
  142. package/scripts/build-lib.mjs +38 -0
  143. package/src/components/tool/canvas/cable-edge.tsx +112 -0
  144. package/src/components/tool/canvas/contexts.ts +18 -0
  145. package/src/components/tool/canvas/device-icon.tsx +105 -0
  146. package/src/components/tool/canvas/inline-text.tsx +69 -0
  147. package/src/components/tool/canvas/lan-node.tsx +148 -0
  148. package/src/components/tool/canvas/machine-addresses.tsx +117 -0
  149. package/src/components/tool/canvas/machine-node.tsx +114 -0
  150. package/src/components/tool/canvas/note-node.tsx +109 -0
  151. package/src/components/tool/canvas/port-led.tsx +21 -0
  152. package/src/components/tool/canvas/router-node.tsx +114 -0
  153. package/src/components/tool/canvas/sketch.tsx +130 -0
  154. package/src/components/tool/canvas/status-dot.tsx +29 -0
  155. package/src/components/tool/canvas/switch-node.tsx +143 -0
  156. package/src/components/tool/canvas/use-measured-ports.ts +25 -0
  157. package/src/components/tool/context-menus.tsx +142 -0
  158. package/src/components/tool/device-block.tsx +107 -0
  159. package/src/components/tool/editor-menu.tsx +191 -0
  160. package/src/components/tool/export/copy-button.tsx +45 -0
  161. package/src/components/tool/export/export-panel.tsx +127 -0
  162. package/src/components/tool/export/target-list.tsx +87 -0
  163. package/src/components/tool/floating-window.tsx +69 -0
  164. package/src/components/tool/flow-canvas.tsx +138 -0
  165. package/src/components/tool/hooks/use-cabling.ts +66 -0
  166. package/src/components/tool/hooks/use-captures.ts +24 -0
  167. package/src/components/tool/hooks/use-clipboard.ts +54 -0
  168. package/src/components/tool/hooks/use-context-menus.ts +37 -0
  169. package/src/components/tool/hooks/use-design.ts +24 -0
  170. package/src/components/tool/hooks/use-desktop.ts +100 -0
  171. package/src/components/tool/hooks/use-devices.ts +58 -0
  172. package/src/components/tool/hooks/use-downloads.ts +42 -0
  173. package/src/components/tool/hooks/use-editor.ts +114 -0
  174. package/src/components/tool/hooks/use-engine.ts +29 -0
  175. package/src/components/tool/hooks/use-history.ts +107 -0
  176. package/src/components/tool/hooks/use-lab-actions.ts +57 -0
  177. package/src/components/tool/hooks/use-lab-run.ts +61 -0
  178. package/src/components/tool/hooks/use-lab.ts +32 -0
  179. package/src/components/tool/hooks/use-lan-carry.ts +43 -0
  180. package/src/components/tool/hooks/use-machine-actions.ts +57 -0
  181. package/src/components/tool/hooks/use-placement.ts +80 -0
  182. package/src/components/tool/hooks/use-project.ts +112 -0
  183. package/src/components/tool/hooks/use-projects.ts +85 -0
  184. package/src/components/tool/hooks/use-shared-link.ts +64 -0
  185. package/src/components/tool/hooks/use-toast.ts +16 -0
  186. package/src/components/tool/hooks/use-tools.ts +60 -0
  187. package/src/components/tool/lab-canvas.tsx +96 -0
  188. package/src/components/tool/overlays.tsx +104 -0
  189. package/src/components/tool/projects-list.tsx +96 -0
  190. package/src/components/tool/text.ts +9 -0
  191. package/src/components/tool/toolbar.tsx +106 -0
  192. package/src/components/tool/top-bars.tsx +90 -0
  193. package/src/components/tool/windows/conditions-fields.tsx +43 -0
  194. package/src/components/tool/windows/editor-windows.tsx +73 -0
  195. package/src/components/tool/windows/fields.ts +6 -0
  196. package/src/components/tool/windows/lab-status.tsx +68 -0
  197. package/src/components/tool/windows/lab-windows.tsx +130 -0
  198. package/src/components/tool/windows/link-controls.tsx +69 -0
  199. package/src/components/tool/windows/machine-controls.tsx +116 -0
  200. package/src/components/tool/windows/machine-inspector.tsx +83 -0
  201. package/src/components/tool/windows/runs-fields.tsx +127 -0
  202. package/src/components/tool/windows/selected-windows.tsx +147 -0
  203. package/src/components/ui/button.tsx +59 -0
  204. package/src/components/ui/dropdown-menu.tsx +185 -0
  205. package/src/index.ts +21 -0
  206. package/src/lib/auth.tsx +43 -0
  207. package/src/lib/brand.tsx +46 -0
  208. package/src/lib/i18n.ts +53 -0
  209. package/src/lib/tool/blocks.test.ts +64 -0
  210. package/src/lib/tool/blocks.ts +78 -0
  211. package/src/lib/tool/cable-wiring.test.ts +74 -0
  212. package/src/lib/tool/cables.test.ts +75 -0
  213. package/src/lib/tool/cables.ts +215 -0
  214. package/src/lib/tool/clone.test.ts +54 -0
  215. package/src/lib/tool/clone.ts +48 -0
  216. package/src/lib/tool/conditions.test.ts +170 -0
  217. package/src/lib/tool/conditions.ts +132 -0
  218. package/src/lib/tool/create.test.ts +67 -0
  219. package/src/lib/tool/create.ts +83 -0
  220. package/src/lib/tool/desktop.test.ts +59 -0
  221. package/src/lib/tool/desktop.ts +99 -0
  222. package/src/lib/tool/draft.test.ts +150 -0
  223. package/src/lib/tool/draft.ts +109 -0
  224. package/src/lib/tool/engine.test.ts +39 -0
  225. package/src/lib/tool/engine.ts +58 -0
  226. package/src/lib/tool/examples.test.ts +35 -0
  227. package/src/lib/tool/examples.ts +71 -0
  228. package/src/lib/tool/export-targets.test.ts +47 -0
  229. package/src/lib/tool/export-targets.ts +84 -0
  230. package/src/lib/tool/facing.test.ts +72 -0
  231. package/src/lib/tool/facing.ts +100 -0
  232. package/src/lib/tool/geometry.test.ts +80 -0
  233. package/src/lib/tool/geometry.ts +109 -0
  234. package/src/lib/tool/gns3-cables.test.ts +47 -0
  235. package/src/lib/tool/gns3-cables.ts +81 -0
  236. package/src/lib/tool/gns3-import.test.ts +102 -0
  237. package/src/lib/tool/gns3-import.ts +198 -0
  238. package/src/lib/tool/gns3-layout.test.ts +62 -0
  239. package/src/lib/tool/gns3-parts.test.ts +47 -0
  240. package/src/lib/tool/gns3-parts.ts +68 -0
  241. package/src/lib/tool/issues.test.ts +35 -0
  242. package/src/lib/tool/issues.ts +60 -0
  243. package/src/lib/tool/layout.test.ts +128 -0
  244. package/src/lib/tool/layout.ts +229 -0
  245. package/src/lib/tool/live.test.ts +46 -0
  246. package/src/lib/tool/live.ts +49 -0
  247. package/src/lib/tool/logos.test.ts +30 -0
  248. package/src/lib/tool/logos.ts +99 -0
  249. package/src/lib/tool/model.test.ts +128 -0
  250. package/src/lib/tool/model.ts +238 -0
  251. package/src/lib/tool/os.test.ts +45 -0
  252. package/src/lib/tool/os.ts +57 -0
  253. package/src/lib/tool/parity.test.ts +145 -0
  254. package/src/lib/tool/pins.test.ts +24 -0
  255. package/src/lib/tool/pins.ts +11 -0
  256. package/src/lib/tool/placement.test.ts +79 -0
  257. package/src/lib/tool/placement.ts +60 -0
  258. package/src/lib/tool/ports.test.ts +39 -0
  259. package/src/lib/tool/ports.ts +48 -0
  260. package/src/lib/tool/presets.test.ts +50 -0
  261. package/src/lib/tool/presets.ts +77 -0
  262. package/src/lib/tool/projects-api.test.ts +59 -0
  263. package/src/lib/tool/projects-api.ts +47 -0
  264. package/src/lib/tool/router-ports.test.ts +63 -0
  265. package/src/lib/tool/router-ports.ts +88 -0
  266. package/src/lib/tool/selection.test.ts +44 -0
  267. package/src/lib/tool/selection.ts +34 -0
  268. package/src/lib/tool/storage.test.ts +123 -0
  269. package/src/lib/tool/storage.ts +127 -0
  270. package/src/lib/tool/switch-edit.test.ts +49 -0
  271. package/src/lib/tool/switch-edit.ts +38 -0
  272. package/src/lib/tool/text/de.ts +358 -0
  273. package/src/lib/tool/text/en.ts +371 -0
  274. package/src/lib/tool/text/es.ts +358 -0
  275. package/src/lib/tool/text/fr.ts +358 -0
  276. package/src/lib/tool/text/index.ts +21 -0
  277. package/src/lib/tool/text/text.test.ts +33 -0
  278. package/src/lib/tool/topology.test.ts +108 -0
  279. package/src/lib/tool/topology.ts +116 -0
  280. package/src/lib/tool/vlans.test.ts +64 -0
  281. package/src/lib/tool/vlans.ts +79 -0
  282. package/src/lib/utils.ts +6 -0
  283. package/src/model.ts +13 -0
  284. package/src/test/arbitrary.ts +41 -0
  285. package/src/test/engine.ts +48 -0
  286. package/src/test/lab.ts +101 -0
  287. package/styles/globals.css +258 -0
  288. package/styles/theme.css +192 -0
  289. package/tsconfig.build.json +12 -0
  290. package/tsconfig.json +22 -0
  291. package/wasm/isothrum_wasm.d.ts +60 -0
  292. package/wasm/isothrum_wasm.js +312 -0
  293. package/wasm/isothrum_wasm_bg.wasm +0 -0
package/dist/model.js ADDED
@@ -0,0 +1,745 @@
1
+ // src/lib/tool/model.ts
2
+ var APPLIANCES = [
3
+ { id: "cisco-iol", label: "IOL (IOS on Linux)", image: "vrnetlab/cisco_iol:17.12.01" },
4
+ { id: "cisco-iol-l2", label: "IOL-L2 (switch)", image: "vrnetlab/cisco_iol:L2-17.12.01", switch: true },
5
+ { id: "cisco-vios", label: "IOSv", image: "vrnetlab/cisco_vios:15.9.3M6", kvm: true },
6
+ { id: "cisco-vios-l2", label: "IOSvL2 (switch)", image: "vrnetlab/cisco_viosl2:15.2", switch: true, kvm: true },
7
+ { id: "cisco-csr1000v", label: "CSR1000v (IOS XE)", image: "vrnetlab/cisco_csr1000v:17.03.08", kvm: true },
8
+ { id: "cisco-c8000v", label: "Catalyst 8000v (IOS XE)", image: "vrnetlab/cisco_c8000v:17.15.01a", kvm: true },
9
+ { id: "cisco-dynamips", label: "Dynamips 7200 (classic IOS)", image: "" }
10
+ ];
11
+ function applianceInterface(kind, k) {
12
+ switch (kind) {
13
+ case "cisco-iol":
14
+ case "cisco-iol-l2":
15
+ return `Ethernet${String(Math.floor(k / 4))}/${String(k % 4)}`;
16
+ case "cisco-vios":
17
+ case "cisco-vios-l2":
18
+ return `GigabitEthernet0/${String(k)}`;
19
+ case "cisco-csr1000v":
20
+ case "cisco-c8000v":
21
+ return `GigabitEthernet${String(k + 1)}`;
22
+ case "cisco-dynamips":
23
+ return k === 1 ? "FastEthernet0/0" : `FastEthernet${String(Math.floor(k / 2))}/${String(k % 2)}`;
24
+ }
25
+ }
26
+ var STOCK_IMAGES = ["alpine", "ubuntu", "debian", "kali-rolling", "fedora", "rockylinux", "almalinux", "centos", "busybox", "archlinux", "netshoot"];
27
+ function imageName(image) {
28
+ return image.trim().split(":")[0].split("/").pop()?.toLowerCase() ?? "";
29
+ }
30
+ function idleByDefault(image) {
31
+ return STOCK_IMAGES.includes(imageName(image));
32
+ }
33
+ var NOTE_COLORS = ["yellow", "red", "blue", "green", "violet"];
34
+ var isMachine = (n) => n.type === "machine";
35
+ var isRealRouter = (n) => isMachine(n) && n.data.icon === "router" && n.data.kind !== "router";
36
+ var ROUTER_PORTS = 4;
37
+ var ROUTER_W = 168;
38
+ var ROUTER_H = 74;
39
+ var routerPort = (i) => `eth${String(i)}`;
40
+ var isAppliance = (n) => isMachine(n) && (n.data.icon === "router" || n.data.icon === "shield");
41
+ var isLan = (n) => n.type === "lan";
42
+ var isSwitch = (n) => n.type === "switch";
43
+ var isNote = (n) => n.type === "note";
44
+ var nodeName = (n) => isNote(n) ? "note" : n.data.name;
45
+ var SWITCH_PORTS = 8;
46
+ var SWITCH_W = 306;
47
+ var SWITCH_H = 74;
48
+ var TRUNK = "trunk";
49
+ var MACHINE_W = 120;
50
+ var MACHINE_H = 116;
51
+ var LAN_W = 420;
52
+ var LAN_H = 300;
53
+ function sizeOf(n) {
54
+ if (n.type === "lan") return { w: n.width ?? LAN_W, h: n.height ?? LAN_H };
55
+ if (n.type === "switch") return { w: SWITCH_W, h: SWITCH_H };
56
+ if (n.type === "note") return { w: n.width ?? 200, h: n.height ?? 90 };
57
+ if (n.data.icon === "router" && n.data.kind !== "router") return { w: ROUTER_W, h: ROUTER_H };
58
+ return { w: MACHINE_W, h: MACHINE_H };
59
+ }
60
+ function centerOf(n, fallback = { w: MACHINE_W, h: MACHINE_H }) {
61
+ return {
62
+ x: n.position.x + (n.width ?? n.measured?.width ?? fallback.w) / 2,
63
+ y: n.position.y + (n.height ?? n.measured?.height ?? fallback.h) / 2
64
+ };
65
+ }
66
+ var LAN_BEHAVIOR = { dragHandle: ".lan-handle", className: "pointer-events-none" };
67
+ var DESIGNS = ["sketch", "business"];
68
+ function cleanName(name) {
69
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "");
70
+ }
71
+ function uid(prefix) {
72
+ return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`;
73
+ }
74
+ function freeName(prefix, nodes) {
75
+ const taken = new Set(nodes.map(nodeName));
76
+ for (let i = 1; ; i++) {
77
+ if (!taken.has(`${prefix}${String(i)}`)) return `${prefix}${String(i)}`;
78
+ }
79
+ }
80
+ function unselected(items) {
81
+ return items.map((n) => ({ ...n, selected: false }));
82
+ }
83
+
84
+ // src/lib/tool/geometry.ts
85
+ var ON = 0.5;
86
+ function lanRect(l) {
87
+ return { x: l.position.x, y: l.position.y, w: l.width ?? LAN_W, h: l.height ?? LAN_H };
88
+ }
89
+ function within(r, p, slack = 0) {
90
+ 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;
91
+ }
92
+ function sideOf(r, p) {
93
+ const alongX = p.x >= r.x && p.x <= r.x + r.w;
94
+ const alongY = p.y >= r.y && p.y <= r.y + r.h;
95
+ if (alongX && Math.abs(p.y - r.y) < ON) return "top";
96
+ if (alongX && Math.abs(p.y - (r.y + r.h)) < ON) return "bottom";
97
+ if (alongY && Math.abs(p.x - r.x) < ON) return "left";
98
+ if (alongY && Math.abs(p.x - (r.x + r.w)) < ON) return "right";
99
+ return null;
100
+ }
101
+ function drawnCenter(m) {
102
+ const { w, h } = sizeOf(m);
103
+ return { x: m.position.x + w / 2, y: m.position.y + h / 2 };
104
+ }
105
+ function sideOnLan(m, lans) {
106
+ const c = drawnCenter(m);
107
+ for (const l of lans) {
108
+ const side2 = sideOf(lanRect(l), c);
109
+ if (side2) return side2;
110
+ }
111
+ return null;
112
+ }
113
+ function lanUnder(m, lans) {
114
+ for (const l of lans) {
115
+ const side2 = sideOnLan(m, [l]);
116
+ if (side2) return { lan: l, side: side2 };
117
+ }
118
+ return null;
119
+ }
120
+ function lansAround(m, nodes) {
121
+ const c = drawnCenter(m);
122
+ const onEdge = isAppliance(m) ? lanUnder(m, nodes.filter(isLan))?.lan.id : void 0;
123
+ return nodes.filter(isLan).filter((l) => (!l.data.hidden || l.id === onEdge) && within(lanRect(l), c));
124
+ }
125
+
126
+ // src/lib/tool/vlans.ts
127
+ var vlanName = (id) => `vlan${String(id)}`;
128
+ function switchPort(handle2) {
129
+ if (handle2 === TRUNK) return TRUNK;
130
+ const m = handle2 ? /^p(\d+)$/.exec(handle2) : null;
131
+ return m ? Number(m[1]) - 1 : null;
132
+ }
133
+ function isTrunkPort(sw, handle2) {
134
+ const port = switchPort(handle2);
135
+ return port === TRUNK || typeof port === "number" && sw.data.ports[port] === TRUNK;
136
+ }
137
+ function accessVlan(sw, handle2) {
138
+ const port = switchPort(handle2);
139
+ const mode = typeof port === "number" ? sw.data.ports[port] : null;
140
+ return typeof mode === "number" ? mode : null;
141
+ }
142
+ function switchLans(nodes, edges) {
143
+ const switches = nodes.filter(isSwitch);
144
+ const group = new Map(switches.map((s) => [s.id, s.id]));
145
+ const root = (id) => {
146
+ const up = group.get(id);
147
+ return up === void 0 || up === id ? id : root(up);
148
+ };
149
+ const unite = (a, b) => group.set(root(a), root(b));
150
+ for (const e of edges) if (group.has(e.source) && group.has(e.target)) unite(e.source, e.target);
151
+ const firstWith = /* @__PURE__ */ new Map();
152
+ for (const sw of switches) {
153
+ for (const v of sw.data.vlans) {
154
+ const other = firstWith.get(v.id);
155
+ if (other) unite(sw.id, other);
156
+ else firstWith.set(v.id, sw.id);
157
+ }
158
+ }
159
+ return (id) => switches.find((s) => root(s.id) === root(id))?.data.name ?? "";
160
+ }
161
+
162
+ // src/lib/tool/cables.ts
163
+ function machineHandle(m, old) {
164
+ if (isAppliance(m)) return "port";
165
+ return old === "bottom" ? "bottom" : "top";
166
+ }
167
+ var isPort = (h) => !!h && /^eth\d+$/.test(h);
168
+ function takenPorts(edges, byId) {
169
+ const taken = /* @__PURE__ */ new Map();
170
+ for (const e of edges) {
171
+ for (const [id, h] of [
172
+ [e.source, e.sourceHandle],
173
+ [e.target, e.targetHandle]
174
+ ]) {
175
+ const n = byId.get(id);
176
+ if (n && isRealRouter(n) && isPort(h)) taken.set(id, (taken.get(id) ?? /* @__PURE__ */ new Set()).add(h));
177
+ }
178
+ }
179
+ return taken;
180
+ }
181
+ function migrateEdges(edges, nodes) {
182
+ const byId = new Map(nodes.map((n) => [n.id, n]));
183
+ const taken = takenPorts(edges, byId);
184
+ const freePort = (id) => {
185
+ const used = taken.get(id) ?? /* @__PURE__ */ new Set();
186
+ for (let i = 0; i < 64; i++) {
187
+ if (!used.has(routerPort(i))) {
188
+ used.add(routerPort(i));
189
+ taken.set(id, used);
190
+ return routerPort(i);
191
+ }
192
+ }
193
+ return routerPort(0);
194
+ };
195
+ const fix = (id, h) => {
196
+ const n = byId.get(id);
197
+ if (n && isRealRouter(n)) return isPort(h) ? h : freePort(id);
198
+ return n && isMachine(n) ? machineHandle(n, h) : h;
199
+ };
200
+ return edges.map((e) => ({ ...e, sourceHandle: fix(e.source, e.sourceHandle), targetHandle: fix(e.target, e.targetHandle) }));
201
+ }
202
+ function usedPorts(nodeId, edges) {
203
+ return edges.flatMap((e) => [...e.source === nodeId && e.data ? [e.data.aPort] : [], ...e.target === nodeId && e.data ? [e.data.bPort] : []]);
204
+ }
205
+ function nextPort(nodeId, edges, first = 0) {
206
+ const used = new Set(usedPorts(nodeId, edges));
207
+ for (let i = first; ; i++) {
208
+ if (!used.has(`eth${String(i)}`)) return `eth${String(i)}`;
209
+ }
210
+ }
211
+
212
+ // src/lib/tool/conditions.ts
213
+ var isSet = (n) => typeof n === "number" && Number.isFinite(n);
214
+ var whole = (n) => Math.abs(n - Math.round(n)) < 1e-9;
215
+ function rateText(rate) {
216
+ if (Number.isInteger(rate)) return `${String(rate)}mbit`;
217
+ if (whole(rate * 1e3)) return `${String(Math.round(rate * 1e3))}kbit`;
218
+ return `${String(rate * 1e6)}bit`;
219
+ }
220
+ function tcOf(c) {
221
+ if (!c) return void 0;
222
+ const tc = {
223
+ ...isSet(c.delay) ? { delay: `${String(c.delay)}ms` } : {},
224
+ ...isSet(c.jitter) ? { jitter: `${String(c.jitter)}ms` } : {},
225
+ ...isSet(c.loss) ? { loss: c.loss } : {},
226
+ ...isSet(c.rate) ? { rate: rateText(c.rate) } : {}
227
+ };
228
+ return Object.keys(tc).length ? tc : void 0;
229
+ }
230
+ var TIME = /* @__PURE__ */ new Map([
231
+ ["us", [1, 1e3]],
232
+ ["ms", [1, 1]],
233
+ ["s", [1e3, 1]]
234
+ ]);
235
+ var RATE = /* @__PURE__ */ new Map([
236
+ ["bit", [1, 1e6]],
237
+ ["kbit", [1, 1e3]],
238
+ ["mbit", [1, 1]],
239
+ ["gbit", [1e3, 1]],
240
+ ["bps", [8, 1e6]],
241
+ ["kbps", [8, 1e3]],
242
+ ["mbps", [8, 1]],
243
+ ["gbps", [8e3, 1]]
244
+ ]);
245
+ var UNITS = { delay: TIME, jitter: TIME, rate: RATE };
246
+ var MEASURE = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)([A-Za-z]*)$/;
247
+ function measure(v, units) {
248
+ const m = MEASURE.exec(v);
249
+ const scale = m ? units.get(m[2]) : void 0;
250
+ if (!m || !scale) return void 0;
251
+ const n = Number(m[1]) * scale[0] / scale[1];
252
+ return Number.isFinite(n) ? n : void 0;
253
+ }
254
+ function conditionsOf(tc) {
255
+ const conditions = {};
256
+ const problems = [];
257
+ for (const field of ["delay", "jitter", "rate"]) {
258
+ const value = tc?.[field];
259
+ if (value === void 0) continue;
260
+ const n = measure(value, UNITS[field]);
261
+ if (n === void 0) problems.push({ field, value });
262
+ else conditions[field] = n;
263
+ }
264
+ if (tc?.loss !== void 0) conditions.loss = tc.loss;
265
+ return { conditions: Object.keys(conditions).length ? conditions : void 0, problems };
266
+ }
267
+ var EXPECTED = { delay: "a time like 40ms, 500us or 1s", jitter: "a time like 40ms, 500us or 1s", rate: "a rate like 10mbit or 512kbit" };
268
+ function tcProblemText(at, p) {
269
+ return `${at}.${p.field}: \`${p.value}\` isn't ${EXPECTED[p.field]}; the drawing leaves it out`;
270
+ }
271
+
272
+ // src/lib/tool/ports.ts
273
+ var PORT_PROTOCOLS = ["tcp", "udp", "http", "https", "tls"];
274
+ var ENTRY = /^(\d{1,5})(?:\/([a-z]+))?$/;
275
+ var isProtocol = (p) => PORT_PROTOCOLS.some((x) => x === p);
276
+ function service(entry) {
277
+ const m = ENTRY.exec(entry);
278
+ if (!m) return null;
279
+ const [, digits, protocol = "tcp"] = m;
280
+ const port = Number(digits);
281
+ if (port < 1 || port > 65535) return null;
282
+ if (protocol === "tcp") return { port };
283
+ return isProtocol(protocol) ? { port, protocol } : null;
284
+ }
285
+ function readPorts(text) {
286
+ const services = [];
287
+ const bad = [];
288
+ for (const entry of text.split(",").map((e) => e.trim()).filter(Boolean)) {
289
+ const s = service(entry);
290
+ if (s) services.push(s);
291
+ else bad.push(entry);
292
+ }
293
+ return { services, bad };
294
+ }
295
+ function portsText(services) {
296
+ return services.map((s) => s.protocol && s.protocol !== "tcp" ? `${String(s.port)}/${s.protocol}` : String(s.port)).join(", ");
297
+ }
298
+
299
+ // src/lib/tool/layout.ts
300
+ var COLS = 3;
301
+ var CELL_W = 170;
302
+ var CELL_H = 175;
303
+ var GAP = 160;
304
+ function icon(kind, access) {
305
+ if (kind.router) return "router";
306
+ if (kind.vm) return access ? "laptop" : "monitor";
307
+ const image = kind.container?.image ?? "";
308
+ return /nginx|httpd|apache|caddy|traefik|postgres|mysql|mariadb|redis|mongo|bind|dnsmasq|vsftpd|postfix/.test(image) ? "server" : "container";
309
+ }
310
+ function conditionsAt(layout, at, tc) {
311
+ const { conditions, problems } = conditionsOf(tc);
312
+ layout.problems.push(...problems.map((p) => tcProblemText(at, p)));
313
+ return conditions;
314
+ }
315
+ var lansOf = (layout, n) => Object.keys(n.networks ?? {}).filter((name) => layout.lans.has(name));
316
+ function residents(topo) {
317
+ const lanNames = Object.keys(topo.networks ?? {});
318
+ const inside = new Map(lanNames.map((l) => [l, []]));
319
+ for (const [name, n] of Object.entries(topo.nodes)) {
320
+ const first = Object.keys(n.networks ?? {}).find((l) => lanNames.includes(l));
321
+ if (!n.kind.router && first) inside.get(first)?.push(name);
322
+ }
323
+ return inside;
324
+ }
325
+ function placeLans(layout, inside) {
326
+ let x = 0;
327
+ for (const [name, net] of Object.entries(layout.topo.networks ?? {})) {
328
+ const count = Math.max(1, inside.get(name)?.length ?? 0);
329
+ const w = Math.max(340, Math.min(COLS, count) * CELL_W + 80);
330
+ const h = Math.max(240, Math.ceil(count / COLS) * CELL_H + 90);
331
+ const lan2 = {
332
+ ...LAN_BEHAVIOR,
333
+ id: uid("l"),
334
+ type: "lan",
335
+ position: { x, y: 0 },
336
+ width: w,
337
+ height: h,
338
+ data: { name: cleanName(name), cidr: net.cidr, internet: net.internet ?? true, tc: conditionsAt(layout, `networks.${name}.tc`, net.tc) }
339
+ };
340
+ layout.lans.set(name, lan2);
341
+ layout.nodes.push(lan2);
342
+ x += w + GAP;
343
+ }
344
+ }
345
+ function addMachine(layout, name, n, position) {
346
+ const access = !!n.access;
347
+ const data = {
348
+ name: cleanName(name),
349
+ kind: n.kind.router ? "router" : n.kind.vm ? "vm" : "container",
350
+ icon: icon(n.kind, access),
351
+ image: n.kind.container?.image ?? "",
352
+ os: n.kind.vm?.os ?? "",
353
+ provision: (n.kind.vm?.provision ?? []).join("\n"),
354
+ ports: portsText(n.services ?? []),
355
+ access,
356
+ ...n.kind.container ? { idle: n.kind.container.idle } : {}
357
+ };
358
+ const pins = Object.fromEntries(Object.entries(n.networks ?? {}).filter((e) => typeof e[1] === "number"));
359
+ if (Object.keys(pins).length) data.pins = pins;
360
+ const m = { id: uid("m"), type: "machine", position, data };
361
+ layout.machines.set(name, m);
362
+ layout.nodes.push(m);
363
+ }
364
+ function placeMachines(layout, inside) {
365
+ for (const [lanName, names] of inside) {
366
+ const lan2 = layout.lans.get(lanName);
367
+ if (!lan2) continue;
368
+ names.forEach((name, i) => {
369
+ const at = { x: lan2.position.x + 40 + i % COLS * CELL_W, y: lan2.position.y + 70 + Math.floor(i / COLS) * CELL_H };
370
+ addMachine(layout, name, layout.topo.nodes[name], at);
371
+ });
372
+ }
373
+ const lanBottom = Math.max(0, ...[...layout.lans.values()].map((l) => l.height ?? 0));
374
+ let routerX = 0;
375
+ let looseX = 0;
376
+ for (const [name, n] of Object.entries(layout.topo.nodes)) {
377
+ if (layout.machines.has(name)) continue;
378
+ const joined = lansOf(layout, n).flatMap((l) => layout.lans.get(l) ?? []);
379
+ if (n.kind.router && joined.length) {
380
+ const center = joined.reduce((s, l) => s + l.position.x + (l.width ?? 0) / 2, 0) / joined.length;
381
+ routerX = Math.max(routerX, center - MACHINE_W / 2);
382
+ addMachine(layout, name, n, { x: routerX, y: lanBottom + 110 });
383
+ routerX += MACHINE_W + 60;
384
+ } else {
385
+ addMachine(layout, name, n, { x: looseX, y: lanBottom + 110 + CELL_H + 60 });
386
+ looseX += CELL_W;
387
+ }
388
+ }
389
+ }
390
+ function side(lan2, m) {
391
+ const d = { x: centerOf(m).x - centerOf(lan2).x, y: centerOf(m).y - centerOf(lan2).y };
392
+ if (Math.abs(d.x) / (lan2.width ?? 1) > Math.abs(d.y) / (lan2.height ?? 1)) return d.x < 0 ? "left" : "right";
393
+ return d.y < 0 ? "top" : "bottom";
394
+ }
395
+ function handle(m, other) {
396
+ if (m.data.icon === "router" || m.data.icon === "shield") return "port";
397
+ return centerOf(other).y < centerOf(m).y ? "top" : "bottom";
398
+ }
399
+ function cableToLans(layout) {
400
+ for (const [name, n] of Object.entries(layout.topo.nodes)) {
401
+ const m = layout.machines.get(name);
402
+ if (!m) continue;
403
+ const skip = n.kind.router ? void 0 : lansOf(layout, n)[0];
404
+ for (const l of lansOf(layout, n).filter((x) => x !== skip)) {
405
+ const lan2 = layout.lans.get(l);
406
+ if (!lan2) continue;
407
+ layout.edges.push({
408
+ id: uid("c"),
409
+ type: "cable",
410
+ source: m.id,
411
+ sourceHandle: handle(m, lan2),
412
+ target: lan2.id,
413
+ targetHandle: side(lan2, m),
414
+ data: { aPort: nextPort(m.id, layout.edges), bPort: "" }
415
+ });
416
+ }
417
+ }
418
+ }
419
+ function cableLinks(layout) {
420
+ for (const [i, link] of (layout.topo.links ?? []).entries()) {
421
+ const a = layout.machines.get(link.a.node);
422
+ const b = layout.machines.get(link.b.node);
423
+ if (!a || !b) continue;
424
+ layout.edges.push({
425
+ id: uid("c"),
426
+ type: "cable",
427
+ source: a.id,
428
+ sourceHandle: handle(a, b),
429
+ target: b.id,
430
+ targetHandle: handle(b, a),
431
+ data: {
432
+ aPort: link.a.port || nextPort(a.id, layout.edges),
433
+ bPort: link.b.port || nextPort(b.id, layout.edges),
434
+ tc: conditionsAt(layout, `links[${String(i)}].tc`, link.tc)
435
+ }
436
+ });
437
+ }
438
+ }
439
+ function draftFromTopology(topo) {
440
+ const layout = { topo, nodes: [], edges: [], lans: /* @__PURE__ */ new Map(), machines: /* @__PURE__ */ new Map(), problems: [] };
441
+ const inside = residents(topo);
442
+ placeLans(layout, inside);
443
+ placeMachines(layout, inside);
444
+ cableToLans(layout);
445
+ cableLinks(layout);
446
+ const draft = { name: cleanName(topo.name) || "lab", nodes: layout.nodes, edges: migrateEdges(layout.edges, layout.nodes) };
447
+ return { draft, problems: layout.problems };
448
+ }
449
+
450
+ // src/lib/tool/topology.ts
451
+ function join(membership, machine2, lan2) {
452
+ membership.set(machine2, (membership.get(machine2) ?? /* @__PURE__ */ new Set()).add(lan2));
453
+ }
454
+ function joinSwitchPorts(membership, e, byId) {
455
+ const a = byId.get(e.source);
456
+ const b = byId.get(e.target);
457
+ for (const [m, sw, handle2] of [
458
+ [a, b, e.targetHandle],
459
+ [b, a, e.sourceHandle]
460
+ ]) {
461
+ if (!m || !sw || !isMachine(m) || !isSwitch(sw)) continue;
462
+ if (isTrunkPort(sw, handle2)) {
463
+ for (const v of sw.data.vlans) join(membership, m.id, vlanName(v.id));
464
+ } else {
465
+ const vlan = accessVlan(sw, handle2);
466
+ if (vlan !== null) join(membership, m.id, vlanName(vlan));
467
+ }
468
+ }
469
+ }
470
+ function membershipOf(nodes, edges, byId) {
471
+ const membership = /* @__PURE__ */ new Map();
472
+ for (const n of nodes.filter(isMachine)) for (const l of lansAround(n, nodes)) join(membership, n.id, l.data.name);
473
+ for (const e of edges) {
474
+ const a = byId.get(e.source);
475
+ const b = byId.get(e.target);
476
+ if (a && b && isMachine(a) && isLan(b)) join(membership, a.id, b.data.name);
477
+ if (a && b && isLan(a) && isMachine(b)) join(membership, b.id, a.data.name);
478
+ joinSwitchPorts(membership, e, byId);
479
+ }
480
+ return membership;
481
+ }
482
+ function kindOf(m) {
483
+ if (m.kind === "router") return { router: {} };
484
+ if (m.kind === "container") {
485
+ return {
486
+ container: m.appliance ? { image: m.image.trim(), appliance: m.appliance, ...m.config?.trim() ? { config: m.config } : {} } : { image: m.image.trim(), idle: m.idle ?? idleByDefault(m.image) }
487
+ };
488
+ }
489
+ const provision = m.provision.split("\n").map((l) => l.trim()).filter(Boolean);
490
+ return { vm: { os: m.os, provision } };
491
+ }
492
+ var servicesOf = (m) => readPorts(m.ports).services;
493
+ function linksOf(edges, byId) {
494
+ return edges.flatMap((e) => {
495
+ const a = byId.get(e.source);
496
+ const b = byId.get(e.target);
497
+ if (!a || !b || !isMachine(a) || !isMachine(b) || !a.data.name || !b.data.name || !e.data) return [];
498
+ 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) }];
499
+ });
500
+ }
501
+ function networksOf(nodes, edges) {
502
+ const lans = nodes.filter(isLan).filter((l) => l.data.name);
503
+ const networks = Object.fromEntries(
504
+ lans.map((l) => [l.data.name, { cidr: l.data.cidr.trim(), internet: l.data.internet, tc: tcOf(l.data.tc) }])
505
+ );
506
+ const nameOfGroup = switchLans(nodes, edges);
507
+ for (const sw of nodes.filter(isSwitch)) {
508
+ for (const v of sw.data.vlans) {
509
+ const net = networks[vlanName(v.id)] ?? { cidr: v.cidr.trim(), internet: true };
510
+ networks[vlanName(v.id)] = { ...net, tc: void 0, switch: nameOfGroup(sw.id), vlan: v.id };
511
+ }
512
+ }
513
+ return networks;
514
+ }
515
+ function toTopology(name, nodes, edges) {
516
+ const byId = new Map(nodes.map((n) => [n.id, n]));
517
+ const membership = membershipOf(nodes, edges, byId);
518
+ const machines = Object.fromEntries(
519
+ nodes.filter(isMachine).filter((n) => n.data.name).map(({ id, data: m }) => {
520
+ const networks = Object.fromEntries([...membership.get(id) ?? []].map((lan2) => [lan2, m.pins?.[lan2] ?? null]));
521
+ return [m.name, { kind: kindOf(m), access: m.access, services: servicesOf(m), networks }];
522
+ })
523
+ );
524
+ const links = linksOf(edges, byId);
525
+ return JSON.stringify({ name, nodes: machines, networks: networksOf(nodes, edges), links });
526
+ }
527
+
528
+ // src/lib/tool/create.ts
529
+ function twoVlans() {
530
+ return {
531
+ name: "",
532
+ vlans: [
533
+ { id: 10, cidr: "10.10.10.0/24" },
534
+ { id: 20, cidr: "10.10.20.0/24" }
535
+ ],
536
+ ports: [10, 10, 10, 10, 20, 20, 20, 20]
537
+ };
538
+ }
539
+
540
+ // src/lib/tool/os.ts
541
+ var DEFAULT_OS = "debian@12";
542
+
543
+ // src/lib/tool/examples.ts
544
+ var base = { image: "", os: "", provision: "", ports: "", access: false };
545
+ var machine = (id, position, data) => ({ id, type: "machine", position, data });
546
+ var lan = (id, area, data) => ({
547
+ ...LAN_BEHAVIOR,
548
+ id,
549
+ type: "lan",
550
+ position: { x: area.x, y: area.y },
551
+ width: area.w,
552
+ height: area.h,
553
+ data
554
+ });
555
+ var cable = (id, [source, target], [sh, th], [aPort, bPort]) => ({
556
+ id,
557
+ type: "cable",
558
+ source,
559
+ sourceHandle: sh,
560
+ target,
561
+ targetHandle: th,
562
+ data: { aPort, bPort }
563
+ });
564
+ function exampleLab() {
565
+ const nodes = [
566
+ // LANs first, so they are drawn under the machines.
567
+ lan("l-office", { x: -40, y: 40, w: 280, h: 260 }, { name: "office", cidr: "10.10.1.0/24", internet: true }),
568
+ lan("l-servers", { x: 560, y: -80, w: 300, h: 500 }, { name: "servers", cidr: "10.10.2.0/24", internet: true }),
569
+ machine("m-user", { x: 40, y: 110 }, { ...base, name: "user", kind: "vm", icon: "laptop", os: DEFAULT_OS, access: true }),
570
+ // On the office LAN's right edge: a two-network appliance, office on one side, its port out.
571
+ machine("m-router", { x: 240 - MACHINE_W / 2, y: 170 - MACHINE_H / 2 }, { ...base, name: "router", kind: "router", icon: "router" }),
572
+ machine("m-web", { x: 650, y: 0 }, { ...base, name: "web", kind: "container", icon: "server", image: "nginx:1.27-alpine", ports: "80" }),
573
+ machine("m-db", { x: 650, y: 240 }, { ...base, name: "db", kind: "vm", icon: "monitor", os: DEFAULT_OS, provision: "provision/db.sh", ports: "5432" })
574
+ ];
575
+ const edges = [cable("c-1", ["m-router", "l-servers"], ["port", "left"], ["eth1", ""])];
576
+ return { name: "office-lab", nodes, edges };
577
+ }
578
+ function vlanExample() {
579
+ const nodes = [
580
+ { id: "s-sw1", type: "switch", position: { x: 40, y: 0 }, data: { ...twoVlans(), name: "sw1" } },
581
+ machine("m-router", { x: 180, y: -230 }, { ...base, name: "router", kind: "router", icon: "router" }),
582
+ machine("m-pc1", { x: -140, y: 220 }, { ...base, name: "pc1", kind: "vm", icon: "laptop", os: DEFAULT_OS, access: true }),
583
+ machine("m-pc2", { x: 40, y: 220 }, { ...base, name: "pc2", kind: "container", icon: "container", image: "alpine:3.20" }),
584
+ machine("m-srv", { x: 300, y: 220 }, { ...base, name: "srv", kind: "container", icon: "server", image: "nginx:1.27-alpine", ports: "80" })
585
+ ];
586
+ const onSwitch = (id, m, mh, port, labels) => cable(id, [m, "s-sw1"], [mh, port], labels);
587
+ const edges = [
588
+ onSwitch("c-1", "m-router", "port", "trunk", ["eth0", "T"]),
589
+ onSwitch("c-2", "m-pc1", "top", "p1", ["eth0", "1"]),
590
+ onSwitch("c-3", "m-pc2", "top", "p2", ["eth0", "2"]),
591
+ onSwitch("c-4", "m-srv", "top", "p5", ["eth0", "5"])
592
+ ];
593
+ return { name: "vlan-lab", nodes, edges };
594
+ }
595
+
596
+ // src/lib/tool/engine.ts
597
+ function planOf(engine, topology, failed) {
598
+ try {
599
+ const raw = JSON.parse(engine.plan_json(topology));
600
+ return { ...raw, isoloom_yml: raw.isoloom_yml ?? null };
601
+ } catch (e) {
602
+ return { ok: false, error: failed(String(e)), isoloom_yml: null, problems: [], files: [], skipped: [], addresses: {} };
603
+ }
604
+ }
605
+ function ownFiles(engine, topology) {
606
+ return JSON.parse(engine.own_files_json(topology));
607
+ }
608
+
609
+ // src/lib/tool/export-targets.ts
610
+ var CLOUDS = {
611
+ aws: "AWS",
612
+ azure: "Azure",
613
+ gcp: "Google Cloud",
614
+ digitalocean: "DigitalOcean",
615
+ linode: "Linode",
616
+ oci: "Oracle Cloud"
617
+ };
618
+ function cloudTarget(target, cloud, terraform, t) {
619
+ const where = CLOUDS[cloud ?? ""] ?? String(cloud);
620
+ if (target === "cloud-docker") return { name: `${where} (${t.dockerOnOneVm})`, isoloom: `isoloom run cloud-docker --cloud ${String(cloud)}`, run: terraform };
621
+ return { name: `${where} (${t.oneVmPerMachine})`, isoloom: `isoloom run cloud-vm --cloud ${String(cloud)}`, run: terraform };
622
+ }
623
+ function describe(dir, t) {
624
+ const [target, cloud] = dir.split("/");
625
+ const terraform = `cd .isoloom/${dir} && terraform init && terraform apply`;
626
+ switch (target) {
627
+ case "docker":
628
+ return { name: "Docker Compose", isoloom: "isoloom run docker", run: "docker compose -f .isoloom/docker/compose.yml up -d" };
629
+ case "hybrid":
630
+ return { name: `Vagrant (${t.containersAndVms})`, isoloom: "isoloom run hybrid", run: "cd .isoloom/hybrid && vagrant up" };
631
+ case "vagrant":
632
+ return { name: `Vagrant (${t.oneVmPerMachine})`, isoloom: "isoloom run vagrant", run: "cd .isoloom/vagrant && vagrant up" };
633
+ case "docker-vm":
634
+ return cloud === "proxmox" ? { name: `Proxmox (${t.dockerOnOneVm})`, isoloom: null, run: terraform } : { name: `Vagrant (${t.dockerOnOneVm})`, isoloom: "isoloom run docker-vm", run: "cd .isoloom/docker-vm && vagrant up" };
635
+ case "proxmox":
636
+ return { name: `Proxmox (${t.oneVmPerMachine})`, isoloom: "isoloom run proxmox", run: terraform };
637
+ case "kubernetes":
638
+ return { name: "Kubernetes", isoloom: "isoloom run kubernetes", run: "kubectl kustomize --load-restrictor LoadRestrictionsNone .isoloom/kubernetes | kubectl apply -f -" };
639
+ case "cloud-docker":
640
+ case "cloud-vm":
641
+ return cloudTarget(target, cloud, terraform, t);
642
+ default:
643
+ return { name: dir, isoloom: null, run: "" };
644
+ }
645
+ }
646
+ function folderOf(path) {
647
+ const parts = path.replace(/^\.isoloom\//, "").split("/");
648
+ const deeper = parts.length > 2 && (parts[0] === "cloud-docker" || parts[0] === "cloud-vm" || parts[0] === "docker-vm" && parts[1] === "proxmox");
649
+ return deeper ? `${parts[0]}/${parts[1]}` : parts[0];
650
+ }
651
+ function groupByTarget(files, t) {
652
+ const groups = /* @__PURE__ */ new Map();
653
+ for (const f of files) {
654
+ const dir = folderOf(f.path);
655
+ groups.set(dir, [...groups.get(dir) ?? [], f]);
656
+ }
657
+ return [...groups.entries()].map(([dir, fs]) => ({ dir, files: fs, ...describe(dir, t) }));
658
+ }
659
+
660
+ // src/lib/tool/issues.ts
661
+ function blockIssues(nodes, text) {
662
+ const issues = [];
663
+ const blocks = /* @__PURE__ */ new Map();
664
+ for (const sw of nodes.filter(isSwitch)) {
665
+ for (const v of sw.data.vlans) {
666
+ const name = vlanName(v.id);
667
+ const seen = blocks.get(name);
668
+ if (seen && seen !== v.cidr) issues.push(text.vlanBlocks(name, seen, v.cidr, sw.data.name));
669
+ blocks.set(name, v.cidr);
670
+ }
671
+ }
672
+ for (const l of nodes.filter(isLan)) {
673
+ const seen = blocks.get(l.data.name);
674
+ if (seen && seen !== l.data.cidr) issues.push(text.areaBlock(l.data.name, l.data.cidr, seen));
675
+ }
676
+ return issues;
677
+ }
678
+ function nameIssues(nodes, text) {
679
+ const issues = [];
680
+ for (const [what, list] of [
681
+ ["machine", nodes.filter(isMachine)],
682
+ ["lan", nodes.filter(isLan)],
683
+ ["switch", nodes.filter(isSwitch)]
684
+ ]) {
685
+ const seen = /* @__PURE__ */ new Set();
686
+ for (const n of list) {
687
+ if (!n.data.name) issues.push(text.noName[what]);
688
+ else if (seen.has(n.data.name)) issues.push(text.sameName[what](n.data.name));
689
+ seen.add(n.data.name);
690
+ }
691
+ }
692
+ return issues;
693
+ }
694
+ function portIssues(nodes, text) {
695
+ return nodes.filter(isMachine).flatMap((m) => readPorts(m.data.ports).bad.map((entry) => text.badPort(m.data.name, entry)));
696
+ }
697
+ function canvasIssues(nodes, text) {
698
+ return [...blockIssues(nodes, text), ...nameIssues(nodes, text), ...portIssues(nodes, text)];
699
+ }
700
+ export {
701
+ APPLIANCES,
702
+ DESIGNS,
703
+ LAN_BEHAVIOR,
704
+ LAN_H,
705
+ LAN_W,
706
+ MACHINE_H,
707
+ MACHINE_W,
708
+ NOTE_COLORS,
709
+ PORT_PROTOCOLS,
710
+ ROUTER_H,
711
+ ROUTER_PORTS,
712
+ ROUTER_W,
713
+ SWITCH_H,
714
+ SWITCH_PORTS,
715
+ SWITCH_W,
716
+ TRUNK,
717
+ applianceInterface,
718
+ canvasIssues,
719
+ centerOf,
720
+ cleanName,
721
+ draftFromTopology,
722
+ exampleLab,
723
+ freeName,
724
+ groupByTarget,
725
+ idleByDefault,
726
+ imageName,
727
+ isAppliance,
728
+ isLan,
729
+ isMachine,
730
+ isNote,
731
+ isRealRouter,
732
+ isSwitch,
733
+ nodeName,
734
+ ownFiles,
735
+ planOf,
736
+ portsText,
737
+ readPorts,
738
+ routerPort,
739
+ sizeOf,
740
+ toTopology,
741
+ uid,
742
+ unselected,
743
+ vlanExample
744
+ };
745
+ //# sourceMappingURL=model.js.map