@takodotid/circuit 0.43.0 → 1.0.0-rc.1

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 (257) hide show
  1. package/README.md +115 -37
  2. package/package.json +21 -58
  3. package/src/checks/across.ts +402 -0
  4. package/src/checks/built-in.ts +256 -0
  5. package/src/checks/device.ts +436 -0
  6. package/src/checks/index.ts +53 -0
  7. package/src/cli/advisories.ts +108 -0
  8. package/src/cli/app.ts +67 -0
  9. package/src/cli/editor-types.generated.ts +30 -0
  10. package/src/cli/editor-types.ts +69 -0
  11. package/src/cli/format.ts +124 -0
  12. package/src/cli/git-hooks.ts +79 -0
  13. package/src/cli/index.ts +348 -1170
  14. package/src/cli/migrate.ts +554 -0
  15. package/src/cli/project-commands.ts +656 -0
  16. package/src/cli/releases.ts +374 -0
  17. package/src/cli/remember.ts +339 -0
  18. package/src/cli/rename.ts +27 -33
  19. package/src/cli/secrets-password.ts +60 -0
  20. package/src/cli/secrets.ts +258 -0
  21. package/src/cli/setup.ts +300 -0
  22. package/src/cli/signing.ts +23 -0
  23. package/src/cli/text-editor.ts +268 -0
  24. package/src/cli/ui.ts +298 -0
  25. package/src/cli/version.ts +2 -0
  26. package/src/cli/versions.ts +158 -0
  27. package/src/config/address.ts +102 -0
  28. package/src/config/circuit.d.ts +30 -0
  29. package/src/config/common.ts +65 -0
  30. package/src/config/communities.ts +479 -0
  31. package/src/config/define.ts +684 -0
  32. package/src/config/device.ts +164 -0
  33. package/src/config/firewall.ts +150 -0
  34. package/src/config/index.ts +22 -0
  35. package/src/{schema → config}/interfaces.ts +121 -85
  36. package/src/config/keys.ts +434 -0
  37. package/src/config/management.ts +61 -0
  38. package/src/config/merge.ts +335 -0
  39. package/src/config/models.ts +70 -0
  40. package/src/config/monitoring.ts +64 -0
  41. package/src/config/network.ts +180 -0
  42. package/src/config/presets.ts +149 -0
  43. package/src/config/routing.ts +401 -0
  44. package/src/config/services.ts +51 -0
  45. package/src/config/switching.ts +84 -0
  46. package/src/config/system.ts +103 -0
  47. package/src/{schema → config}/time-zones.ts +1 -1
  48. package/src/core/address.ts +1 -1
  49. package/src/core/cabling.ts +8 -43
  50. package/src/core/graph.ts +40 -0
  51. package/src/{integrations → core}/registries.ts +5 -3
  52. package/src/core/secrets-file.ts +539 -0
  53. package/src/core/secrets.ts +83 -64
  54. package/src/core/topology.ts +103 -146
  55. package/src/core/utils.ts +12 -1
  56. package/src/model.ts +83 -0
  57. package/src/platforms/derive.ts +64 -37
  58. package/src/platforms/finding.ts +9 -0
  59. package/src/platforms/fortios/firewall.ts +76 -64
  60. package/src/platforms/fortios/index.ts +189 -125
  61. package/src/platforms/fortios/names.ts +51 -18
  62. package/src/platforms/fortios/plan.ts +132 -44
  63. package/src/platforms/fortios/render.ts +69 -62
  64. package/src/platforms/fortios/routing.ts +89 -58
  65. package/src/platforms/fortios/vdoms.ts +47 -16
  66. package/src/platforms/junos/TRAPS.md +2 -2
  67. package/src/platforms/junos/bgp.ts +63 -42
  68. package/src/platforms/junos/index.ts +122 -89
  69. package/src/platforms/junos/render.ts +139 -104
  70. package/src/platforms/platform.ts +10 -8
  71. package/src/platforms/raisecom-ros/TRAPS.md +1 -1
  72. package/src/platforms/raisecom-ros/index.ts +89 -70
  73. package/src/platforms/raisecom-ros/render.ts +49 -45
  74. package/src/platforms/routeros/TRAPS.md +3 -3
  75. package/src/platforms/routeros/index.ts +121 -60
  76. package/src/platforms/routeros/render.ts +214 -195
  77. package/src/platforms/vrp/TRAPS.md +1 -1
  78. package/src/platforms/vrp/index.ts +74 -50
  79. package/src/platforms/vrp/render.ts +67 -56
  80. package/src/project/project.ts +725 -0
  81. package/src/studio/app/env.d.ts +9 -0
  82. package/src/studio/app/index.html +18 -0
  83. package/src/studio/app/src/App.vue +95 -0
  84. package/src/studio/app/src/components/AddressesView.vue +38 -0
  85. package/src/studio/app/src/components/CablesView.vue +29 -0
  86. package/src/studio/app/src/components/CompareView.vue +160 -0
  87. package/src/studio/app/src/components/DataTable.vue +49 -0
  88. package/src/studio/app/src/components/DeviceView.vue +237 -0
  89. package/src/studio/app/src/components/DevicesView.vue +32 -0
  90. package/src/studio/app/src/components/Faceplate.vue +75 -0
  91. package/src/studio/app/src/components/GraphView.vue +282 -0
  92. package/src/studio/app/src/components/LineDiff.vue +24 -0
  93. package/src/studio/app/src/components/ProblemsView.vue +42 -0
  94. package/src/studio/app/src/components/SearchView.vue +72 -0
  95. package/src/studio/app/src/components/SecretsView.vue +112 -0
  96. package/src/studio/app/src/components/SessionsView.vue +35 -0
  97. package/src/studio/app/src/components/Settings.vue +95 -0
  98. package/src/studio/app/src/components/ToolsView.vue +118 -0
  99. package/src/studio/app/src/components/VlansView.vue +50 -0
  100. package/src/studio/app/src/lib/csv.ts +19 -0
  101. package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
  102. package/src/studio/app/src/lib/format.ts +38 -0
  103. package/src/studio/app/src/lib/net.ts +68 -0
  104. package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
  105. package/src/studio/app/src/lib/store.ts +71 -0
  106. package/src/studio/app/src/lib/tools.ts +217 -0
  107. package/src/studio/app/src/main.ts +9 -0
  108. package/src/studio/{public → app/src}/style.css +14 -4
  109. package/src/studio/app/tsconfig.json +17 -0
  110. package/src/studio/app/vite.config.ts +16 -0
  111. package/src/studio/assets.generated.ts +3 -0
  112. package/src/studio/data.ts +96 -188
  113. package/src/studio/git.ts +27 -43
  114. package/src/studio/server.ts +196 -107
  115. package/src/studio/types.ts +141 -0
  116. package/src/transport/console.ts +3 -3
  117. package/src/transport/ssh.ts +1 -1
  118. package/dist/chunks/index-3b343xva.js +0 -177
  119. package/dist/chunks/index-6wb41q67.js +0 -62
  120. package/dist/chunks/index-n7w3tg5y.js +0 -149
  121. package/dist/chunks/index-yzw86rc8.js +0 -921
  122. package/dist/chunks/new-w3zys5y4.js +0 -172
  123. package/dist/chunks/server-cxr9ynxc.js +0 -259
  124. package/dist/cli/index.js +0 -12872
  125. package/dist/helpers/index.js +0 -881
  126. package/dist/index.js +0 -28
  127. package/dist/types/core/address-space.d.ts +0 -62
  128. package/dist/types/core/address.d.ts +0 -44
  129. package/dist/types/core/cabling.d.ts +0 -41
  130. package/dist/types/core/cross-checks.d.ts +0 -28
  131. package/dist/types/core/define.d.ts +0 -79
  132. package/dist/types/core/project-files.d.ts +0 -12
  133. package/dist/types/core/schema-docs.d.ts +0 -25
  134. package/dist/types/core/secrets.d.ts +0 -62
  135. package/dist/types/core/topology.d.ts +0 -96
  136. package/dist/types/core/utils.d.ts +0 -10
  137. package/dist/types/core/validator.d.ts +0 -23
  138. package/dist/types/helpers/bgp.d.ts +0 -195
  139. package/dist/types/helpers/cables.d.ts +0 -69
  140. package/dist/types/helpers/checks.d.ts +0 -36
  141. package/dist/types/helpers/exchange.d.ts +0 -55
  142. package/dist/types/helpers/filters.d.ts +0 -51
  143. package/dist/types/helpers/index.d.ts +0 -14
  144. package/dist/types/helpers/route-server.d.ts +0 -52
  145. package/dist/types/helpers/upstreams.d.ts +0 -192
  146. package/dist/types/index.d.ts +0 -10
  147. package/dist/types/integrations/librenms.d.ts +0 -60
  148. package/dist/types/integrations/netbox.d.ts +0 -89
  149. package/dist/types/integrations/peeringdb.d.ts +0 -24
  150. package/dist/types/integrations/prometheus.d.ts +0 -24
  151. package/dist/types/integrations/registries.d.ts +0 -8
  152. package/dist/types/platforms/bgp-wait.d.ts +0 -24
  153. package/dist/types/platforms/catalog.d.ts +0 -109
  154. package/dist/types/platforms/derive.d.ts +0 -17
  155. package/dist/types/platforms/fortios/firewall.d.ts +0 -47
  156. package/dist/types/platforms/fortios/index.d.ts +0 -140
  157. package/dist/types/platforms/fortios/models.d.ts +0 -14
  158. package/dist/types/platforms/fortios/names.d.ts +0 -52
  159. package/dist/types/platforms/fortios/plan.d.ts +0 -104
  160. package/dist/types/platforms/fortios/render.d.ts +0 -77
  161. package/dist/types/platforms/fortios/routing.d.ts +0 -56
  162. package/dist/types/platforms/fortios/tree.d.ts +0 -37
  163. package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
  164. package/dist/types/platforms/index.d.ts +0 -13
  165. package/dist/types/platforms/junos/bgp.d.ts +0 -44
  166. package/dist/types/platforms/junos/index.d.ts +0 -114
  167. package/dist/types/platforms/junos/models.d.ts +0 -14
  168. package/dist/types/platforms/junos/render.d.ts +0 -154
  169. package/dist/types/platforms/line-diff.d.ts +0 -67
  170. package/dist/types/platforms/platform.d.ts +0 -135
  171. package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
  172. package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
  173. package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
  174. package/dist/types/platforms/routeros/export.d.ts +0 -59
  175. package/dist/types/platforms/routeros/index.d.ts +0 -116
  176. package/dist/types/platforms/routeros/models.d.ts +0 -18
  177. package/dist/types/platforms/routeros/plan.d.ts +0 -58
  178. package/dist/types/platforms/routeros/render.d.ts +0 -250
  179. package/dist/types/platforms/vrp/index.d.ts +0 -83
  180. package/dist/types/platforms/vrp/models.d.ts +0 -12
  181. package/dist/types/platforms/vrp/render.d.ts +0 -96
  182. package/dist/types/schema/common.d.ts +0 -30
  183. package/dist/types/schema/device.d.ts +0 -126
  184. package/dist/types/schema/firewall.d.ts +0 -131
  185. package/dist/types/schema/index.d.ts +0 -8
  186. package/dist/types/schema/interfaces.d.ts +0 -205
  187. package/dist/types/schema/policy.d.ts +0 -107
  188. package/dist/types/schema/routing.d.ts +0 -161
  189. package/dist/types/schema/system.d.ts +0 -198
  190. package/dist/types/schema/time-zones.d.ts +0 -2
  191. package/dist/types/transport/console.d.ts +0 -115
  192. package/dist/types/transport/path-check.d.ts +0 -32
  193. package/dist/types/transport/ssh.d.ts +0 -27
  194. package/src/cli/graph-format.ts +0 -91
  195. package/src/cli/new.ts +0 -234
  196. package/src/core/address-space.ts +0 -320
  197. package/src/core/cross-checks.ts +0 -369
  198. package/src/core/define.ts +0 -124
  199. package/src/core/project-files.ts +0 -35
  200. package/src/core/schema-docs.ts +0 -174
  201. package/src/core/validator.ts +0 -508
  202. package/src/helpers/bgp.ts +0 -473
  203. package/src/helpers/cables.ts +0 -74
  204. package/src/helpers/checks.ts +0 -201
  205. package/src/helpers/exchange.ts +0 -123
  206. package/src/helpers/filters.ts +0 -107
  207. package/src/helpers/index.ts +0 -25
  208. package/src/helpers/route-server.ts +0 -212
  209. package/src/helpers/upstreams.ts +0 -369
  210. package/src/index.ts +0 -13
  211. package/src/integrations/librenms.ts +0 -291
  212. package/src/integrations/netbox.ts +0 -620
  213. package/src/integrations/peeringdb.ts +0 -173
  214. package/src/integrations/prometheus.ts +0 -90
  215. package/src/schema/common.ts +0 -41
  216. package/src/schema/device.ts +0 -145
  217. package/src/schema/firewall.ts +0 -140
  218. package/src/schema/index.ts +0 -8
  219. package/src/schema/policy.ts +0 -115
  220. package/src/schema/routing.ts +0 -173
  221. package/src/schema/system.ts +0 -203
  222. package/src/studio/public/app.js +0 -186
  223. package/src/studio/public/compare.js +0 -250
  224. package/src/studio/public/dom.js +0 -138
  225. package/src/studio/public/evaluate.js +0 -687
  226. package/src/studio/public/export.js +0 -130
  227. package/src/studio/public/graph.js +0 -1541
  228. package/src/studio/public/index.html +0 -83
  229. package/src/studio/public/net.js +0 -134
  230. package/src/studio/public/tables.js +0 -1289
  231. package/src/studio/public/tools.js +0 -610
  232. package/templates/colocation/checks.ts +0 -53
  233. package/templates/colocation/circuit.config.ts +0 -9
  234. package/templates/colocation/edge-01.ts +0 -133
  235. package/templates/colocation/routing.ts +0 -13
  236. package/templates/colocation/tenancy.ts +0 -76
  237. package/templates/colocation/tenants/acme.ts +0 -19
  238. package/templates/colocation/tenants/index.ts +0 -6
  239. package/templates/colocation/tor-01.ts +0 -33
  240. package/templates/edge-router/checks.ts +0 -20
  241. package/templates/edge-router/circuit.config.ts +0 -8
  242. package/templates/edge-router/edge-01.ts +0 -110
  243. package/templates/edge-router/routing.ts +0 -13
  244. package/templates/project/AGENTS.md +0 -51
  245. package/templates/project/CLAUDE.md +0 -1
  246. package/templates/project/README.md +0 -16
  247. package/templates/project/gitignore +0 -5
  248. package/templates/project/tsconfig.json +0 -13
  249. package/templates/single-site/checks.ts +0 -26
  250. package/templates/single-site/circuit.config.ts +0 -8
  251. package/templates/single-site/router.ts +0 -86
  252. package/templates/single-site/site.ts +0 -18
  253. package/templates/single-site/switch.ts +0 -34
  254. /package/src/studio/{public → app/public}/favicon.svg +0 -0
  255. /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
  256. /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
  257. /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
@@ -0,0 +1,217 @@
1
+ // What Studio's tools work out from the project: where an address is and how each device would reach it, and the cables that carry a VLAN from one device to another. They say what the project means, not what a device did: a route learned from BGP or OSPF is not in the project.
2
+
3
+ import type { Device } from "../../../../model";
4
+ import type { StudioData } from "../../../types";
5
+ import { Net } from "./net";
6
+
7
+ /** How one device would reach an address, in one routing table. */
8
+ export type RouteFound = {
9
+ device: string;
10
+ table: string;
11
+ kind: "local" | "connected" | "static" | "none";
12
+ prefix?: string;
13
+ via?: string;
14
+ through?: string;
15
+ note?: string;
16
+ };
17
+
18
+ /** A rule that names the address. */
19
+ export type RuleFound = { device: string; where: string; rule: number; description?: string; then: string; side: string };
20
+
21
+ export type Trace = {
22
+ held: { device: string; holder: string; address: string }[];
23
+ routes: RouteFound[];
24
+ rules: RuleFound[];
25
+ };
26
+
27
+ export class Tools {
28
+ /** The addresses on a device's ports and interfaces, with the VRF each routes in. */
29
+ private static addressesOf(device: Device): { holder: string; address: string; peer?: string; vrf?: string }[] {
30
+ const groups = Object.values(device.interfaces ?? {}) as (
31
+ Record<string, { addresses?: readonly unknown[]; vrf?: string } | undefined> | undefined
32
+ )[];
33
+
34
+ return groups.flatMap((group) =>
35
+ Object.entries(group ?? {}).flatMap(([holder, settings]) =>
36
+ (settings?.addresses ?? []).map((entry) =>
37
+ typeof entry === "string"
38
+ ? { holder, address: entry, ...(settings?.vrf ? { vrf: settings.vrf } : {}) }
39
+ : {
40
+ holder,
41
+ address: (entry as { address: string }).address,
42
+ peer: (entry as { peer_address: string }).peer_address,
43
+ ...(settings?.vrf ? { vrf: settings.vrf } : {}),
44
+ }
45
+ )
46
+ )
47
+ );
48
+ }
49
+
50
+ /** Where an address is, how each device would reach it from what the project says, and every rule that names it. Undefined when the text is not one address. */
51
+ static trace(data: StudioData, text: string): Trace | undefined {
52
+ const target = Net.parseRange(text.trim());
53
+ if (!target || target.first !== target.last) return undefined;
54
+ const address = text.trim();
55
+
56
+ const held = data.devices.flatMap((device) =>
57
+ Tools.addressesOf(device)
58
+ .filter((entry) => Net.sameAddress(entry.address, address))
59
+ .map((entry) => ({ device: device.name, holder: entry.holder, address: entry.address }))
60
+ );
61
+
62
+ const routes: RouteFound[] = [];
63
+
64
+ for (const device of data.devices) {
65
+ // The longest prefix wins in each table; on a tie, the device's own address, then a connected one, then a static route by its distance.
66
+ const best = new Map<string, RouteFound & { length: number; rank: number }>();
67
+ const offer = (found: RouteFound & { length: number; rank: number }) => {
68
+ const current = best.get(found.table);
69
+ if (!current || found.length > current.length || (found.length === current.length && found.rank < current.rank))
70
+ best.set(found.table, found);
71
+ };
72
+
73
+ for (const entry of Tools.addressesOf(device)) {
74
+ const table = entry.vrf ? `VRF ${entry.vrf}` : "main";
75
+
76
+ if (Net.sameAddress(entry.address, address)) {
77
+ offer({
78
+ device: device.name,
79
+ table,
80
+ kind: "local",
81
+ prefix: entry.address,
82
+ through: entry.holder,
83
+ length: 129,
84
+ rank: 0,
85
+ });
86
+ } else if (entry.peer && Net.sameAddress(entry.peer, address)) {
87
+ offer({
88
+ device: device.name,
89
+ table,
90
+ kind: "connected",
91
+ prefix: `${entry.peer}, the far end of ${entry.address}`,
92
+ through: entry.holder,
93
+ length: 128,
94
+ rank: 1,
95
+ });
96
+ } else if (!entry.peer && Net.covers(entry.address, address)) {
97
+ offer({
98
+ device: device.name,
99
+ table,
100
+ kind: "connected",
101
+ prefix: entry.address,
102
+ through: entry.holder,
103
+ length: Net.lengthOf(entry.address),
104
+ rank: 1,
105
+ });
106
+ }
107
+ }
108
+
109
+ for (const route of device.routing?.static_routes ?? []) {
110
+ if (!Net.covers(route.prefix, address)) continue;
111
+
112
+ const table = route.vrf ? `VRF ${route.vrf}` : (route.routing_table ?? "main");
113
+ offer({
114
+ device: device.name,
115
+ table,
116
+ kind: "static",
117
+ prefix: route.prefix,
118
+ ...(route.next_hop ? { via: route.next_hop } : {}),
119
+ ...(route.interface ? { through: route.interface } : {}),
120
+ ...(route.blackhole ? { note: "dropped: a blackhole route" } : route.description ? { note: route.description } : {}),
121
+ length: Net.lengthOf(route.prefix),
122
+ rank: 2 + (route.distance ?? 1),
123
+ });
124
+ }
125
+
126
+ if (!best.size) routes.push({ device: device.name, table: "main", kind: "none" });
127
+ for (const { length: _length, rank: _rank, ...found } of best.values()) routes.push(found);
128
+ }
129
+
130
+ const rules: RuleFound[] = [];
131
+
132
+ for (const device of data.devices) {
133
+ const sets = (device.firewall?.address_sets ?? {}) as Record<string, readonly string[] | undefined>;
134
+ const inSet = (name: unknown) => typeof name === "string" && (sets[name] ?? []).some((entry) => Net.covers(entry, address));
135
+ const covers = (value: unknown) =>
136
+ (Array.isArray(value) ? value : [value]).some((entry) => typeof entry === "string" && Net.covers(entry, address));
137
+
138
+ const look = (
139
+ where: string,
140
+ list: readonly { description?: string; if?: Record<string, unknown>; then: string }[] | undefined
141
+ ) => {
142
+ for (const [index, rule] of (list ?? []).entries()) {
143
+ const match = rule.if ?? {};
144
+ const sides = [
145
+ covers(match.src) || inSet(match.src_address_set) ? "source" : "",
146
+ covers(match.dst) || inSet(match.dst_address_set) ? "destination" : "",
147
+ ].filter(Boolean);
148
+ if (!sides.length) continue;
149
+
150
+ rules.push({
151
+ device: device.name,
152
+ where,
153
+ rule: index + 1,
154
+ then: rule.then,
155
+ side: sides.join(" and "),
156
+ ...(rule.description ? { description: rule.description } : {}),
157
+ });
158
+ }
159
+ };
160
+
161
+ for (const chain of ["input", "forward", "output"] as const) look(`filter ${chain}`, device.firewall?.filter?.[chain]?.rules);
162
+ look("NAT source", device.firewall?.nat?.source);
163
+ look("NAT destination", device.firewall?.nat?.destination);
164
+ for (const [name, acl] of Object.entries(device.switching?.acls ?? {})) look(`ACL ${name}`, acl as never);
165
+ }
166
+
167
+ return { held, routes, rules };
168
+ }
169
+
170
+ /** Whether a port, or the LAG it is in, carries a VLAN. */
171
+ private static carries(device: Device | undefined, portName: string, vlan: string): boolean {
172
+ const port = device?.interfaces?.ethernet?.[portName];
173
+ if (!port || !device) return false;
174
+
175
+ const carrier = port.in_lag ? device.interfaces?.lag?.[port.in_lag] : port;
176
+ return carrier?.access_vlan === vlan || carrier?.native_vlan === vlan || Boolean(carrier?.trunk_vlans?.includes(vlan));
177
+ }
178
+
179
+ /** The fewest cables that carry a VLAN from one device to another, at both ends of each. Undefined when no path carries it. */
180
+ static vlanPath(
181
+ data: StudioData,
182
+ vlan: string,
183
+ from: string,
184
+ to: string
185
+ ): { device: string; port: string; farDevice: string; farPort: string }[] | undefined {
186
+ const byName = new Map(data.devices.map((device) => [device.name, device]));
187
+ const hops = data.cables
188
+ .filter(
189
+ (cable) =>
190
+ Tools.carries(byName.get(cable.device), cable.port, vlan) &&
191
+ Tools.carries(byName.get(cable.farDevice), cable.farPort, vlan)
192
+ )
193
+ .flatMap((cable) => [cable, { device: cable.farDevice, port: cable.farPort, farDevice: cable.device, farPort: cable.port }]);
194
+
195
+ const came = new Map<string, (typeof hops)[number] | null>([[from, null]]);
196
+ const queue = [from];
197
+
198
+ while (queue.length) {
199
+ const here = queue.shift()!;
200
+ if (here === to) break;
201
+
202
+ for (const hop of hops.filter((candidate) => candidate.device === here)) {
203
+ if (came.has(hop.farDevice)) continue;
204
+ came.set(hop.farDevice, hop);
205
+ queue.push(hop.farDevice);
206
+ }
207
+ }
208
+
209
+ if (!came.has(to)) return undefined;
210
+
211
+ const path: { device: string; port: string; farDevice: string; farPort: string }[] = [];
212
+ for (let hop = came.get(to); hop; hop = came.get(hop.device)) {
213
+ path.unshift({ device: hop.device, port: hop.port, farDevice: hop.farDevice, farPort: hop.farPort });
214
+ }
215
+ return path;
216
+ }
217
+ }
@@ -0,0 +1,9 @@
1
+ // Studio's page: one Vue app, which reads what the server gathered from the project.
2
+
3
+ import { createApp } from "vue";
4
+ import App from "./App.vue";
5
+ import { connect } from "./lib/store";
6
+ import "./style.css";
7
+
8
+ createApp(App).mount("#app");
9
+ connect();
@@ -6,7 +6,7 @@
6
6
  font-weight: 400 700;
7
7
  font-stretch: 75% 125%;
8
8
  font-display: swap;
9
- src: url("special-gothic-latin-ext.woff2") format("woff2");
9
+ src: url("/special-gothic-latin-ext.woff2") format("woff2");
10
10
  unicode-range:
11
11
  U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
12
12
  U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
@@ -18,7 +18,7 @@
18
18
  font-weight: 400 700;
19
19
  font-stretch: 75% 125%;
20
20
  font-display: swap;
21
- src: url("special-gothic-latin.woff2") format("woff2");
21
+ src: url("/special-gothic-latin.woff2") format("woff2");
22
22
  unicode-range:
23
23
  U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
24
24
  U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
@@ -106,6 +106,14 @@ body {
106
106
  flex-direction: column;
107
107
  }
108
108
 
109
+ /* Vue's root holds the rail and the view, and passes the window's height down to the view. */
110
+ #app {
111
+ flex: 1;
112
+ display: flex;
113
+ flex-direction: column;
114
+ min-height: 0;
115
+ }
116
+
109
117
  ::selection {
110
118
  color: #11141b;
111
119
  background: var(--yellow);
@@ -1365,7 +1373,8 @@ details.change summary {
1365
1373
  }
1366
1374
 
1367
1375
  .field input[type="text"],
1368
- .field select {
1376
+ .field select,
1377
+ .field input {
1369
1378
  min-width: 150px;
1370
1379
  min-height: 36px;
1371
1380
  padding: 5px 9px;
@@ -1378,7 +1387,8 @@ details.change summary {
1378
1387
  }
1379
1388
 
1380
1389
  .field input[type="text"]:focus,
1381
- .field select:focus {
1390
+ .field select:focus,
1391
+ .field input:focus {
1382
1392
  border-color: var(--cobalt);
1383
1393
  }
1384
1394
 
@@ -0,0 +1,17 @@
1
+ {
2
+ // Studio's page: the browser's own types. Its .vue files are checked by Vite when it builds them.
3
+ "compilerOptions": {
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "types": [],
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "skipLibCheck": true,
12
+ "verbatimModuleSyntax": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true
15
+ },
16
+ "include": ["env.d.ts", "src/**/*.ts"]
17
+ }
@@ -0,0 +1,16 @@
1
+ // Builds Studio's page into dist/studio/. `bun scripts/studio.ts` runs it and carries the result into Circuit.
2
+
3
+ import vue from "@vitejs/plugin-vue";
4
+ import { join } from "node:path";
5
+ import { defineConfig } from "vite";
6
+
7
+ export default defineConfig({
8
+ root: import.meta.dirname,
9
+ plugins: [vue()],
10
+ logLevel: "warn",
11
+ build: {
12
+ outDir: join(import.meta.dirname, "..", "..", "..", "dist", "studio"),
13
+ emptyOutDir: true,
14
+ chunkSizeWarningLimit: 1500,
15
+ },
16
+ });