@takodotid/circuit 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{index-2r4bbhac.js → index-k38bby43.js} +5 -3
- package/dist/chunks/{server-gzsf5me5.js → server-ggertnce.js} +1 -1
- package/dist/cli/index.js +8 -2
- package/dist/types/core/address-space.d.ts +1 -1
- package/dist/types/core/address.d.ts +1 -1
- package/dist/types/core/cabling.d.ts +3 -3
- package/dist/types/core/cross-checks.d.ts +3 -3
- package/dist/types/core/define.d.ts +2 -2
- package/dist/types/core/secrets.d.ts +2 -2
- package/dist/types/core/topology.d.ts +2 -2
- package/dist/types/core/validator.d.ts +1 -1
- package/dist/types/helpers/bgp.d.ts +4 -4
- package/dist/types/helpers/checks.d.ts +1 -1
- package/dist/types/helpers/exchange.d.ts +3 -3
- package/dist/types/helpers/filters.d.ts +1 -1
- package/dist/types/helpers/index.d.ts +10 -10
- package/dist/types/helpers/route-server.d.ts +2 -2
- package/dist/types/index.d.ts +8 -6
- package/dist/types/integrations/librenms.d.ts +2 -2
- package/dist/types/integrations/netbox.d.ts +1 -1
- package/dist/types/integrations/peeringdb.d.ts +1 -1
- package/dist/types/integrations/prometheus.d.ts +2 -2
- package/dist/types/integrations/registries.d.ts +2 -2
- package/dist/types/platforms/bgp-wait.d.ts +1 -1
- package/dist/types/platforms/derive.d.ts +1 -1
- package/dist/types/platforms/fortios/firewall.d.ts +2 -2
- package/dist/types/platforms/fortios/index.d.ts +4 -4
- package/dist/types/platforms/fortios/names.d.ts +1 -1
- package/dist/types/platforms/fortios/plan.d.ts +1 -1
- package/dist/types/platforms/fortios/render.d.ts +1 -1
- package/dist/types/platforms/fortios/routing.d.ts +2 -2
- package/dist/types/platforms/index.d.ts +1 -1
- package/dist/types/platforms/junos/bgp.d.ts +2 -2
- package/dist/types/platforms/junos/index.d.ts +4 -4
- package/dist/types/platforms/junos/render.d.ts +1 -1
- package/dist/types/platforms/line-diff.d.ts +1 -1
- package/dist/types/platforms/platform.d.ts +5 -5
- package/dist/types/platforms/raisecom-ros/index.d.ts +4 -4
- package/dist/types/platforms/raisecom-ros/render.d.ts +1 -1
- package/dist/types/platforms/routeros/index.d.ts +4 -4
- package/dist/types/platforms/routeros/plan.d.ts +1 -1
- package/dist/types/platforms/routeros/render.d.ts +1 -1
- package/dist/types/platforms/vrp/index.d.ts +4 -4
- package/dist/types/platforms/vrp/render.d.ts +1 -1
- package/dist/types/schema/device.d.ts +6 -6
- package/dist/types/schema/firewall.d.ts +1 -1
- package/dist/types/schema/index.d.ts +8 -8
- package/dist/types/schema/interfaces.d.ts +5 -1
- package/dist/types/schema/policy.d.ts +1 -1
- package/dist/types/schema/routing.d.ts +1 -1
- package/dist/types/schema/system.d.ts +2 -2
- package/dist/types/transport/console.d.ts +1 -1
- package/dist/types/transport/ssh.d.ts +1 -1
- package/package.json +2 -2
- package/src/core/cabling.ts +4 -1
- package/src/core/cross-checks.ts +6 -1
- package/src/core/topology.ts +4 -3
- package/src/index.ts +3 -0
- package/src/schema/interfaces.ts +4 -0
- package/src/studio/data.ts +3 -0
- package/src/studio/public/tables.js +15 -1
|
@@ -160,7 +160,8 @@ class Topology {
|
|
|
160
160
|
const farPort = far?.ports?.[link.port];
|
|
161
161
|
const linksBack = farPort?.link?.device === device.name && farPort.link.port === portName;
|
|
162
162
|
const to = far ? Topology.deviceId(far.name) : graph.node(`missing:${link.device}`, "outside", link.device, ["named by a link, and not in the network"]);
|
|
163
|
-
const
|
|
163
|
+
const through = link.through ? `, through ${link.through}` : "";
|
|
164
|
+
const cableLine = `${here} to ${there}${through}`;
|
|
164
165
|
const lag = port.lag;
|
|
165
166
|
const farLag = farPort?.lag;
|
|
166
167
|
const bundleKey = `${device.name} ${lag}|${link.device} ${farLag}`;
|
|
@@ -168,7 +169,7 @@ class Topology {
|
|
|
168
169
|
if (bundle) {
|
|
169
170
|
bundle.members++;
|
|
170
171
|
bundle.cable.detail.push(cableLine);
|
|
171
|
-
bundle.cable.label = `${lag} to ${farLag}, ${bundle.members} cables`;
|
|
172
|
+
bundle.cable.label = `${lag} to ${farLag}, ${bundle.members} cables${through}`;
|
|
172
173
|
continue;
|
|
173
174
|
}
|
|
174
175
|
const isBundle = Boolean(lag && farLag && linksBack);
|
|
@@ -176,7 +177,7 @@ class Topology {
|
|
|
176
177
|
layer: "l1",
|
|
177
178
|
from: Topology.deviceId(device.name),
|
|
178
179
|
to,
|
|
179
|
-
label: isBundle ? `${lag} to ${farLag}` : `${portName} to ${link.port}`,
|
|
180
|
+
label: `${isBundle ? `${lag} to ${farLag}` : `${portName} to ${link.port}`}${through}`,
|
|
180
181
|
detail: [cableLine],
|
|
181
182
|
...linksBack ? {} : { oneSided: true }
|
|
182
183
|
});
|
|
@@ -950,6 +951,7 @@ class StudioModel {
|
|
|
950
951
|
...port.lag ? { lag: port.lag } : {},
|
|
951
952
|
...farPort?.lag ? { farLag: farPort.lag } : {},
|
|
952
953
|
...port.description ? { description: port.description } : {},
|
|
954
|
+
...link.through ? { through: link.through } : {},
|
|
953
955
|
vlans: vlansOf(device, portName),
|
|
954
956
|
...linksBack ? {} : { oneSided: true }
|
|
955
957
|
});
|
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Topology,
|
|
9
9
|
AddressSpace,
|
|
10
10
|
StudioModel
|
|
11
|
-
} from "../chunks/index-
|
|
11
|
+
} from "../chunks/index-k38bby43.js";
|
|
12
12
|
import {
|
|
13
13
|
Address2
|
|
14
14
|
} from "../chunks/index-n7w3tg5y.js";
|
|
@@ -3732,6 +3732,8 @@ class Cabling {
|
|
|
3732
3732
|
}
|
|
3733
3733
|
continue;
|
|
3734
3734
|
}
|
|
3735
|
+
if (link.through)
|
|
3736
|
+
continue;
|
|
3735
3737
|
const far = devices.find((candidate) => candidate.name === link.device);
|
|
3736
3738
|
if (!far || !Cabling.runsOn(far, link.port))
|
|
3737
3739
|
continue;
|
|
@@ -10694,6 +10696,10 @@ class CrossChecks {
|
|
|
10694
10696
|
report(`${link.device} ${link.port} does not link back to it`);
|
|
10695
10697
|
continue;
|
|
10696
10698
|
}
|
|
10699
|
+
if (link.through !== farPort.link?.through) {
|
|
10700
|
+
const say = (through) => through ? `through ${through}` : "direct";
|
|
10701
|
+
report(`${say(link.through)} here, and ${say(farPort.link?.through)} on ${link.device} ${link.port}`);
|
|
10702
|
+
}
|
|
10697
10703
|
const near = CrossChecks.carrierOf(device, portName, port);
|
|
10698
10704
|
const away = CrossChecks.carrierOf(far, link.port, farPort);
|
|
10699
10705
|
const inLag = near.name !== portName;
|
|
@@ -12280,7 +12286,7 @@ Renamed. Run circuit validate, then circuit apply ${to}: the device keeps its ol
|
|
|
12280
12286
|
const port = Number(flags.port);
|
|
12281
12287
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
12282
12288
|
Cli.fail("--port takes a number from 1 to 65535");
|
|
12283
|
-
const { StudioServer } = await import("../chunks/server-
|
|
12289
|
+
const { StudioServer } = await import("../chunks/server-ggertnce.js");
|
|
12284
12290
|
const host = flags.host;
|
|
12285
12291
|
const allowHosts = (flags.allowHost ?? "").split(",").filter(Boolean);
|
|
12286
12292
|
if (!StudioServer.isLoopback(host) && !allowHosts.length) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Family, IP, IPv4, Prefix } from "../schema";
|
|
1
|
+
import type { Family, IP, IPv4, Prefix } from "../schema/index.js";
|
|
2
2
|
/** Address helpers, for checks of your own. */
|
|
3
3
|
export declare class Address {
|
|
4
4
|
/** IPv4 space another network cannot reach: this host, private, shared, loopback, link-local and documentation, after RFC 6890. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Device } from "../schema";
|
|
2
|
-
import type { Finding } from "./define";
|
|
1
|
+
import type { Device } from "../schema/index.js";
|
|
2
|
+
import type { Finding } from "./define.js";
|
|
3
3
|
/** A device one of our ports sees at the far end of its cable, over LLDP. */
|
|
4
4
|
export type Neighbor = {
|
|
5
5
|
/** Our port, by Circuit's name, such as `10g-1`. */
|
|
@@ -28,7 +28,7 @@ export declare class Cabling {
|
|
|
28
28
|
/**
|
|
29
29
|
* Warnings where LLDP and `link` disagree, from each device's own view: a cable in another port than `link` names, a cable `link` names that LLDP does not see, and another of our devices on a port without `link`.
|
|
30
30
|
*
|
|
31
|
-
* Only ports where both ends run LLDP, by the config, are compared, and only on devices whose neighbors were read. LLDP can be off at the far end, so each is a warning.
|
|
31
|
+
* Only ports where both ends run LLDP, by the config, are compared, and only on devices whose neighbors were read. A link `through` a carrier is not compared. LLDP can be off at the far end, so each is a warning.
|
|
32
32
|
*/
|
|
33
33
|
static compare(devices: readonly Device[], neighbors: ReadonlyMap<string, readonly Neighbor[]>): Finding[];
|
|
34
34
|
/** A system name is the device's name, alone or with a domain after it. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Device } from "../schema";
|
|
2
|
-
import type { Finding } from "./define";
|
|
1
|
+
import type { Device } from "../schema/index.js";
|
|
2
|
+
import type { Finding } from "./define.js";
|
|
3
3
|
export declare class CrossChecks {
|
|
4
4
|
/** Every rule across devices. */
|
|
5
5
|
static run(devices: readonly Device[]): Finding[];
|
|
@@ -15,7 +15,7 @@ export declare class CrossChecks {
|
|
|
15
15
|
private static plainAddresses;
|
|
16
16
|
/** Why the two ends of a cable cannot reach each other by address, or undefined when they can. */
|
|
17
17
|
private static addressDisagreement;
|
|
18
|
-
/** A link names a device and port that exist, the far end links back, and both ends carry the same VLANs the same way, with the same MTU, speed and addresses. */
|
|
18
|
+
/** A link names a device and port that exist, the far end links back through the same carrier, and both ends carry the same VLANs the same way, with the same MTU, speed and addresses. */
|
|
19
19
|
private static checkLinks;
|
|
20
20
|
/** No address is held by two devices in one VRF. Two loopbacks may hold one on purpose, for anycast, so that is a warning. */
|
|
21
21
|
private static checkAddresses;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ModelName, PlatformName, PortName } from "../platforms/catalog";
|
|
2
|
-
import type { Device, Secret } from "../schema";
|
|
1
|
+
import type { ModelName, PlatformName, PortName } from "../platforms/catalog.js";
|
|
2
|
+
import type { Device, Secret } from "../schema/index.js";
|
|
3
3
|
/** A value resolved by name when a device is applied, from the environment or `.env` beside the config, and through 1Password when that value is an `op://` reference. Never the value itself. */
|
|
4
4
|
export declare const secret: (name: string) => Secret;
|
|
5
5
|
/** A value read from a file when a device is applied, such as a private key in PEM. The path is relative to the config file; keep the file out of git. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Network } from "./define";
|
|
2
|
-
import type { Console, Device, IP, Secret } from "../schema";
|
|
1
|
+
import type { Network } from "./define.js";
|
|
2
|
+
import type { Console, Device, IP, Secret } from "../schema/index.js";
|
|
3
3
|
/** How a device is logged in to: its management address and SSH port, its connection user and that user's password, and its console. */
|
|
4
4
|
export type DeviceLogin = {
|
|
5
5
|
/** Absent for a device reached only through its console. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Device, Routed } from "../schema";
|
|
2
|
-
import type { Finding } from "./define";
|
|
1
|
+
import type { Device, Routed } from "../schema/index.js";
|
|
2
|
+
import type { Finding } from "./define.js";
|
|
3
3
|
/** One view of the network. Every edge belongs to one. */
|
|
4
4
|
export type Layer = "l1" | "l2" | "l3" | "bgp" | "ospf" | "tunnels";
|
|
5
5
|
/** One of our devices, a segment several of them share, or something outside the network. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Community } from "../core/define";
|
|
2
|
-
import type { Device, PolicyRule, Prefix, StandardCommunity } from "../schema";
|
|
1
|
+
import type { Community } from "../core/define.js";
|
|
2
|
+
import type { Device, PolicyRule, Prefix, StandardCommunity } from "../schema/index.js";
|
|
3
3
|
/** The prefix lengths a BGP import accepts. */
|
|
4
4
|
export type SanityLengths = {
|
|
5
5
|
/** Shortest and longest IPv4 prefix accepted. /8 to /24 when absent. */
|
|
@@ -63,8 +63,8 @@ export declare class Bgp {
|
|
|
63
63
|
/** Each BGP neighbor's own policies and role, or its group's. */
|
|
64
64
|
static neighborPolicies(device: Device): {
|
|
65
65
|
name: string;
|
|
66
|
-
address: import("
|
|
67
|
-
role: import("
|
|
66
|
+
address: import("../index.js").IP;
|
|
67
|
+
role: import("../index.js").BgpRole | undefined;
|
|
68
68
|
import: string | undefined;
|
|
69
69
|
export: string | undefined;
|
|
70
70
|
}[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Check } from "../core/define";
|
|
1
|
+
import type { Check } from "../core/define.js";
|
|
2
2
|
/** Where traffic from outside, before anything filters it, may go. Every VLAN not listed as untrusted is trusted, so a VLAN added later is protected without being listed. */
|
|
3
3
|
export type TrustBoundary = {
|
|
4
4
|
/** VLANs carrying traffic straight from outside: transits, exchanges, an ISP handoff. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Check } from "../core/define";
|
|
2
|
-
import type { Ethertype, IP, Port, Prefix } from "../schema";
|
|
3
|
-
import { type RouteServer } from "./route-server";
|
|
1
|
+
import type { Check } from "../core/define.js";
|
|
2
|
+
import type { Ethertype, IP, Port, Prefix } from "../schema/index.js";
|
|
3
|
+
import { type RouteServer } from "./route-server.js";
|
|
4
4
|
/** A member of an internet exchange: a network with a port on the fabric and an address on the peering LAN. */
|
|
5
5
|
export type ExchangeMember = {
|
|
6
6
|
/** Lowercase, and never reused. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AclRule, FilterRule, Prefix } from "../schema";
|
|
1
|
+
import type { AclRule, FilterRule, Prefix } from "../schema/index.js";
|
|
2
2
|
/** What `Filters.antiAmplification` leaves alone. */
|
|
3
3
|
export type AntiAmplificationOptions = {
|
|
4
4
|
/** Prefixes inside the destinations whose UDP fragments pass: for a host that receives UDP too large for one packet, such as SIP or some VPNs. Every fragment after the first is dropped when absent. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { Bgp } from "./bgp";
|
|
2
|
-
export type { CommunityScheme, SanityLengths } from "./bgp";
|
|
3
|
-
export { Checks } from "./checks";
|
|
4
|
-
export type { TrustBoundary } from "./checks";
|
|
5
|
-
export { Exchange } from "./exchange";
|
|
6
|
-
export type { ExchangeMember, InternetExchange } from "./exchange";
|
|
7
|
-
export { Filters } from "./filters";
|
|
8
|
-
export { RouteServerConfig } from "./route-server";
|
|
9
|
-
export type { RouteServer } from "./route-server";
|
|
10
|
-
export type { AntiAmplificationOptions } from "./filters";
|
|
1
|
+
export { Bgp } from "./bgp.js";
|
|
2
|
+
export type { CommunityScheme, SanityLengths } from "./bgp.js";
|
|
3
|
+
export { Checks } from "./checks.js";
|
|
4
|
+
export type { TrustBoundary } from "./checks.js";
|
|
5
|
+
export { Exchange } from "./exchange.js";
|
|
6
|
+
export type { ExchangeMember, InternetExchange } from "./exchange.js";
|
|
7
|
+
export { Filters } from "./filters.js";
|
|
8
|
+
export { RouteServerConfig } from "./route-server.js";
|
|
9
|
+
export type { RouteServer } from "./route-server.js";
|
|
10
|
+
export type { AntiAmplificationOptions } from "./filters.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Family, IPv4, IPv6, Prefix } from "../schema";
|
|
2
|
-
import type { ExchangeMember } from "./exchange";
|
|
1
|
+
import type { Family, IPv4, IPv6, Prefix } from "../schema/index.js";
|
|
2
|
+
import type { ExchangeMember } from "./exchange.js";
|
|
3
3
|
/** A route server on an exchange's peering LAN: a server running BIRD 2 or OpenBGPD that passes every member's routes to every other member. */
|
|
4
4
|
export type RouteServer = {
|
|
5
5
|
/** Its name, such as `rs1`, written at the top of its config. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export { defineDevice, defineNetwork, secret, secretFile } from "./core/define";
|
|
2
|
-
export { Utils } from "./core/utils";
|
|
3
|
-
export type { Check, Community, Finding, NetBoxDevice, Network } from "./core/define";
|
|
4
|
-
export type * from "./schema";
|
|
1
|
+
export { defineDevice, defineNetwork, secret, secretFile } from "./core/define.js";
|
|
2
|
+
export { Utils } from "./core/utils.js";
|
|
3
|
+
export type { Check, Community, Finding, NetBoxDevice, Network } from "./core/define.js";
|
|
4
|
+
export type * from "./schema/index.js";
|
|
5
|
+
/** The platforms, their models and each model's ports, for types of your own: `PortName<"junos", "QFX5100-48S-6Q">` is each port of that model, such as `"10g-1"` or `"40g-6"`. */
|
|
6
|
+
export type { ModelName, PlatformName, PortName } from "./platforms/catalog.js";
|
|
5
7
|
/** Address helpers, for checks of your own. */
|
|
6
|
-
export { Address } from "./core/address";
|
|
8
|
+
export { Address } from "./core/address.js";
|
|
7
9
|
/** Secrets, for tools of your own: a secret's value, text with every secret hidden, and how Circuit logs in to a device. */
|
|
8
|
-
export { Secrets } from "./core/secrets";
|
|
10
|
+
export { Secrets } from "./core/secrets.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Network } from "../core/define";
|
|
2
|
-
import type { Device } from "../schema";
|
|
1
|
+
import type { Network } from "../core/define.js";
|
|
2
|
+
import type { Device } from "../schema/index.js";
|
|
3
3
|
/** A device LibreNMS monitors whose settings differ from its config. */
|
|
4
4
|
export type LibreNmsChange = {
|
|
5
5
|
device: Device;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Network } from "../core/define";
|
|
1
|
+
import type { Network } from "../core/define.js";
|
|
2
2
|
/** Each kind of object Circuit writes, in the order they are made: each refers only to kinds before it, or waits for the next pass. */
|
|
3
3
|
export type NetBoxKind = "tag" | "site" | "manufacturer" | "platform" | "device-type" | "role" | "vrf" | "vlan" | "device" | "interface" | "prefix" | "ip-range" | "ip" | "cable";
|
|
4
4
|
/** One object to make, change or delete. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Network } from "../core/define";
|
|
2
|
-
import type { Device } from "../schema";
|
|
1
|
+
import type { Network } from "../core/define.js";
|
|
2
|
+
import type { Device } from "../schema/index.js";
|
|
3
3
|
export type PrometheusTarget = {
|
|
4
4
|
targets: string[];
|
|
5
5
|
labels: Record<string, string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Family } from "../schema";
|
|
2
|
-
import type { Registry } from "../schema/policy";
|
|
1
|
+
import type { Family } from "../schema/index.js";
|
|
2
|
+
import type { Registry } from "../schema/policy.js";
|
|
3
3
|
export declare class Registries {
|
|
4
4
|
/** The prefixes of one family a registry gives for a query. */
|
|
5
5
|
static prefixes(name: Registry, query: string, family: Family): Promise<string[]>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Device } from "../schema";
|
|
1
|
+
import type { Device } from "../schema/index.js";
|
|
2
2
|
/** A feature a platform renders once proved on one of its devices. */
|
|
3
3
|
export type ProvedFeature = "bfd-timers" | "static-route-bfd" | "multihop-bfd" | "alarm-severity" | "mac-limit" | "ethertype-filter";
|
|
4
4
|
export declare class Derive {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AclRule, Device, Family, FilterMatch, FilterRule, NatRule, Ports, Protocol } from "../../schema";
|
|
2
|
-
import { FortiOsBlock } from "./tree";
|
|
1
|
+
import type { AclRule, Device, Family, FilterMatch, FilterRule, NatRule, Ports, Protocol } from "../../schema/index.js";
|
|
2
|
+
import { FortiOsBlock } from "./tree.js";
|
|
3
3
|
export declare class FortiOsFirewall {
|
|
4
4
|
/** IP protocol numbers, for a protocol the platform names only by number. */
|
|
5
5
|
static readonly PROTOCOL_NUMBER: Record<Protocol, number>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Neighbor } from "../../core/cabling";
|
|
2
|
-
import type { Device, Family } from "../../schema";
|
|
3
|
-
import { type ConsoleDialect } from "../../transport/console";
|
|
4
|
-
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform";
|
|
1
|
+
import type { Neighbor } from "../../core/cabling.js";
|
|
2
|
+
import type { Device, Family } from "../../schema/index.js";
|
|
3
|
+
import { type ConsoleDialect } from "../../transport/console.js";
|
|
4
|
+
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform.js";
|
|
5
5
|
/** What a session reads and writes: an SSH shell, or anything else that streams the CLI. */
|
|
6
6
|
export type Channel = {
|
|
7
7
|
write(data: string): unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApplyOptions, Plan } from "../platform";
|
|
1
|
+
import type { ApplyOptions, Plan } from "../platform.js";
|
|
2
2
|
export declare class FortiOsPlan {
|
|
3
3
|
/** Objects every FortiGate running 6.2 is shipped with, which policies and profiles of the factory name. */
|
|
4
4
|
private static readonly FACTORY_ADDRESSES;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Device, Helper, Severity } from "../../schema";
|
|
1
|
+
import type { Device, Helper, Severity } from "../../schema/index.js";
|
|
2
2
|
export type Section = (typeof FortiOsRender.SECTIONS)[number];
|
|
3
3
|
export declare class FortiOsRender {
|
|
4
4
|
/** Where the pre-login banner's text is kept. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Device, Family, Neighbor, NeighborSettings, PolicyRule } from "../../schema";
|
|
2
|
-
import { FortiOsBlock } from "./tree";
|
|
1
|
+
import type { Device, Family, Neighbor, NeighborSettings, PolicyRule } from "../../schema/index.js";
|
|
2
|
+
import { FortiOsBlock } from "./tree.js";
|
|
3
3
|
type AnyRule = PolicyRule<string, string>;
|
|
4
4
|
type AnyNeighbor = Neighbor<string, string>;
|
|
5
5
|
/** The lengths a prefix list's rule takes within its prefix. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Device, Neighbor, NeighborSettings } from "../../schema";
|
|
2
|
-
import { type JunosStatements } from "./render";
|
|
1
|
+
import type { Device, Neighbor, NeighborSettings } from "../../schema/index.js";
|
|
2
|
+
import { type JunosStatements } from "./render.js";
|
|
3
3
|
type AnyNeighbor = Neighbor<string, string>;
|
|
4
4
|
/** BGP sessions, the policies they use, prefix sets and RPKI servers, added to one device's statements. */
|
|
5
5
|
export declare class JunosBgp {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Neighbor } from "../../core/cabling";
|
|
2
|
-
import type { Device, Family } from "../../schema";
|
|
3
|
-
import { type ConsoleDialect } from "../../transport/console";
|
|
4
|
-
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform";
|
|
1
|
+
import type { Neighbor } from "../../core/cabling.js";
|
|
2
|
+
import type { Device, Family } from "../../schema/index.js";
|
|
3
|
+
import { type ConsoleDialect } from "../../transport/console.js";
|
|
4
|
+
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform.js";
|
|
5
5
|
/** How a commit that could cut access is guarded: it rolls back after `minutes` unless `prove` shows access survived. */
|
|
6
6
|
export type Guard = {
|
|
7
7
|
minutes: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Device, Interface, Port, Vrrp } from "../../schema";
|
|
1
|
+
import type { Device, Interface, Port, Vrrp } from "../../schema/index.js";
|
|
2
2
|
type AnyPort = Port<string, string, string, string>;
|
|
3
3
|
type AnyInterface = Interface<string, string>;
|
|
4
4
|
type LagInterface = Extract<AnyInterface, {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Neighbor } from "../core/cabling";
|
|
2
|
-
import type { Device, Family } from "../schema";
|
|
3
|
-
import type { ConsoleDialect } from "../transport/console";
|
|
4
|
-
import type { WaitLimits } from "./bgp-wait";
|
|
5
|
-
import type { PlatformName } from "./catalog";
|
|
1
|
+
import type { Neighbor } from "../core/cabling.js";
|
|
2
|
+
import type { Device, Family } from "../schema/index.js";
|
|
3
|
+
import type { ConsoleDialect } from "../transport/console.js";
|
|
4
|
+
import type { WaitLimits } from "./bgp-wait.js";
|
|
5
|
+
import type { PlatformName } from "./catalog.js";
|
|
6
6
|
/** One part of a plan: what a reviewer reads, and what is sent. */
|
|
7
7
|
export type Step = {
|
|
8
8
|
title: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ClientChannel } from "ssh2";
|
|
2
|
-
import type { Neighbor } from "../../core/cabling";
|
|
3
|
-
import type { Device } from "../../schema";
|
|
4
|
-
import { type ConsoleDialect } from "../../transport/console";
|
|
5
|
-
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform";
|
|
2
|
+
import type { Neighbor } from "../../core/cabling.js";
|
|
3
|
+
import type { Device } from "../../schema/index.js";
|
|
4
|
+
import { type ConsoleDialect } from "../../transport/console.js";
|
|
5
|
+
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform.js";
|
|
6
6
|
/** A session, and how to prove access in a fresh login: a new SSH session, or logging out and in again on the console. */
|
|
7
7
|
export type Session = {
|
|
8
8
|
channel: ClientChannel;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Device, SnmpUser } from "../../schema";
|
|
1
|
+
import type { Device, SnmpUser } from "../../schema/index.js";
|
|
2
2
|
/** The configuration a Raisecom ROS device should run. */
|
|
3
3
|
export declare class RaisecomRosRender {
|
|
4
4
|
/** The access-list that guards every management service. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Neighbor } from "../../core/cabling";
|
|
2
|
-
import type { Device, Family } from "../../schema";
|
|
3
|
-
import { type ConsoleDialect } from "../../transport/console";
|
|
4
|
-
import { type ApplyOptions, type BgpLogLine, type BgpSession, type HealthCheck, type HealthOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, type SwitchRules, Platform } from "../platform";
|
|
1
|
+
import type { Neighbor } from "../../core/cabling.js";
|
|
2
|
+
import type { Device, Family } from "../../schema/index.js";
|
|
3
|
+
import { type ConsoleDialect } from "../../transport/console.js";
|
|
4
|
+
import { type ApplyOptions, type BgpLogLine, type BgpSession, type HealthCheck, type HealthOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, type SwitchRules, Platform } from "../platform.js";
|
|
5
5
|
export declare class RouterOs extends Platform {
|
|
6
6
|
/** How the RouterOS console asks for a login. The flags after the user turn off colours and terminal detection, without which the console redraws every line. */
|
|
7
7
|
static readonly CONSOLE: ConsoleDialect;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApplyOptions, Plan } from "../platform";
|
|
1
|
+
import type { ApplyOptions, Plan } from "../platform.js";
|
|
2
2
|
export declare class RouterOsPlan {
|
|
3
3
|
private readonly sendSecrets;
|
|
4
4
|
/** A plan line the apply handles itself: upload a certificate and its key, and import them. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AclRule, Device, Ethertype, Interface, Port } from "../../schema";
|
|
1
|
+
import type { AclRule, Device, Ethertype, Interface, Port } from "../../schema/index.js";
|
|
2
2
|
/** A property value as a command takes it. `undefined` and `false` leave the property out. */
|
|
3
3
|
export type Value = string | number | undefined | false;
|
|
4
4
|
export type AnyPort = Port<string, string, string, string>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Neighbor } from "../../core/cabling";
|
|
2
|
-
import type { Device } from "../../schema";
|
|
3
|
-
import { type ConsoleDialect } from "../../transport/console";
|
|
4
|
-
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform";
|
|
1
|
+
import type { Neighbor } from "../../core/cabling.js";
|
|
2
|
+
import type { Device } from "../../schema/index.js";
|
|
3
|
+
import { type ConsoleDialect } from "../../transport/console.js";
|
|
4
|
+
import { type ApplyOptions, type OsSupport, type Plan, type ReadOptions, type ReadSession, Platform } from "../platform.js";
|
|
5
5
|
export declare class Vrp extends Platform {
|
|
6
6
|
/** How the console asks for a login: the same question to change the password, always answered no, a system view left with `return`, and the line's banner after a logout or a refused password answered with Enter. Not yet proved on a device. */
|
|
7
7
|
static readonly CONSOLE: ConsoleDialect;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { IP, Secret } from "./common";
|
|
2
|
-
import type { AclRule, Firewall } from "./firewall";
|
|
3
|
-
import type { Interface, Port, Vlan } from "./interfaces";
|
|
4
|
-
import type { PrefixSet, RoutePolicy } from "./policy";
|
|
5
|
-
import type { Routing } from "./routing";
|
|
6
|
-
import type { Certificate, DhcpRelay, DhcpServer, FlowExport, Management, System, User } from "./system";
|
|
1
|
+
import type { IP, Secret } from "./common.js";
|
|
2
|
+
import type { AclRule, Firewall } from "./firewall.js";
|
|
3
|
+
import type { Interface, Port, Vlan } from "./interfaces.js";
|
|
4
|
+
import type { PrefixSet, RoutePolicy } from "./policy.js";
|
|
5
|
+
import type { Routing } from "./routing.js";
|
|
6
|
+
import type { Certificate, DhcpRelay, DhcpServer, FlowExport, Management, System, User } from "./system.js";
|
|
7
7
|
/** How the framework reaches the device. */
|
|
8
8
|
export type Connection<U extends string> = {
|
|
9
9
|
/** Management address. Absent for a device reached only through its console, `console`. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Family, IP, Prefix, Rate } from "./common";
|
|
1
|
+
import type { Family, IP, Prefix, Rate } from "./common.js";
|
|
2
2
|
/** An IP protocol. `icmp` means ICMPv6 in an IPv6 rule. */
|
|
3
3
|
export type Protocol = "tcp" | "udp" | "icmp" | "gre" | "esp" | "ah" | "ospf" | "vrrp";
|
|
4
4
|
/** The state connection tracking assigns a packet. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export type * from "./common";
|
|
2
|
-
export type * from "./device";
|
|
3
|
-
export type * from "./firewall";
|
|
4
|
-
export type * from "./interfaces";
|
|
5
|
-
export type * from "./policy";
|
|
6
|
-
export type * from "./routing";
|
|
7
|
-
export type * from "./system";
|
|
8
|
-
export type * from "./time-zones";
|
|
1
|
+
export type * from "./common.js";
|
|
2
|
+
export type * from "./device.js";
|
|
3
|
+
export type * from "./firewall.js";
|
|
4
|
+
export type * from "./interfaces.js";
|
|
5
|
+
export type * from "./policy.js";
|
|
6
|
+
export type * from "./routing.js";
|
|
7
|
+
export type * from "./system.js";
|
|
8
|
+
export type * from "./time-zones.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InterfaceAddress, IP, MacAddress, Prefix, Secret } from "./common";
|
|
1
|
+
import type { InterfaceAddress, IP, MacAddress, Prefix, Secret } from "./common.js";
|
|
2
2
|
/** A VLAN. Its key in `vlans` is its name. */
|
|
3
3
|
export type Vlan = {
|
|
4
4
|
/** The VLAN's number on the wire, 1 to 4094. */
|
|
@@ -90,6 +90,10 @@ export type Link = {
|
|
|
90
90
|
device: string;
|
|
91
91
|
/** The port on that device. */
|
|
92
92
|
port: string;
|
|
93
|
+
/**
|
|
94
|
+
* What is between the two ends when the cable is not direct, such as a carrier's local loop or L2 transport: `"Carrier A local loop"`. LLDP then sees the carrier's switches, not the far end, so `validate` does not compare LLDP on this port. Both ends name the same carrier.
|
|
95
|
+
*/
|
|
96
|
+
through?: string;
|
|
93
97
|
};
|
|
94
98
|
/** A physical port. Ports a device does not declare are shut down. */
|
|
95
99
|
export type Port<V extends string, A extends string, L extends string, F extends string> = Routed<F> & Switched<V> & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Family, IP, Prefix } from "./common";
|
|
1
|
+
import type { Family, IP, Prefix } from "./common.js";
|
|
2
2
|
/** A standard BGP community, `asn:value`, for example `64500:100`. Each part is 0 to 65535. */
|
|
3
3
|
export type StandardCommunity = `${number}:${number}`;
|
|
4
4
|
/** A large BGP community, `asn:function:parameter`, for example `64500:1:6939`. Each part is 0 to 4294967295. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { IP, MacAddress, Prefix, Secret, Severity } from "./common";
|
|
2
|
-
import type { TimeZone } from "./time-zones";
|
|
1
|
+
import type { IP, MacAddress, Prefix, Secret, Severity } from "./common.js";
|
|
2
|
+
import type { TimeZone } from "./time-zones.js";
|
|
3
3
|
/** A software release track, as RouterOS names them. `long-term` changes least; `development` is the newest and least tested. */
|
|
4
4
|
export type ReleaseChannel = "long-term" | "stable" | "testing" | "development";
|
|
5
5
|
/** How bad an alarm is, most severe first, as ITU-T X.733 ranks them. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ClientChannel } from "ssh2";
|
|
2
|
-
import type { Console, Device } from "../schema";
|
|
2
|
+
import type { Console, Device } from "../schema/index.js";
|
|
3
3
|
/** An open console: the stream to the device's CLI, and how to let it go. `close` waits for the console to go quiet, then lets the port go. */
|
|
4
4
|
export type ConsoleSession = {
|
|
5
5
|
channel: ClientChannel;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takodotid/circuit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Declarative, vendor-neutral network configuration: describe each device in TypeScript, and Circuit makes the device match. Alpha.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"network",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"release": "bun scripts/release.ts",
|
|
90
90
|
"prepare": "husky || true",
|
|
91
91
|
"prepack": "bun run build",
|
|
92
|
-
"check": "bun run typecheck && bun run test && bun run format:check && bun run docs:build && bun run build && node dist/cli/index.js validate --config templates/single-site/circuit.config.ts && node dist/cli/index.js validate --config templates/edge-router/circuit.config.ts && node dist/cli/index.js validate --config templates/colocation/circuit.config.ts",
|
|
92
|
+
"check": "bun run typecheck && bun run test && bun run format:check && bun run docs:build && bun run build && bunx tsc -p test/package && node dist/cli/index.js validate --config templates/single-site/circuit.config.ts && node dist/cli/index.js validate --config templates/edge-router/circuit.config.ts && node dist/cli/index.js validate --config templates/colocation/circuit.config.ts",
|
|
93
93
|
"agents:setup": "npx -y skills experimental_install"
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
package/src/core/cabling.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class Cabling {
|
|
|
67
67
|
/**
|
|
68
68
|
* Warnings where LLDP and `link` disagree, from each device's own view: a cable in another port than `link` names, a cable `link` names that LLDP does not see, and another of our devices on a port without `link`.
|
|
69
69
|
*
|
|
70
|
-
* Only ports where both ends run LLDP, by the config, are compared, and only on devices whose neighbors were read. LLDP can be off at the far end, so each is a warning.
|
|
70
|
+
* Only ports where both ends run LLDP, by the config, are compared, and only on devices whose neighbors were read. A link `through` a carrier is not compared. LLDP can be off at the far end, so each is a warning.
|
|
71
71
|
*/
|
|
72
72
|
static compare(devices: readonly Device[], neighbors: ReadonlyMap<string, readonly Neighbor[]>): Finding[] {
|
|
73
73
|
const findings: Finding[] = [];
|
|
@@ -97,6 +97,9 @@ export class Cabling {
|
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Through a carrier, LLDP sees the carrier's switches, not the far end.
|
|
101
|
+
if (link.through) continue;
|
|
102
|
+
|
|
100
103
|
const far = devices.find((candidate) => candidate.name === link.device);
|
|
101
104
|
if (!far || !Cabling.runsOn(far, link.port)) continue;
|
|
102
105
|
|
package/src/core/cross-checks.ts
CHANGED
|
@@ -97,7 +97,7 @@ export class CrossChecks {
|
|
|
97
97
|
return undefined;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/** A link names a device and port that exist, the far end links back, and both ends carry the same VLANs the same way, with the same MTU, speed and addresses. */
|
|
100
|
+
/** A link names a device and port that exist, the far end links back through the same carrier, and both ends carry the same VLANs the same way, with the same MTU, speed and addresses. */
|
|
101
101
|
private static checkLinks(devices: readonly Device[]): Finding[] {
|
|
102
102
|
const findings: Finding[] = [];
|
|
103
103
|
const byName = new Map(devices.map((device) => [device.name, device]));
|
|
@@ -132,6 +132,11 @@ export class CrossChecks {
|
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
if (link.through !== farPort.link?.through) {
|
|
136
|
+
const say = (through: string | undefined) => (through ? `through ${through}` : "direct");
|
|
137
|
+
report(`${say(link.through)} here, and ${say(farPort.link?.through)} on ${link.device} ${link.port}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
135
140
|
const near = CrossChecks.carrierOf(device, portName, port);
|
|
136
141
|
const away = CrossChecks.carrierOf(far, link.port, farPort);
|
|
137
142
|
|
package/src/core/topology.ts
CHANGED
|
@@ -203,7 +203,8 @@ export class Topology {
|
|
|
203
203
|
? Topology.deviceId(far.name)
|
|
204
204
|
: graph.node(`missing:${link.device}`, "outside", link.device, ["named by a link, and not in the network"]);
|
|
205
205
|
|
|
206
|
-
const
|
|
206
|
+
const through = link.through ? `, through ${link.through}` : "";
|
|
207
|
+
const cableLine = `${here} to ${there}${through}`;
|
|
207
208
|
|
|
208
209
|
// A LAG's member cables to one LAG on the far side are one edge.
|
|
209
210
|
const lag = port.lag;
|
|
@@ -214,7 +215,7 @@ export class Topology {
|
|
|
214
215
|
if (bundle) {
|
|
215
216
|
bundle.members++;
|
|
216
217
|
bundle.cable.detail.push(cableLine);
|
|
217
|
-
bundle.cable.label = `${lag} to ${farLag}, ${bundle.members} cables`;
|
|
218
|
+
bundle.cable.label = `${lag} to ${farLag}, ${bundle.members} cables${through}`;
|
|
218
219
|
continue;
|
|
219
220
|
}
|
|
220
221
|
|
|
@@ -224,7 +225,7 @@ export class Topology {
|
|
|
224
225
|
layer: "l1",
|
|
225
226
|
from: Topology.deviceId(device.name),
|
|
226
227
|
to,
|
|
227
|
-
label: isBundle ? `${lag} to ${farLag}` : `${portName} to ${link.port}`,
|
|
228
|
+
label: `${isBundle ? `${lag} to ${farLag}` : `${portName} to ${link.port}`}${through}`,
|
|
228
229
|
detail: [cableLine],
|
|
229
230
|
...(linksBack ? {} : { oneSided: true as const }),
|
|
230
231
|
});
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,9 @@ export { Utils } from "./core/utils";
|
|
|
3
3
|
export type { Check, Community, Finding, NetBoxDevice, Network } from "./core/define";
|
|
4
4
|
export type * from "./schema";
|
|
5
5
|
|
|
6
|
+
/** The platforms, their models and each model's ports, for types of your own: `PortName<"junos", "QFX5100-48S-6Q">` is each port of that model, such as `"10g-1"` or `"40g-6"`. */
|
|
7
|
+
export type { ModelName, PlatformName, PortName } from "./platforms/catalog";
|
|
8
|
+
|
|
6
9
|
/** Address helpers, for checks of your own. */
|
|
7
10
|
export { Address } from "./core/address";
|
|
8
11
|
|
package/src/schema/interfaces.ts
CHANGED
|
@@ -92,6 +92,10 @@ export type Link = {
|
|
|
92
92
|
device: string;
|
|
93
93
|
/** The port on that device. */
|
|
94
94
|
port: string;
|
|
95
|
+
/**
|
|
96
|
+
* What is between the two ends when the cable is not direct, such as a carrier's local loop or L2 transport: `"Carrier A local loop"`. LLDP then sees the carrier's switches, not the far end, so `validate` does not compare LLDP on this port. Both ends name the same carrier.
|
|
97
|
+
*/
|
|
98
|
+
through?: string;
|
|
95
99
|
};
|
|
96
100
|
|
|
97
101
|
/** A physical port. Ports a device does not declare are shut down. */
|
package/src/studio/data.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type Cable = {
|
|
|
37
37
|
lag?: string;
|
|
38
38
|
farLag?: string;
|
|
39
39
|
description?: string;
|
|
40
|
+
/** What is between the two ends, such as a carrier's local loop. */
|
|
41
|
+
through?: string;
|
|
40
42
|
/** VLANs carried, as `name (id)`, from the port or its LAG. */
|
|
41
43
|
vlans: string[];
|
|
42
44
|
/** The far end does not name this cable back. */
|
|
@@ -227,6 +229,7 @@ export class StudioModel {
|
|
|
227
229
|
...(port.lag ? { lag: port.lag } : {}),
|
|
228
230
|
...(farPort?.lag ? { farLag: farPort.lag } : {}),
|
|
229
231
|
...(port.description ? { description: port.description } : {}),
|
|
232
|
+
...(link.through ? { through: link.through } : {}),
|
|
230
233
|
vlans: vlansOf(device, portName),
|
|
231
234
|
...(linksBack ? {} : { oneSided: true as const }),
|
|
232
235
|
});
|
|
@@ -1021,19 +1021,33 @@ export class Tables {
|
|
|
1021
1021
|
|
|
1022
1022
|
/** @param {HTMLElement} container */
|
|
1023
1023
|
static #cablesView(container) {
|
|
1024
|
+
// A carrier column only when a cable runs through one.
|
|
1025
|
+
const carried = Tables.#data.cables.some((cable) => cable.through);
|
|
1026
|
+
|
|
1024
1027
|
container.append(
|
|
1025
1028
|
Tables.heading("Cables", "h2"),
|
|
1026
1029
|
Tables.empty("Every cable a port's link names, once, from the end that names it first."),
|
|
1027
1030
|
Tables.#data.cables.length
|
|
1028
1031
|
? Tables.of({
|
|
1029
1032
|
csv: "cables",
|
|
1030
|
-
head: [
|
|
1033
|
+
head: [
|
|
1034
|
+
"Device",
|
|
1035
|
+
"Port",
|
|
1036
|
+
"Far device",
|
|
1037
|
+
"Far port",
|
|
1038
|
+
...(carried ? ["Through"] : []),
|
|
1039
|
+
"LAG",
|
|
1040
|
+
"VLANs",
|
|
1041
|
+
"Description",
|
|
1042
|
+
"",
|
|
1043
|
+
].map((label) => ({ label })),
|
|
1031
1044
|
rows: Tables.#data.cables.map((cable) => ({
|
|
1032
1045
|
cells: [
|
|
1033
1046
|
Tables.deviceLink(cable.device),
|
|
1034
1047
|
cable.port,
|
|
1035
1048
|
Tables.deviceLink(cable.farDevice),
|
|
1036
1049
|
cable.farPort,
|
|
1050
|
+
...(carried ? [cable.through ?? ""] : []),
|
|
1037
1051
|
[cable.lag, cable.farLag].filter(Boolean).join(" to "),
|
|
1038
1052
|
cable.vlans.join(", "),
|
|
1039
1053
|
cable.description ?? "",
|