@takodotid/circuit 0.43.1 → 1.0.0-rc.2

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 (256) 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 +71 -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 +350 -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/render.ts +69 -62
  63. package/src/platforms/fortios/routing.ts +89 -58
  64. package/src/platforms/fortios/vdoms.ts +47 -16
  65. package/src/platforms/junos/TRAPS.md +2 -2
  66. package/src/platforms/junos/bgp.ts +63 -42
  67. package/src/platforms/junos/index.ts +122 -89
  68. package/src/platforms/junos/render.ts +139 -104
  69. package/src/platforms/platform.ts +10 -8
  70. package/src/platforms/raisecom-ros/TRAPS.md +1 -1
  71. package/src/platforms/raisecom-ros/index.ts +89 -70
  72. package/src/platforms/raisecom-ros/render.ts +49 -45
  73. package/src/platforms/routeros/TRAPS.md +3 -3
  74. package/src/platforms/routeros/index.ts +121 -60
  75. package/src/platforms/routeros/render.ts +214 -195
  76. package/src/platforms/vrp/TRAPS.md +1 -1
  77. package/src/platforms/vrp/index.ts +74 -50
  78. package/src/platforms/vrp/render.ts +67 -56
  79. package/src/project/project.ts +725 -0
  80. package/src/studio/app/env.d.ts +9 -0
  81. package/src/studio/app/index.html +18 -0
  82. package/src/studio/app/src/App.vue +95 -0
  83. package/src/studio/app/src/components/AddressesView.vue +38 -0
  84. package/src/studio/app/src/components/CablesView.vue +29 -0
  85. package/src/studio/app/src/components/CompareView.vue +160 -0
  86. package/src/studio/app/src/components/DataTable.vue +49 -0
  87. package/src/studio/app/src/components/DeviceView.vue +237 -0
  88. package/src/studio/app/src/components/DevicesView.vue +32 -0
  89. package/src/studio/app/src/components/Faceplate.vue +75 -0
  90. package/src/studio/app/src/components/GraphView.vue +282 -0
  91. package/src/studio/app/src/components/LineDiff.vue +24 -0
  92. package/src/studio/app/src/components/ProblemsView.vue +42 -0
  93. package/src/studio/app/src/components/SearchView.vue +72 -0
  94. package/src/studio/app/src/components/SecretsView.vue +112 -0
  95. package/src/studio/app/src/components/SessionsView.vue +35 -0
  96. package/src/studio/app/src/components/Settings.vue +95 -0
  97. package/src/studio/app/src/components/ToolsView.vue +118 -0
  98. package/src/studio/app/src/components/VlansView.vue +50 -0
  99. package/src/studio/app/src/lib/csv.ts +19 -0
  100. package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
  101. package/src/studio/app/src/lib/format.ts +38 -0
  102. package/src/studio/app/src/lib/net.ts +68 -0
  103. package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
  104. package/src/studio/app/src/lib/store.ts +71 -0
  105. package/src/studio/app/src/lib/tools.ts +217 -0
  106. package/src/studio/app/src/main.ts +9 -0
  107. package/src/studio/{public → app/src}/style.css +14 -4
  108. package/src/studio/app/tsconfig.json +17 -0
  109. package/src/studio/app/vite.config.ts +16 -0
  110. package/src/studio/assets.generated.ts +3 -0
  111. package/src/studio/data.ts +96 -188
  112. package/src/studio/git.ts +27 -43
  113. package/src/studio/server.ts +196 -107
  114. package/src/studio/types.ts +141 -0
  115. package/src/transport/console.ts +3 -3
  116. package/src/transport/ssh.ts +1 -1
  117. package/dist/chunks/index-3b343xva.js +0 -177
  118. package/dist/chunks/index-6wb41q67.js +0 -62
  119. package/dist/chunks/index-n7w3tg5y.js +0 -149
  120. package/dist/chunks/index-yzw86rc8.js +0 -921
  121. package/dist/chunks/new-w3zys5y4.js +0 -172
  122. package/dist/chunks/server-cxr9ynxc.js +0 -259
  123. package/dist/cli/index.js +0 -12922
  124. package/dist/helpers/index.js +0 -881
  125. package/dist/index.js +0 -28
  126. package/dist/types/core/address-space.d.ts +0 -62
  127. package/dist/types/core/address.d.ts +0 -44
  128. package/dist/types/core/cabling.d.ts +0 -41
  129. package/dist/types/core/cross-checks.d.ts +0 -28
  130. package/dist/types/core/define.d.ts +0 -79
  131. package/dist/types/core/project-files.d.ts +0 -12
  132. package/dist/types/core/schema-docs.d.ts +0 -25
  133. package/dist/types/core/secrets.d.ts +0 -62
  134. package/dist/types/core/topology.d.ts +0 -96
  135. package/dist/types/core/utils.d.ts +0 -10
  136. package/dist/types/core/validator.d.ts +0 -23
  137. package/dist/types/helpers/bgp.d.ts +0 -195
  138. package/dist/types/helpers/cables.d.ts +0 -69
  139. package/dist/types/helpers/checks.d.ts +0 -36
  140. package/dist/types/helpers/exchange.d.ts +0 -55
  141. package/dist/types/helpers/filters.d.ts +0 -51
  142. package/dist/types/helpers/index.d.ts +0 -14
  143. package/dist/types/helpers/route-server.d.ts +0 -52
  144. package/dist/types/helpers/upstreams.d.ts +0 -192
  145. package/dist/types/index.d.ts +0 -10
  146. package/dist/types/integrations/librenms.d.ts +0 -60
  147. package/dist/types/integrations/netbox.d.ts +0 -89
  148. package/dist/types/integrations/peeringdb.d.ts +0 -24
  149. package/dist/types/integrations/prometheus.d.ts +0 -24
  150. package/dist/types/integrations/registries.d.ts +0 -8
  151. package/dist/types/platforms/bgp-wait.d.ts +0 -24
  152. package/dist/types/platforms/catalog.d.ts +0 -109
  153. package/dist/types/platforms/derive.d.ts +0 -17
  154. package/dist/types/platforms/fortios/firewall.d.ts +0 -47
  155. package/dist/types/platforms/fortios/index.d.ts +0 -140
  156. package/dist/types/platforms/fortios/models.d.ts +0 -14
  157. package/dist/types/platforms/fortios/names.d.ts +0 -52
  158. package/dist/types/platforms/fortios/plan.d.ts +0 -114
  159. package/dist/types/platforms/fortios/render.d.ts +0 -77
  160. package/dist/types/platforms/fortios/routing.d.ts +0 -56
  161. package/dist/types/platforms/fortios/tree.d.ts +0 -37
  162. package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
  163. package/dist/types/platforms/index.d.ts +0 -13
  164. package/dist/types/platforms/junos/bgp.d.ts +0 -44
  165. package/dist/types/platforms/junos/index.d.ts +0 -114
  166. package/dist/types/platforms/junos/models.d.ts +0 -14
  167. package/dist/types/platforms/junos/render.d.ts +0 -154
  168. package/dist/types/platforms/line-diff.d.ts +0 -67
  169. package/dist/types/platforms/platform.d.ts +0 -135
  170. package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
  171. package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
  172. package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
  173. package/dist/types/platforms/routeros/export.d.ts +0 -59
  174. package/dist/types/platforms/routeros/index.d.ts +0 -116
  175. package/dist/types/platforms/routeros/models.d.ts +0 -18
  176. package/dist/types/platforms/routeros/plan.d.ts +0 -58
  177. package/dist/types/platforms/routeros/render.d.ts +0 -250
  178. package/dist/types/platforms/vrp/index.d.ts +0 -83
  179. package/dist/types/platforms/vrp/models.d.ts +0 -12
  180. package/dist/types/platforms/vrp/render.d.ts +0 -96
  181. package/dist/types/schema/common.d.ts +0 -30
  182. package/dist/types/schema/device.d.ts +0 -126
  183. package/dist/types/schema/firewall.d.ts +0 -131
  184. package/dist/types/schema/index.d.ts +0 -8
  185. package/dist/types/schema/interfaces.d.ts +0 -205
  186. package/dist/types/schema/policy.d.ts +0 -107
  187. package/dist/types/schema/routing.d.ts +0 -161
  188. package/dist/types/schema/system.d.ts +0 -198
  189. package/dist/types/schema/time-zones.d.ts +0 -2
  190. package/dist/types/transport/console.d.ts +0 -115
  191. package/dist/types/transport/path-check.d.ts +0 -32
  192. package/dist/types/transport/ssh.d.ts +0 -27
  193. package/src/cli/graph-format.ts +0 -91
  194. package/src/cli/new.ts +0 -234
  195. package/src/core/address-space.ts +0 -320
  196. package/src/core/cross-checks.ts +0 -369
  197. package/src/core/define.ts +0 -124
  198. package/src/core/project-files.ts +0 -35
  199. package/src/core/schema-docs.ts +0 -174
  200. package/src/core/validator.ts +0 -508
  201. package/src/helpers/bgp.ts +0 -473
  202. package/src/helpers/cables.ts +0 -74
  203. package/src/helpers/checks.ts +0 -201
  204. package/src/helpers/exchange.ts +0 -123
  205. package/src/helpers/filters.ts +0 -107
  206. package/src/helpers/index.ts +0 -25
  207. package/src/helpers/route-server.ts +0 -212
  208. package/src/helpers/upstreams.ts +0 -369
  209. package/src/index.ts +0 -13
  210. package/src/integrations/librenms.ts +0 -291
  211. package/src/integrations/netbox.ts +0 -620
  212. package/src/integrations/peeringdb.ts +0 -173
  213. package/src/integrations/prometheus.ts +0 -90
  214. package/src/schema/common.ts +0 -41
  215. package/src/schema/device.ts +0 -145
  216. package/src/schema/firewall.ts +0 -140
  217. package/src/schema/index.ts +0 -8
  218. package/src/schema/policy.ts +0 -115
  219. package/src/schema/routing.ts +0 -173
  220. package/src/schema/system.ts +0 -203
  221. package/src/studio/public/app.js +0 -186
  222. package/src/studio/public/compare.js +0 -250
  223. package/src/studio/public/dom.js +0 -138
  224. package/src/studio/public/evaluate.js +0 -687
  225. package/src/studio/public/export.js +0 -130
  226. package/src/studio/public/graph.js +0 -1541
  227. package/src/studio/public/index.html +0 -83
  228. package/src/studio/public/net.js +0 -134
  229. package/src/studio/public/tables.js +0 -1289
  230. package/src/studio/public/tools.js +0 -610
  231. package/templates/colocation/checks.ts +0 -53
  232. package/templates/colocation/circuit.config.ts +0 -9
  233. package/templates/colocation/edge-01.ts +0 -133
  234. package/templates/colocation/routing.ts +0 -13
  235. package/templates/colocation/tenancy.ts +0 -76
  236. package/templates/colocation/tenants/acme.ts +0 -19
  237. package/templates/colocation/tenants/index.ts +0 -6
  238. package/templates/colocation/tor-01.ts +0 -33
  239. package/templates/edge-router/checks.ts +0 -20
  240. package/templates/edge-router/circuit.config.ts +0 -8
  241. package/templates/edge-router/edge-01.ts +0 -110
  242. package/templates/edge-router/routing.ts +0 -13
  243. package/templates/project/AGENTS.md +0 -51
  244. package/templates/project/CLAUDE.md +0 -1
  245. package/templates/project/README.md +0 -16
  246. package/templates/project/gitignore +0 -5
  247. package/templates/project/tsconfig.json +0 -13
  248. package/templates/single-site/checks.ts +0 -26
  249. package/templates/single-site/circuit.config.ts +0 -8
  250. package/templates/single-site/router.ts +0 -86
  251. package/templates/single-site/site.ts +0 -18
  252. package/templates/single-site/switch.ts +0 -34
  253. /package/src/studio/{public → app/public}/favicon.svg +0 -0
  254. /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
  255. /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
  256. /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
+ });