@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.
- package/README.md +115 -37
- package/package.json +21 -58
- package/src/checks/across.ts +402 -0
- package/src/checks/built-in.ts +256 -0
- package/src/checks/device.ts +436 -0
- package/src/checks/index.ts +53 -0
- package/src/cli/advisories.ts +108 -0
- package/src/cli/app.ts +71 -0
- package/src/cli/editor-types.generated.ts +30 -0
- package/src/cli/editor-types.ts +69 -0
- package/src/cli/format.ts +124 -0
- package/src/cli/git-hooks.ts +79 -0
- package/src/cli/index.ts +350 -1170
- package/src/cli/migrate.ts +554 -0
- package/src/cli/project-commands.ts +656 -0
- package/src/cli/releases.ts +374 -0
- package/src/cli/remember.ts +339 -0
- package/src/cli/rename.ts +27 -33
- package/src/cli/secrets-password.ts +60 -0
- package/src/cli/secrets.ts +258 -0
- package/src/cli/setup.ts +300 -0
- package/src/cli/signing.ts +23 -0
- package/src/cli/text-editor.ts +268 -0
- package/src/cli/ui.ts +298 -0
- package/src/cli/version.ts +2 -0
- package/src/cli/versions.ts +158 -0
- package/src/config/address.ts +102 -0
- package/src/config/circuit.d.ts +30 -0
- package/src/config/common.ts +65 -0
- package/src/config/communities.ts +479 -0
- package/src/config/define.ts +684 -0
- package/src/config/device.ts +164 -0
- package/src/config/firewall.ts +150 -0
- package/src/config/index.ts +22 -0
- package/src/{schema → config}/interfaces.ts +121 -85
- package/src/config/keys.ts +434 -0
- package/src/config/management.ts +61 -0
- package/src/config/merge.ts +335 -0
- package/src/config/models.ts +70 -0
- package/src/config/monitoring.ts +64 -0
- package/src/config/network.ts +180 -0
- package/src/config/presets.ts +149 -0
- package/src/config/routing.ts +401 -0
- package/src/config/services.ts +51 -0
- package/src/config/switching.ts +84 -0
- package/src/config/system.ts +103 -0
- package/src/{schema → config}/time-zones.ts +1 -1
- package/src/core/address.ts +1 -1
- package/src/core/cabling.ts +8 -43
- package/src/core/graph.ts +40 -0
- package/src/{integrations → core}/registries.ts +5 -3
- package/src/core/secrets-file.ts +539 -0
- package/src/core/secrets.ts +83 -64
- package/src/core/topology.ts +103 -146
- package/src/core/utils.ts +12 -1
- package/src/model.ts +83 -0
- package/src/platforms/derive.ts +64 -37
- package/src/platforms/finding.ts +9 -0
- package/src/platforms/fortios/firewall.ts +76 -64
- package/src/platforms/fortios/index.ts +189 -125
- package/src/platforms/fortios/names.ts +51 -18
- package/src/platforms/fortios/render.ts +69 -62
- package/src/platforms/fortios/routing.ts +89 -58
- package/src/platforms/fortios/vdoms.ts +47 -16
- package/src/platforms/junos/TRAPS.md +2 -2
- package/src/platforms/junos/bgp.ts +63 -42
- package/src/platforms/junos/index.ts +122 -89
- package/src/platforms/junos/render.ts +139 -104
- package/src/platforms/platform.ts +10 -8
- package/src/platforms/raisecom-ros/TRAPS.md +1 -1
- package/src/platforms/raisecom-ros/index.ts +89 -70
- package/src/platforms/raisecom-ros/render.ts +49 -45
- package/src/platforms/routeros/TRAPS.md +3 -3
- package/src/platforms/routeros/index.ts +121 -60
- package/src/platforms/routeros/render.ts +214 -195
- package/src/platforms/vrp/TRAPS.md +1 -1
- package/src/platforms/vrp/index.ts +74 -50
- package/src/platforms/vrp/render.ts +67 -56
- package/src/project/project.ts +725 -0
- package/src/studio/app/env.d.ts +9 -0
- package/src/studio/app/index.html +18 -0
- package/src/studio/app/src/App.vue +95 -0
- package/src/studio/app/src/components/AddressesView.vue +38 -0
- package/src/studio/app/src/components/CablesView.vue +29 -0
- package/src/studio/app/src/components/CompareView.vue +160 -0
- package/src/studio/app/src/components/DataTable.vue +49 -0
- package/src/studio/app/src/components/DeviceView.vue +237 -0
- package/src/studio/app/src/components/DevicesView.vue +32 -0
- package/src/studio/app/src/components/Faceplate.vue +75 -0
- package/src/studio/app/src/components/GraphView.vue +282 -0
- package/src/studio/app/src/components/LineDiff.vue +24 -0
- package/src/studio/app/src/components/ProblemsView.vue +42 -0
- package/src/studio/app/src/components/SearchView.vue +72 -0
- package/src/studio/app/src/components/SecretsView.vue +112 -0
- package/src/studio/app/src/components/SessionsView.vue +35 -0
- package/src/studio/app/src/components/Settings.vue +95 -0
- package/src/studio/app/src/components/ToolsView.vue +118 -0
- package/src/studio/app/src/components/VlansView.vue +50 -0
- package/src/studio/app/src/lib/csv.ts +19 -0
- package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
- package/src/studio/app/src/lib/format.ts +38 -0
- package/src/studio/app/src/lib/net.ts +68 -0
- package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
- package/src/studio/app/src/lib/store.ts +71 -0
- package/src/studio/app/src/lib/tools.ts +217 -0
- package/src/studio/app/src/main.ts +9 -0
- package/src/studio/{public → app/src}/style.css +14 -4
- package/src/studio/app/tsconfig.json +17 -0
- package/src/studio/app/vite.config.ts +16 -0
- package/src/studio/assets.generated.ts +3 -0
- package/src/studio/data.ts +96 -188
- package/src/studio/git.ts +27 -43
- package/src/studio/server.ts +196 -107
- package/src/studio/types.ts +141 -0
- package/src/transport/console.ts +3 -3
- package/src/transport/ssh.ts +1 -1
- package/dist/chunks/index-3b343xva.js +0 -177
- package/dist/chunks/index-6wb41q67.js +0 -62
- package/dist/chunks/index-n7w3tg5y.js +0 -149
- package/dist/chunks/index-yzw86rc8.js +0 -921
- package/dist/chunks/new-w3zys5y4.js +0 -172
- package/dist/chunks/server-cxr9ynxc.js +0 -259
- package/dist/cli/index.js +0 -12922
- package/dist/helpers/index.js +0 -881
- package/dist/index.js +0 -28
- package/dist/types/core/address-space.d.ts +0 -62
- package/dist/types/core/address.d.ts +0 -44
- package/dist/types/core/cabling.d.ts +0 -41
- package/dist/types/core/cross-checks.d.ts +0 -28
- package/dist/types/core/define.d.ts +0 -79
- package/dist/types/core/project-files.d.ts +0 -12
- package/dist/types/core/schema-docs.d.ts +0 -25
- package/dist/types/core/secrets.d.ts +0 -62
- package/dist/types/core/topology.d.ts +0 -96
- package/dist/types/core/utils.d.ts +0 -10
- package/dist/types/core/validator.d.ts +0 -23
- package/dist/types/helpers/bgp.d.ts +0 -195
- package/dist/types/helpers/cables.d.ts +0 -69
- package/dist/types/helpers/checks.d.ts +0 -36
- package/dist/types/helpers/exchange.d.ts +0 -55
- package/dist/types/helpers/filters.d.ts +0 -51
- package/dist/types/helpers/index.d.ts +0 -14
- package/dist/types/helpers/route-server.d.ts +0 -52
- package/dist/types/helpers/upstreams.d.ts +0 -192
- package/dist/types/index.d.ts +0 -10
- package/dist/types/integrations/librenms.d.ts +0 -60
- package/dist/types/integrations/netbox.d.ts +0 -89
- package/dist/types/integrations/peeringdb.d.ts +0 -24
- package/dist/types/integrations/prometheus.d.ts +0 -24
- package/dist/types/integrations/registries.d.ts +0 -8
- package/dist/types/platforms/bgp-wait.d.ts +0 -24
- package/dist/types/platforms/catalog.d.ts +0 -109
- package/dist/types/platforms/derive.d.ts +0 -17
- package/dist/types/platforms/fortios/firewall.d.ts +0 -47
- package/dist/types/platforms/fortios/index.d.ts +0 -140
- package/dist/types/platforms/fortios/models.d.ts +0 -14
- package/dist/types/platforms/fortios/names.d.ts +0 -52
- package/dist/types/platforms/fortios/plan.d.ts +0 -114
- package/dist/types/platforms/fortios/render.d.ts +0 -77
- package/dist/types/platforms/fortios/routing.d.ts +0 -56
- package/dist/types/platforms/fortios/tree.d.ts +0 -37
- package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
- package/dist/types/platforms/index.d.ts +0 -13
- package/dist/types/platforms/junos/bgp.d.ts +0 -44
- package/dist/types/platforms/junos/index.d.ts +0 -114
- package/dist/types/platforms/junos/models.d.ts +0 -14
- package/dist/types/platforms/junos/render.d.ts +0 -154
- package/dist/types/platforms/line-diff.d.ts +0 -67
- package/dist/types/platforms/platform.d.ts +0 -135
- package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
- package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
- package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
- package/dist/types/platforms/routeros/export.d.ts +0 -59
- package/dist/types/platforms/routeros/index.d.ts +0 -116
- package/dist/types/platforms/routeros/models.d.ts +0 -18
- package/dist/types/platforms/routeros/plan.d.ts +0 -58
- package/dist/types/platforms/routeros/render.d.ts +0 -250
- package/dist/types/platforms/vrp/index.d.ts +0 -83
- package/dist/types/platforms/vrp/models.d.ts +0 -12
- package/dist/types/platforms/vrp/render.d.ts +0 -96
- package/dist/types/schema/common.d.ts +0 -30
- package/dist/types/schema/device.d.ts +0 -126
- package/dist/types/schema/firewall.d.ts +0 -131
- package/dist/types/schema/index.d.ts +0 -8
- package/dist/types/schema/interfaces.d.ts +0 -205
- package/dist/types/schema/policy.d.ts +0 -107
- package/dist/types/schema/routing.d.ts +0 -161
- package/dist/types/schema/system.d.ts +0 -198
- package/dist/types/schema/time-zones.d.ts +0 -2
- package/dist/types/transport/console.d.ts +0 -115
- package/dist/types/transport/path-check.d.ts +0 -32
- package/dist/types/transport/ssh.d.ts +0 -27
- package/src/cli/graph-format.ts +0 -91
- package/src/cli/new.ts +0 -234
- package/src/core/address-space.ts +0 -320
- package/src/core/cross-checks.ts +0 -369
- package/src/core/define.ts +0 -124
- package/src/core/project-files.ts +0 -35
- package/src/core/schema-docs.ts +0 -174
- package/src/core/validator.ts +0 -508
- package/src/helpers/bgp.ts +0 -473
- package/src/helpers/cables.ts +0 -74
- package/src/helpers/checks.ts +0 -201
- package/src/helpers/exchange.ts +0 -123
- package/src/helpers/filters.ts +0 -107
- package/src/helpers/index.ts +0 -25
- package/src/helpers/route-server.ts +0 -212
- package/src/helpers/upstreams.ts +0 -369
- package/src/index.ts +0 -13
- package/src/integrations/librenms.ts +0 -291
- package/src/integrations/netbox.ts +0 -620
- package/src/integrations/peeringdb.ts +0 -173
- package/src/integrations/prometheus.ts +0 -90
- package/src/schema/common.ts +0 -41
- package/src/schema/device.ts +0 -145
- package/src/schema/firewall.ts +0 -140
- package/src/schema/index.ts +0 -8
- package/src/schema/policy.ts +0 -115
- package/src/schema/routing.ts +0 -173
- package/src/schema/system.ts +0 -203
- package/src/studio/public/app.js +0 -186
- package/src/studio/public/compare.js +0 -250
- package/src/studio/public/dom.js +0 -138
- package/src/studio/public/evaluate.js +0 -687
- package/src/studio/public/export.js +0 -130
- package/src/studio/public/graph.js +0 -1541
- package/src/studio/public/index.html +0 -83
- package/src/studio/public/net.js +0 -134
- package/src/studio/public/tables.js +0 -1289
- package/src/studio/public/tools.js +0 -610
- package/templates/colocation/checks.ts +0 -53
- package/templates/colocation/circuit.config.ts +0 -9
- package/templates/colocation/edge-01.ts +0 -133
- package/templates/colocation/routing.ts +0 -13
- package/templates/colocation/tenancy.ts +0 -76
- package/templates/colocation/tenants/acme.ts +0 -19
- package/templates/colocation/tenants/index.ts +0 -6
- package/templates/colocation/tor-01.ts +0 -33
- package/templates/edge-router/checks.ts +0 -20
- package/templates/edge-router/circuit.config.ts +0 -8
- package/templates/edge-router/edge-01.ts +0 -110
- package/templates/edge-router/routing.ts +0 -13
- package/templates/project/AGENTS.md +0 -51
- package/templates/project/CLAUDE.md +0 -1
- package/templates/project/README.md +0 -16
- package/templates/project/gitignore +0 -5
- package/templates/project/tsconfig.json +0 -13
- package/templates/single-site/checks.ts +0 -26
- package/templates/single-site/circuit.config.ts +0 -8
- package/templates/single-site/router.ts +0 -86
- package/templates/single-site/site.ts +0 -18
- package/templates/single-site/switch.ts +0 -34
- /package/src/studio/{public → app/public}/favicon.svg +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
- /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
|
+
});
|