@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
package/src/schema/policy.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type { Family, IP, Prefix } from "./common";
|
|
2
|
-
|
|
3
|
-
/** A standard BGP community, `asn:value`, for example `64500:100`. Each part is 0 to 65535. */
|
|
4
|
-
export type StandardCommunity = `${number}:${number}`;
|
|
5
|
-
|
|
6
|
-
/** A large BGP community, `asn:function:parameter`, for example `64500:1:6939`. Each part is 0 to 4294967295. */
|
|
7
|
-
export type LargeCommunity = `${number}:${number}:${number}`;
|
|
8
|
-
|
|
9
|
-
/** A standard community to remove. `*` stands for any value of a part, as in `64500:*`. */
|
|
10
|
-
export type StandardCommunityPattern = `${number | "*"}:${number | "*"}`;
|
|
11
|
-
|
|
12
|
-
/** A large community to remove. `*` stands for any value of a part, as in `64500:*:*`. */
|
|
13
|
-
export type LargeCommunityPattern = `${number | "*"}:${number | "*"}:${number | "*"}`;
|
|
14
|
-
|
|
15
|
-
/** Registries a prefix set can be fetched from. */
|
|
16
|
-
export type Registry = "ripe-stat";
|
|
17
|
-
|
|
18
|
-
/** Prefixes listed here, or fetched from a registry by `refresh` and never typed by hand. */
|
|
19
|
-
export type PrefixSet =
|
|
20
|
-
| {
|
|
21
|
-
/** The prefixes, of one family. */
|
|
22
|
-
prefixes: readonly Prefix[];
|
|
23
|
-
}
|
|
24
|
-
| {
|
|
25
|
-
source: {
|
|
26
|
-
/** Where the prefixes come from. */
|
|
27
|
-
registry: Registry;
|
|
28
|
-
/** What to ask for, for example `AS7713`. */
|
|
29
|
-
query: string;
|
|
30
|
-
/** Which family to keep. */
|
|
31
|
-
family: Family;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** Conditions on a route. All must be true. An empty match is true for every route. */
|
|
36
|
-
export type Match<S extends string> = {
|
|
37
|
-
/** Only routes of this family. */
|
|
38
|
-
family?: Family;
|
|
39
|
-
/** Exactly this prefix. */
|
|
40
|
-
prefix?: Prefix;
|
|
41
|
-
/** Any prefix in this set. */
|
|
42
|
-
prefix_set?: S;
|
|
43
|
-
/** Prefix length within this range, inclusive. */
|
|
44
|
-
prefix_length?: { min?: number; max?: number };
|
|
45
|
-
/** AS path length within this range, inclusive. Every AS counts, prepends included. */
|
|
46
|
-
as_path_length?: { min?: number; max?: number };
|
|
47
|
-
/** The AS path holds this AS anywhere: the route came from it or through it. */
|
|
48
|
-
through_as?: number;
|
|
49
|
-
/** The route's origin validation state. */
|
|
50
|
-
rpki?: "valid" | "invalid" | "not-found";
|
|
51
|
-
/** Has this standard community, `asn:value`. */
|
|
52
|
-
community?: StandardCommunity;
|
|
53
|
-
/** Has this large community, `asn:function:parameter`. */
|
|
54
|
-
large_community?: LargeCommunity;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/** Changes to a route. In community lists to remove, `*` stands for any value of a field, as in `64500:*:*`. */
|
|
58
|
-
export type Sets = {
|
|
59
|
-
/** Preference among routes to the same prefix within our AS. Higher wins. */
|
|
60
|
-
local_pref?: number;
|
|
61
|
-
/** Multi-exit discriminator offered to the neighbor. Lower wins. */
|
|
62
|
-
med?: number;
|
|
63
|
-
/** Prepend our own AS this many times, to make the path look longer. */
|
|
64
|
-
prepend?: number;
|
|
65
|
-
/** Replace the route's standard communities with these. */
|
|
66
|
-
communities?: readonly StandardCommunity[];
|
|
67
|
-
/** Add these standard communities. */
|
|
68
|
-
add_communities?: readonly StandardCommunity[];
|
|
69
|
-
/** Remove standard communities matching these. A part may be `*`. */
|
|
70
|
-
remove_communities?: readonly StandardCommunityPattern[];
|
|
71
|
-
/** Replace the route's large communities with these. */
|
|
72
|
-
large_communities?: readonly LargeCommunity[];
|
|
73
|
-
/** Add these large communities. */
|
|
74
|
-
add_large_communities?: readonly LargeCommunity[];
|
|
75
|
-
/** Remove large communities matching these. A part may be `*`. */
|
|
76
|
-
remove_large_communities?: readonly LargeCommunityPattern[];
|
|
77
|
-
/** Next hop for the route. */
|
|
78
|
-
next_hop?: IP;
|
|
79
|
-
/** Source address for traffic the device itself sends to the route. Applies to routes of the same family as the address. */
|
|
80
|
-
preferred_source?: IP;
|
|
81
|
-
/** Drop traffic to the route on this device, instead of forwarding it. */
|
|
82
|
-
blackhole?: true;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Run another policy at this point. A route it accepts or rejects stops there; any other route goes on to the next rule. A call holds nothing else: to call a policy for some routes only, put the match inside that policy.
|
|
87
|
-
*/
|
|
88
|
-
export type CallRule<P extends string> = {
|
|
89
|
-
/** Shown beside the rule on the device. */
|
|
90
|
-
description?: string;
|
|
91
|
-
/** The policy to run. */
|
|
92
|
-
call: P;
|
|
93
|
-
match?: never;
|
|
94
|
-
set?: never;
|
|
95
|
-
action?: never;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/** Change and decide on the routes that match. A rule without `action` changes the route and moves on to the next rule. */
|
|
99
|
-
export type MatchRule<S extends string> = {
|
|
100
|
-
/** Shown beside the rule on the device. */
|
|
101
|
-
description?: string;
|
|
102
|
-
call?: never;
|
|
103
|
-
/** Which routes the rule applies to. Every route when absent. */
|
|
104
|
-
match?: Match<S>;
|
|
105
|
-
/** What to change on a matching route. */
|
|
106
|
-
set?: Sets;
|
|
107
|
-
/** Accept or reject a matching route, ending the policy for it. */
|
|
108
|
-
action?: "accept" | "reject";
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/** One step of a route policy: a call to another policy, or a rule that matches routes, changes them and may decide on them. */
|
|
112
|
-
export type PolicyRule<P extends string, S extends string> = CallRule<P> | MatchRule<S>;
|
|
113
|
-
|
|
114
|
-
/** Rules evaluated in order, first decision wins. A route no rule accepts is rejected. */
|
|
115
|
-
export type RoutePolicy<P extends string, S extends string> = readonly PolicyRule<P, S>[];
|
package/src/schema/routing.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import type { Family, IP, Prefix, Secret } from "./common";
|
|
2
|
-
|
|
3
|
-
/** A route the device has without learning it. */
|
|
4
|
-
export type StaticRoute<I extends string, F extends string, T extends string = never> = {
|
|
5
|
-
/** Destination. */
|
|
6
|
-
prefix: Prefix;
|
|
7
|
-
/** Next-hop address. */
|
|
8
|
-
via?: IP;
|
|
9
|
-
/** Next-hop interface, for a link without a next-hop address. */
|
|
10
|
-
interface?: I;
|
|
11
|
-
/** Discard matching traffic. The usual way to originate an aggregate. */
|
|
12
|
-
blackhole?: true;
|
|
13
|
-
/** Withdraw the route while BFD finds `via` dead, and put it back once `via` answers again. Needs `via`. */
|
|
14
|
-
bfd?: true;
|
|
15
|
-
/** Preference against other routes to the same prefix. Lower wins. 1 when absent. */
|
|
16
|
-
distance?: number;
|
|
17
|
-
/** The VRF the route is in. The main table when absent. */
|
|
18
|
-
vrf?: F;
|
|
19
|
-
/** The table the route is in: `main`, or one of `routing.tables`. The main table when absent; write `main` to say so. A route has a `vrf` or a `table`, not both. */
|
|
20
|
-
table?: T | "main";
|
|
21
|
-
/** Why the route exists. */
|
|
22
|
-
description?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* What a BGP neighbor is to us, with the roles RFC 9234 defines: `provider`, a transit we buy from; `customer`, one we sell transit to; `peer`, one we exchange our own routes with; `rs`, an exchange's route server; `rs-client`, a network using our route server.
|
|
27
|
-
*/
|
|
28
|
-
export type BgpRole = "provider" | "customer" | "peer" | "rs" | "rs-client";
|
|
29
|
-
|
|
30
|
-
/** Settings a BGP neighbor can set itself or take from its group. */
|
|
31
|
-
export type NeighborSettings<P extends string, T extends string = never> = {
|
|
32
|
-
/** The neighbor's AS. Our own AS makes the session internal. */
|
|
33
|
-
remote_as?: number;
|
|
34
|
-
/** Who the neighbor is. */
|
|
35
|
-
description?: string;
|
|
36
|
-
/** Source address for the session. The outgoing interface's address when absent. */
|
|
37
|
-
local_address?: IP;
|
|
38
|
-
/** Policy for routes learned. Nothing is accepted when absent. */
|
|
39
|
-
import?: P;
|
|
40
|
-
/** Policy for routes announced. Nothing is announced when absent. */
|
|
41
|
-
export?: P;
|
|
42
|
-
/** What the neighbor is to us, such as `provider` for a transit. Both sides then reject a route leak, as RFC 9234 describes. */
|
|
43
|
-
role?: BgpRole;
|
|
44
|
-
/** The neighbor is more than one hop away. */
|
|
45
|
-
multihop?: boolean;
|
|
46
|
-
/** TCP MD5 password shared with the neighbor. */
|
|
47
|
-
password?: Secret;
|
|
48
|
-
/** Close the session when the neighbor announces more prefixes than this. It stays closed until cleared by hand, on RouterOS with `/routing bgp session clear <session> flag=limit-exceeded`. */
|
|
49
|
-
max_prefixes?: number;
|
|
50
|
-
/** Seconds without a message before the session is declared down. */
|
|
51
|
-
hold_time?: number;
|
|
52
|
-
/** Seconds between keepalive messages. */
|
|
53
|
-
keepalive?: number;
|
|
54
|
-
/** Families exchanged. The family of the neighbor's address when absent. */
|
|
55
|
-
families?: readonly Family[];
|
|
56
|
-
/** Wait for the neighbor to connect instead of connecting to it. */
|
|
57
|
-
passive?: boolean;
|
|
58
|
-
/** Detect a dead link in under a second with BFD. */
|
|
59
|
-
bfd?: boolean;
|
|
60
|
-
/** The table the routes learned are installed in: `main`, or one of `routing.tables`. The main table when absent; write `main` to say so. On RouterOS the session also announces from that table, so each prefix of `networks` it announces needs a route in the table too, and a running session takes a new table only when it restarts. */
|
|
61
|
-
table?: T | "main";
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/** One BGP session. Settings it does not set come from its group. */
|
|
65
|
-
export type Neighbor<P extends string, G extends string, T extends string = never> = NeighborSettings<P, T> & {
|
|
66
|
-
/** The neighbor's address. */
|
|
67
|
-
address: IP;
|
|
68
|
-
/** The group this neighbor takes a setting from when it does not set that setting itself. */
|
|
69
|
-
group?: NoInfer<G>;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/** BGP on this device. */
|
|
73
|
-
export type Bgp<P extends string, G extends string, T extends string = never> = {
|
|
74
|
-
/** Our AS number. */
|
|
75
|
-
asn: number;
|
|
76
|
-
/** Prefixes this device originates. Each needs a route, for example a static blackhole. Export policy still decides who receives them. */
|
|
77
|
-
networks?: readonly Prefix[];
|
|
78
|
-
/** Settings shared by several neighbors, keyed by group name. */
|
|
79
|
-
groups?: Record<G, NeighborSettings<P, T>>;
|
|
80
|
-
/** Sessions keyed by name. */
|
|
81
|
-
neighbors?: Record<string, Neighbor<P, G, T>>;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/** How fast BFD notices a dead neighbor: after `multiplier` packets in a row are missed, so in `interval` times `multiplier` milliseconds. */
|
|
85
|
-
export type Bfd = {
|
|
86
|
-
/** Milliseconds between the packets this device sends, and the shortest gap it asks of the neighbor. */
|
|
87
|
-
interval: number;
|
|
88
|
-
/** How many packets in a row may be missed before the neighbor is declared down. */
|
|
89
|
-
multiplier: number;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/** A route origin validation server, RFC 8210. */
|
|
93
|
-
export type RpkiServer = {
|
|
94
|
-
/** Where the validator listens. */
|
|
95
|
-
address: IP;
|
|
96
|
-
/** Its RTR port, often 323 or 8282. */
|
|
97
|
-
port: number;
|
|
98
|
-
/** Who runs it, or why it is here. */
|
|
99
|
-
description?: string;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/** OSPF on one interface. */
|
|
103
|
-
export type OspfInterface = {
|
|
104
|
-
/** Cost of sending through this interface. Lower is preferred. Derived from speed when absent. */
|
|
105
|
-
cost?: number;
|
|
106
|
-
/** Advertise the interface's network but form no adjacency on it. */
|
|
107
|
-
passive?: boolean;
|
|
108
|
-
/** How the link is treated. Broadcast when absent. */
|
|
109
|
-
network?: "broadcast" | "point-to-point";
|
|
110
|
-
/** Detect a dead neighbor in under a second with BFD. */
|
|
111
|
-
bfd?: boolean;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/** An OSPF area ID, written as four numbers with dots between them, for example `0.0.0.0` for the backbone. */
|
|
115
|
-
export type OspfAreaId = `${number}.${number}.${number}.${number}`;
|
|
116
|
-
|
|
117
|
-
/** OSPF, version 2 for IPv4 and version 3 for IPv6. */
|
|
118
|
-
export type Ospf<I extends string> = {
|
|
119
|
-
/** Which versions run. IPv4 when absent. */
|
|
120
|
-
families?: readonly Family[];
|
|
121
|
-
/** Areas keyed by area ID, for example `0.0.0.0`, each with the interfaces in it. */
|
|
122
|
-
areas: Record<OspfAreaId, { interfaces: Partial<Record<I, OspfInterface>> }>;
|
|
123
|
-
/** Routes from other sources announced into OSPF. */
|
|
124
|
-
redistribute?: readonly ("connected" | "static")[];
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/** A routing table beside the main one. It holds no interfaces: routes go in from static routes and BGP neighbors that name it in `table`, and `routing.rules` send traffic to it. */
|
|
128
|
-
export type RoutingTable = {
|
|
129
|
-
/** What the table is for. */
|
|
130
|
-
description?: string;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
/** Traffic a routing rule matches: all of what is given. Every packet when absent. */
|
|
134
|
-
export type RoutingRuleMatch<I extends string> = {
|
|
135
|
-
/** Source prefix. */
|
|
136
|
-
src?: Prefix;
|
|
137
|
-
/** Destination prefix. */
|
|
138
|
-
dst?: Prefix;
|
|
139
|
-
/** The interface the traffic arrives on. */
|
|
140
|
-
in_interface?: I;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/** One routing rule: traffic it matches is routed by `table` instead of the main table. */
|
|
144
|
-
export type RoutingRule<I extends string, T extends string> = {
|
|
145
|
-
/** Why the rule exists. */
|
|
146
|
-
description?: string;
|
|
147
|
-
/** What the traffic must match. Every packet when absent, so such a rule is the last one. */
|
|
148
|
-
match?: RoutingRuleMatch<I>;
|
|
149
|
-
/** The table that routes the traffic: one of `routing.tables`, or `main`. */
|
|
150
|
-
table: T | "main";
|
|
151
|
-
/** When `table` has no route for a packet: `none` drops it as unroutable, `main` routes it by the main table instead. `none` when absent. Not for a rule whose table is `main`. */
|
|
152
|
-
fallback?: "none" | "main";
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/** Routing on this device. */
|
|
156
|
-
export type Routing<P extends string, G extends string, I extends string, F extends string, T extends string = never> = {
|
|
157
|
-
/** Identifies this router to its neighbors. */
|
|
158
|
-
router_id?: IP;
|
|
159
|
-
/** Routing tables beside the main one, keyed by name. Not `main`, and not the name of a VRF. */
|
|
160
|
-
tables?: Record<T, RoutingTable>;
|
|
161
|
-
/** Rules that route matching traffic by another table, such as traffic from one block by its own transits' default. In order: the first that matches decides, and traffic no rule matches is routed by the main table. */
|
|
162
|
-
rules?: readonly RoutingRule<I, NoInfer<T>>[];
|
|
163
|
-
/** Routes the device has without learning them. */
|
|
164
|
-
static?: readonly StaticRoute<I, F, NoInfer<T>>[];
|
|
165
|
-
/** BGP sessions and what they exchange. */
|
|
166
|
-
bgp?: Bgp<P, G, NoInfer<T>>;
|
|
167
|
-
/** OSPF areas and interfaces. */
|
|
168
|
-
ospf?: Ospf<I>;
|
|
169
|
-
/** Validators answering route origin validation. */
|
|
170
|
-
rpki?: readonly RpkiServer[];
|
|
171
|
-
/** BFD timers for every BFD session on the device: BGP, OSPF and static routes. The platform's own when absent; Junos has none, so Circuit sends every 300 ms there. Both ends of a link should agree. */
|
|
172
|
-
bfd?: Bfd;
|
|
173
|
-
};
|
package/src/schema/system.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import type { IP, MacAddress, Prefix, Secret, Severity } from "./common";
|
|
2
|
-
import type { TimeZone } from "./time-zones";
|
|
3
|
-
|
|
4
|
-
/** A software release track, as RouterOS names them. `long-term` changes least; `development` is the newest and least tested. */
|
|
5
|
-
export type ReleaseChannel = "long-term" | "stable" | "testing" | "development";
|
|
6
|
-
|
|
7
|
-
/** How bad an alarm is, most severe first, as ITU-T X.733 ranks them. */
|
|
8
|
-
export type AlarmSeverity = "critical" | "major" | "minor" | "warning";
|
|
9
|
-
|
|
10
|
-
/** Device-wide settings. */
|
|
11
|
-
export type System = {
|
|
12
|
-
/** IANA zone, for example `Asia/Jakarta`. The editor suggests every name. UTC when absent. */
|
|
13
|
-
timezone?: TimeZone;
|
|
14
|
-
/** Shown at login. */
|
|
15
|
-
banner?: string;
|
|
16
|
-
/** Resolvers the device uses. */
|
|
17
|
-
dns?: {
|
|
18
|
-
/** Resolvers, in order of preference. */
|
|
19
|
-
servers: readonly IP[];
|
|
20
|
-
/** Answer queries from others. Off when absent. */
|
|
21
|
-
serve?: boolean;
|
|
22
|
-
};
|
|
23
|
-
/** How the device keeps time. */
|
|
24
|
-
ntp?: {
|
|
25
|
-
/** Time sources, by address or name. */
|
|
26
|
-
servers: readonly string[];
|
|
27
|
-
/** Answer time queries from others. Off when absent. */
|
|
28
|
-
serve?: boolean;
|
|
29
|
-
};
|
|
30
|
-
/** Where log messages go. */
|
|
31
|
-
logging?: {
|
|
32
|
-
/** Kept on the device's own storage, from `level` up. */
|
|
33
|
-
local?: {
|
|
34
|
-
/** The least severe message kept. */
|
|
35
|
-
level: Severity;
|
|
36
|
-
/** How many rotated files to keep. */
|
|
37
|
-
files?: number;
|
|
38
|
-
/** Lines in each file before it rotates. */
|
|
39
|
-
lines_per_file?: number;
|
|
40
|
-
};
|
|
41
|
-
/** Sent to syslog collectors, each from its own `level` up. */
|
|
42
|
-
remote?: readonly {
|
|
43
|
-
address: IP;
|
|
44
|
-
/** UDP port. 514 when absent. */
|
|
45
|
-
port?: number;
|
|
46
|
-
/** The least severe message sent. */
|
|
47
|
-
level: Severity;
|
|
48
|
-
}[];
|
|
49
|
-
};
|
|
50
|
-
/** Software release track the device updates from. `stable` when absent. */
|
|
51
|
-
release_channel?: ReleaseChannel;
|
|
52
|
-
/** Alarms raised at another severity than the platform's own, keyed by the alarm's name as the platform spells it, such as `hwOpticalUnAuthorized` on VRP. */
|
|
53
|
-
alarms?: Record<string, { severity: AlarmSeverity }>;
|
|
54
|
-
/** Read optic temperature, power and voltage. */
|
|
55
|
-
transceiver_monitoring?: boolean;
|
|
56
|
-
/** Host behaviour of the IP stack. */
|
|
57
|
-
ip?: {
|
|
58
|
-
/** Send ICMP redirects. Off when absent. */
|
|
59
|
-
icmp_redirects?: boolean;
|
|
60
|
-
/** Answer a TCP SYN flood with SYN cookies. Off when absent. */
|
|
61
|
-
syn_cookies?: boolean;
|
|
62
|
-
};
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/** A login on the device. */
|
|
66
|
-
export type User = {
|
|
67
|
-
/** What the user may do. */
|
|
68
|
-
role: "admin" | "operator" | "read-only";
|
|
69
|
-
/** The login password. */
|
|
70
|
-
password: Secret;
|
|
71
|
-
/** Who the account is for. */
|
|
72
|
-
description?: string;
|
|
73
|
-
/** Public keys accepted instead of the password over SSH. */
|
|
74
|
-
ssh_keys?: readonly string[];
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/** A way into the device. Absent means turned off. */
|
|
78
|
-
export type Service = {
|
|
79
|
-
/** Sources allowed. `management.allow` when absent. */
|
|
80
|
-
allow?: readonly Prefix[];
|
|
81
|
-
/** TCP or UDP port. The protocol's own when absent. */
|
|
82
|
-
port?: number;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/** One SNMPv3 user. */
|
|
86
|
-
export type SnmpUser = {
|
|
87
|
-
/** How messages are authenticated. */
|
|
88
|
-
auth: "sha1" | "sha256";
|
|
89
|
-
/** The authentication passphrase. */
|
|
90
|
-
auth_password: Secret;
|
|
91
|
-
/** How messages are encrypted. */
|
|
92
|
-
privacy: "aes128" | "des";
|
|
93
|
-
/** The encryption passphrase. */
|
|
94
|
-
privacy_password: Secret;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/** How the device is reached and managed. */
|
|
98
|
-
export type Management<I extends string, C extends string> = {
|
|
99
|
-
/** Sources allowed to every service that does not set its own. */
|
|
100
|
-
allow?: readonly Prefix[];
|
|
101
|
-
/** The ports and interfaces the services answer on; every other one answers ping alone. Every one with an address when absent. */
|
|
102
|
-
interfaces?: readonly I[];
|
|
103
|
-
/** Secure shell. */
|
|
104
|
-
ssh?: Service & {
|
|
105
|
-
/** Also offer legacy ciphers, MACs and key exchanges. Off when absent. */
|
|
106
|
-
weak_crypto?: boolean;
|
|
107
|
-
/** Seconds a login may take. */
|
|
108
|
-
auth_timeout?: number;
|
|
109
|
-
/** Failed attempts allowed per connection. */
|
|
110
|
-
auth_retries?: number;
|
|
111
|
-
};
|
|
112
|
-
/** Unencrypted remote login. */
|
|
113
|
-
telnet?: Service;
|
|
114
|
-
/** Web interface, unencrypted. */
|
|
115
|
-
http?: Service;
|
|
116
|
-
/** Web interface over TLS. */
|
|
117
|
-
https?: Service & {
|
|
118
|
-
/** One of the device's `certificates`. */
|
|
119
|
-
certificate?: C;
|
|
120
|
-
};
|
|
121
|
-
/** The platform's own API, unencrypted. */
|
|
122
|
-
api?: Service;
|
|
123
|
-
/** The platform's own API over TLS. */
|
|
124
|
-
api_tls?: Service & {
|
|
125
|
-
/** One of the device's `certificates`. */
|
|
126
|
-
certificate?: C;
|
|
127
|
-
};
|
|
128
|
-
/** File transfer, unencrypted. */
|
|
129
|
-
ftp?: Service;
|
|
130
|
-
/** The platform's own management protocol. `interfaces` also allows it at layer 2 on those interfaces. */
|
|
131
|
-
native?: Service & { interfaces?: readonly I[] };
|
|
132
|
-
/** Polling by SNMP. */
|
|
133
|
-
snmp?: Service & {
|
|
134
|
-
/** Read-only community for version 2c. Version 2c is off when absent. */
|
|
135
|
-
community?: Secret;
|
|
136
|
-
/** Version 3 users, keyed by name. */
|
|
137
|
-
users?: Record<string, SnmpUser>;
|
|
138
|
-
/** Who to contact about the device. */
|
|
139
|
-
contact?: string;
|
|
140
|
-
/** Where the device is. */
|
|
141
|
-
location?: string;
|
|
142
|
-
};
|
|
143
|
-
/** Console login uses this password. The device's users when absent. */
|
|
144
|
-
console?: { password: Secret };
|
|
145
|
-
/** Password for privileged mode, on platforms that have one. */
|
|
146
|
-
privilege_password?: Secret;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/** A DHCP server for one network. */
|
|
150
|
-
export type DhcpServer<I extends string> = {
|
|
151
|
-
/** Where it answers. */
|
|
152
|
-
interface: I;
|
|
153
|
-
/** The network it serves. */
|
|
154
|
-
network: Prefix;
|
|
155
|
-
/** Default gateway given to clients. */
|
|
156
|
-
gateway: IP;
|
|
157
|
-
/** Resolvers given to clients. */
|
|
158
|
-
dns?: readonly IP[];
|
|
159
|
-
/** First and last address given to clients. */
|
|
160
|
-
pool: readonly [IP, IP];
|
|
161
|
-
/** Seconds a lease lasts. */
|
|
162
|
-
lease_time?: number;
|
|
163
|
-
/** Addresses fixed to a MAC. */
|
|
164
|
-
reservations?: readonly {
|
|
165
|
-
address: IP;
|
|
166
|
-
/** The client's MAC address. */
|
|
167
|
-
mac: MacAddress;
|
|
168
|
-
/** Whose address it is. */
|
|
169
|
-
description?: string;
|
|
170
|
-
}[];
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
/** Forward DHCP requests heard on an interface to servers elsewhere. */
|
|
174
|
-
export type DhcpRelay<I extends string> = {
|
|
175
|
-
/** Where requests are heard. */
|
|
176
|
-
interface: I;
|
|
177
|
-
/** Where they are forwarded. */
|
|
178
|
-
servers: readonly IP[];
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
/** A certificate the device presents or trusts. */
|
|
182
|
-
export type Certificate = {
|
|
183
|
-
/** The certificate, PEM. Public, so it is kept in the repository. */
|
|
184
|
-
certificate: string;
|
|
185
|
-
/** Its private key: a PEM file through `secretFile`, or PEM base64-encoded as one line through `secret`. A certificate the device only trusts, such as a CA, has none. */
|
|
186
|
-
private_key?: Secret;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
/** Export flow records or packet samples to collectors. */
|
|
190
|
-
export type FlowExport<I extends string> = {
|
|
191
|
-
/** The record format. */
|
|
192
|
-
protocol: "sflow" | "netflow-v9" | "ipfix";
|
|
193
|
-
/** Where records are sent. */
|
|
194
|
-
collectors: readonly {
|
|
195
|
-
address: IP;
|
|
196
|
-
/** UDP port. 6343 for sFlow and 2055 otherwise when absent. */
|
|
197
|
-
port?: number;
|
|
198
|
-
}[];
|
|
199
|
-
/** Sample one packet in this many. Every packet when absent, on a platform that can export every packet. */
|
|
200
|
-
sampling?: number;
|
|
201
|
-
/** Where traffic is observed. Every interface when absent. */
|
|
202
|
-
interfaces?: readonly I[];
|
|
203
|
-
};
|
package/src/studio/public/app.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
/// <reference lib="dom" />
|
|
3
|
-
// Studio's page: loads what the server gathered from the config, shows the place the address names, and loads again whenever the server says the config changed.
|
|
4
|
-
|
|
5
|
-
import { Dom } from "./dom.js";
|
|
6
|
-
import { Graph } from "./graph.js";
|
|
7
|
-
import { Tables } from "./tables.js";
|
|
8
|
-
import { Tools } from "./tools.js";
|
|
9
|
-
import { Compare } from "./compare.js";
|
|
10
|
-
|
|
11
|
-
/** @typedef {import("../data").StudioData} StudioData */
|
|
12
|
-
|
|
13
|
-
export class App {
|
|
14
|
-
static #views = Dom.byId("views");
|
|
15
|
-
static #search = /** @type {HTMLInputElement} */ (Dom.byId("search"));
|
|
16
|
-
static #summary = Dom.byId("summary");
|
|
17
|
-
static #live = Dom.byId("live");
|
|
18
|
-
static #banner = Dom.byId("banner");
|
|
19
|
-
static #graphView = Dom.byId("graph-view");
|
|
20
|
-
static #tablesView = Dom.byId("tables-view");
|
|
21
|
-
static #tables = Dom.byId("tables");
|
|
22
|
-
static #problems = Dom.byId("problem-count");
|
|
23
|
-
|
|
24
|
-
/** What the server gathered last. @type {StudioData | undefined} */
|
|
25
|
-
static #studio;
|
|
26
|
-
/** The address last shown, so drawing the same place again keeps where the reader had scrolled to. */
|
|
27
|
-
static #shownRoute = "";
|
|
28
|
-
|
|
29
|
-
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
30
|
-
static #searching;
|
|
31
|
-
|
|
32
|
-
static #showSummary() {
|
|
33
|
-
const studio = App.#studio;
|
|
34
|
-
if (!studio) return;
|
|
35
|
-
|
|
36
|
-
const { graph } = studio;
|
|
37
|
-
const devices = studio.devices.length;
|
|
38
|
-
const errors = graph.findings.filter((finding) => finding.level === "error").length;
|
|
39
|
-
const warnings = graph.findings.length - errors;
|
|
40
|
-
const drifted = Object.values(graph.drift).filter((drift) => drift.status === "differs").length;
|
|
41
|
-
|
|
42
|
-
const count = Tables.problemCount(studio);
|
|
43
|
-
App.#problems.textContent = count ? String(count) : "";
|
|
44
|
-
|
|
45
|
-
const items = [
|
|
46
|
-
{ value: devices, label: devices === 1 ? "device" : "devices", state: "neutral" },
|
|
47
|
-
{ value: errors, label: errors === 1 ? "error" : "errors", state: errors ? "error" : "quiet" },
|
|
48
|
-
{ value: warnings, label: warnings === 1 ? "warning" : "warnings", state: warnings ? "warning" : "quiet" },
|
|
49
|
-
{ value: drifted, label: "unlike the snapshot", state: drifted ? "changed" : "quiet" },
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
App.#summary.replaceChildren(
|
|
53
|
-
...items.map(({ value, label, state }) => {
|
|
54
|
-
const item = document.createElement("span");
|
|
55
|
-
item.className = `summary-item ${state}`;
|
|
56
|
-
item.append(String(value), ` ${label}`);
|
|
57
|
-
return item;
|
|
58
|
-
})
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Show the place the address names, such as `#graph/bgp` or `#devices/edge-01/ports`. */
|
|
63
|
-
static #route() {
|
|
64
|
-
const studio = App.#studio;
|
|
65
|
-
if (!studio) return;
|
|
66
|
-
|
|
67
|
-
const parts = location.hash.slice(1).split("/").map(decodeURIComponent).filter(Boolean);
|
|
68
|
-
const [view] = parts;
|
|
69
|
-
|
|
70
|
-
// An address from before the tables, such as `#bgp`, still opens that layer.
|
|
71
|
-
const isGraph = !view || view === "graph" || Graph.isLayer(view);
|
|
72
|
-
const asked = view === "graph" ? parts[1] : view;
|
|
73
|
-
|
|
74
|
-
const moved = location.hash !== App.#shownRoute;
|
|
75
|
-
App.#shownRoute = location.hash;
|
|
76
|
-
|
|
77
|
-
for (const button of App.#views.querySelectorAll("button")) {
|
|
78
|
-
const pressed = isGraph ? button.dataset.view === "graph" : button.dataset.view === view;
|
|
79
|
-
button.setAttribute("aria-pressed", String(pressed));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
App.#graphView.hidden = !isGraph;
|
|
83
|
-
App.#tablesView.hidden = isGraph;
|
|
84
|
-
|
|
85
|
-
if (view === "search") {
|
|
86
|
-
if (App.#search.value !== (parts[1] ?? "")) App.#search.value = parts[1] ?? "";
|
|
87
|
-
} else if (document.activeElement !== App.#search) {
|
|
88
|
-
App.#search.value = "";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
App.#showSummary();
|
|
92
|
-
|
|
93
|
-
if (isGraph) {
|
|
94
|
-
Graph.show(Graph.isLayer(asked) ? asked : Graph.currentLayer());
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const tables = App.#tables;
|
|
99
|
-
const scrolled = tables.scrollTop;
|
|
100
|
-
if (view === "tools") Tools.show(parts, tables, studio);
|
|
101
|
-
else if (view === "compare") Compare.show(parts, tables, studio);
|
|
102
|
-
else Tables.show(parts, tables, studio);
|
|
103
|
-
tables.scrollTop = moved ? 0 : scrolled;
|
|
104
|
-
|
|
105
|
-
// A new place comes in; the same place drawn again, because the config changed, stays still.
|
|
106
|
-
if (moved) Dom.enter(tables);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Load what Studio shows, at the start and whenever the server says the config changed. */
|
|
110
|
-
static async #load() {
|
|
111
|
-
/** @type {(StudioData & { error?: undefined }) | { error: string }} */
|
|
112
|
-
let next;
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
next = await (await fetch("data", { cache: "no-store" })).json();
|
|
116
|
-
} catch (error) {
|
|
117
|
-
next = { error: `could not reach circuit studio: ${error instanceof Error ? error.message : String(error)}` };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (next.error !== undefined) {
|
|
121
|
-
App.#banner.textContent = `The config could not be read, so what is shown is the last that could:\n${next.error}`;
|
|
122
|
-
App.#banner.hidden = false;
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
App.#banner.hidden = true;
|
|
127
|
-
App.#studio = next;
|
|
128
|
-
Graph.set(next.graph);
|
|
129
|
-
App.#route();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Wire up the page and load what it shows. */
|
|
133
|
-
static start() {
|
|
134
|
-
for (const button of App.#views.querySelectorAll("button")) {
|
|
135
|
-
const view = button.dataset.view ?? "graph";
|
|
136
|
-
button.addEventListener("click", () => Dom.go(view === "graph" ? `graph/${Graph.currentLayer()}` : view));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
window.addEventListener("hashchange", App.#route);
|
|
140
|
-
|
|
141
|
-
// A menu closes once one of its items is chosen, or when the reader clicks anywhere else.
|
|
142
|
-
document.addEventListener("click", (event) => {
|
|
143
|
-
const target = event.target instanceof Element ? event.target : undefined;
|
|
144
|
-
|
|
145
|
-
for (const menu of document.querySelectorAll("details.menu[open]")) {
|
|
146
|
-
if (!target || !menu.contains(target) || target.closest(".menu-items button")) menu.removeAttribute("open");
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
App.#search.addEventListener("input", () => {
|
|
151
|
-
clearTimeout(App.#searching);
|
|
152
|
-
|
|
153
|
-
const query = App.#search.value.trim();
|
|
154
|
-
App.#searching = setTimeout(() => Dom.go(query ? `search/${encodeURIComponent(query)}` : "devices"), 200);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const events = new EventSource("events");
|
|
158
|
-
const live = App.#live;
|
|
159
|
-
|
|
160
|
-
events.addEventListener("open", () => {
|
|
161
|
-
live.textContent = "live";
|
|
162
|
-
live.title = "Watching the config: a saved change shows here at once.";
|
|
163
|
-
live.classList.add("on");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
events.addEventListener("error", () => {
|
|
167
|
-
live.textContent = "not connected";
|
|
168
|
-
live.title = "Circuit Studio is not answering. Is circuit studio still running?";
|
|
169
|
-
live.classList.remove("on");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// The config changed: load it again, and the live mark beats once to say so. The page itself changed, while working on Circuit: load the page again.
|
|
173
|
-
events.addEventListener("message", (event) => {
|
|
174
|
-
if (event.data === "page") return location.reload();
|
|
175
|
-
|
|
176
|
-
live.classList.remove("beat");
|
|
177
|
-
void live.offsetWidth;
|
|
178
|
-
live.classList.add("beat");
|
|
179
|
-
void App.#load();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
void App.#load();
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
App.start();
|