@takodotid/circuit 0.18.5 → 0.20.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/README.md +18 -0
- package/dist/chunks/index-jtzfw679.js +936 -0
- package/dist/chunks/{index-ex1wcst7.js → index-xm9nma2t.js} +20 -1
- package/dist/chunks/server-5shmsmse.js +246 -0
- package/dist/cli/index.js +701 -106
- package/dist/index.js +1 -1
- package/dist/presets/index.js +1 -1
- package/dist/types/adapters/devices/derive.d.ts +2 -0
- package/dist/types/core/across.d.ts +4 -0
- package/dist/types/core/addr.d.ts +10 -0
- package/dist/types/core/addresses.d.ts +46 -0
- package/dist/types/core/files.d.ts +6 -0
- package/dist/types/core/graph.d.ts +74 -0
- package/dist/types/core/schema-docs.d.ts +16 -0
- package/package.json +1 -1
- package/src/adapters/devices/derive.ts +32 -0
- package/src/adapters/devices/raisecom-ros/index.ts +23 -3
- package/src/adapters/devices/routeros/index.ts +10 -0
- package/src/adapters/devices/vrp/index.ts +10 -1
- package/src/cli/graph.ts +89 -0
- package/src/cli/index.ts +205 -2
- package/src/cli/rename.ts +129 -0
- package/src/core/across.ts +322 -0
- package/src/core/addr.ts +29 -0
- package/src/core/addresses.ts +316 -0
- package/src/core/files.ts +30 -0
- package/src/core/graph.ts +634 -0
- package/src/core/schema-docs.ts +166 -0
- package/src/core/validate.ts +2 -52
- package/src/studio/data.ts +255 -0
- package/src/studio/git.ts +119 -0
- package/src/studio/public/app.js +147 -0
- package/src/studio/public/compare.js +243 -0
- package/src/studio/public/diff.js +97 -0
- package/src/studio/public/dom.js +108 -0
- package/src/studio/public/evaluate.js +648 -0
- package/src/studio/public/export.js +122 -0
- package/src/studio/public/favicon.svg +6 -0
- package/src/studio/public/graph.js +914 -0
- package/src/studio/public/index.html +67 -0
- package/src/studio/public/net.js +128 -0
- package/src/studio/public/style.css +693 -0
- package/src/studio/public/tables.js +1223 -0
- package/src/studio/public/tools.js +578 -0
- package/src/studio/server.ts +218 -0
- package/templates/project/AGENTS.md +12 -9
- package/templates/project/README.md +1 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- Circuit Studio. The scripts fill the parts below from /data, and draw again when the server says the config changed. -->
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<title>Circuit Studio</title>
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
9
|
+
<!-- The documentation site's fonts. Offline, the page falls back to the system's. -->
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
|
+
<link
|
|
13
|
+
rel="stylesheet"
|
|
14
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Newsreader:ital,opsz,wght@0,6..72,400;1,6..72,400&display=swap"
|
|
15
|
+
/>
|
|
16
|
+
<link rel="stylesheet" href="style.css" />
|
|
17
|
+
<script type="module" src="app.js"></script>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<header>
|
|
21
|
+
<span class="brand" aria-label="Circuit Studio"><span class="wordmark">Circuit</span><span class="product">Studio</span></span>
|
|
22
|
+
<nav id="views" aria-label="Views">
|
|
23
|
+
<button data-view="graph">Graph</button>
|
|
24
|
+
<button data-view="devices">Devices</button>
|
|
25
|
+
<button data-view="vlans">VLANs</button>
|
|
26
|
+
<button data-view="addresses">Addresses</button>
|
|
27
|
+
<button data-view="sessions">BGP</button>
|
|
28
|
+
<button data-view="cables">Cables</button>
|
|
29
|
+
<button data-view="tools">Tools</button>
|
|
30
|
+
<button data-view="compare">Compare</button>
|
|
31
|
+
<button data-view="problems">Problems<span id="problem-count" class="count"></span></button>
|
|
32
|
+
</nav>
|
|
33
|
+
<input id="search" type="search" placeholder="Search the config" aria-label="Search the config" />
|
|
34
|
+
<span id="summary" class="summary"></span>
|
|
35
|
+
<span id="live" class="live">connecting</span>
|
|
36
|
+
</header>
|
|
37
|
+
|
|
38
|
+
<pre id="banner" class="banner" hidden></pre>
|
|
39
|
+
|
|
40
|
+
<main>
|
|
41
|
+
<section id="graph-view" class="view">
|
|
42
|
+
<div class="toolbar">
|
|
43
|
+
<nav id="layers" aria-label="Layers"></nav>
|
|
44
|
+
<div class="actions">
|
|
45
|
+
<button id="fit" title="Fit the drawing to the window">Fit</button>
|
|
46
|
+
<button id="lay-out" title="Forget where nodes were moved, and lay them out again">Lay out again</button>
|
|
47
|
+
<button id="save-svg" title="Save this layer as an SVG image">Save SVG</button>
|
|
48
|
+
<button id="save-png" title="Save this layer as a PNG image">Save PNG</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="stage">
|
|
52
|
+
<svg id="canvas" class="canvas" role="img" aria-label="The network">
|
|
53
|
+
<g id="viewport">
|
|
54
|
+
<g id="edges"></g>
|
|
55
|
+
<g id="nodes"></g>
|
|
56
|
+
</g>
|
|
57
|
+
</svg>
|
|
58
|
+
<aside id="panel"></aside>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section id="tables-view" class="view" hidden>
|
|
63
|
+
<div id="tables" class="tables"></div>
|
|
64
|
+
</section>
|
|
65
|
+
</main>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Address arithmetic for the browser, for both families, as src/core/addr.ts does it for Circuit: addresses as numbers, so IPv4 and IPv6 share one path.
|
|
3
|
+
|
|
4
|
+
/** @typedef {"ipv4" | "ipv6"} Family */
|
|
5
|
+
/** @typedef {{ family: Family, value: bigint }} Address */
|
|
6
|
+
/** @typedef {{ family: Family, first: bigint, last: bigint, length: number }} Range */
|
|
7
|
+
|
|
8
|
+
/** @param {Family} family */
|
|
9
|
+
const bitsOf = (family) => (family === "ipv4" ? 32 : 128);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An address as a number, or undefined when the text is not one.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} text
|
|
15
|
+
* @returns {Address | undefined}
|
|
16
|
+
*/
|
|
17
|
+
export function parseAddress(text) {
|
|
18
|
+
if (!text.includes(":")) {
|
|
19
|
+
const octets = text.split(".");
|
|
20
|
+
if (octets.length !== 4 || !octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255)) return undefined;
|
|
21
|
+
return { family: "ipv4", value: octets.reduce((value, octet) => (value << 8n) | BigInt(octet), 0n) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const [before, after, extra] = text.split("::");
|
|
25
|
+
if (extra !== undefined || before === undefined) return undefined;
|
|
26
|
+
|
|
27
|
+
/** @param {string | undefined} part */
|
|
28
|
+
const groupsOf = (part) => (part ? part.split(":") : []);
|
|
29
|
+
const leading = groupsOf(before);
|
|
30
|
+
const trailing = groupsOf(after);
|
|
31
|
+
|
|
32
|
+
// `::` stands for as many zero groups as make eight.
|
|
33
|
+
const zeros = after === undefined ? 0 : 8 - leading.length - trailing.length;
|
|
34
|
+
if (zeros < 0) return undefined;
|
|
35
|
+
const groups = [...leading, ...Array(zeros).fill("0"), ...trailing];
|
|
36
|
+
if (groups.length !== 8 || !groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group))) return undefined;
|
|
37
|
+
|
|
38
|
+
return { family: "ipv6", value: groups.reduce((value, group) => (value << 16n) | BigInt(parseInt(group, 16)), 0n) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A prefix, or a lone address as the prefix of just itself, as the range of numbers it covers. Undefined when the text is neither.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} text
|
|
45
|
+
* @returns {Range | undefined}
|
|
46
|
+
*/
|
|
47
|
+
export function parseRange(text) {
|
|
48
|
+
const [addressText, lengthText] = text.split("/");
|
|
49
|
+
const address = parseAddress(addressText ?? "");
|
|
50
|
+
if (!address) return undefined;
|
|
51
|
+
|
|
52
|
+
const bits = bitsOf(address.family);
|
|
53
|
+
const length = lengthText === undefined ? bits : Number(lengthText);
|
|
54
|
+
if (!Number.isInteger(length) || length < 0 || length > bits) return undefined;
|
|
55
|
+
|
|
56
|
+
const hostBits = (1n << BigInt(bits - length)) - 1n;
|
|
57
|
+
const first = address.value & ~hostBits & ((1n << BigInt(bits)) - 1n);
|
|
58
|
+
return { family: address.family, first, last: first | hostBits, length };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a prefix, or an address, holds an address or a whole other prefix.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} outer
|
|
65
|
+
* @param {string} inner
|
|
66
|
+
*/
|
|
67
|
+
export function covers(outer, inner) {
|
|
68
|
+
const a = parseRange(outer);
|
|
69
|
+
const b = parseRange(inner);
|
|
70
|
+
return Boolean(a && b && a.family === b.family && a.first <= b.first && b.last <= a.last);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The same prefix, however it is written: same family, same length, same network.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} a
|
|
77
|
+
* @param {string} b
|
|
78
|
+
*/
|
|
79
|
+
export function samePrefix(a, b) {
|
|
80
|
+
const x = parseRange(a);
|
|
81
|
+
const y = parseRange(b);
|
|
82
|
+
return Boolean(x && y && x.family === y.family && x.length === y.length && x.first === y.first);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The family of an address or prefix, or undefined when it is not one.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} text
|
|
89
|
+
*/
|
|
90
|
+
export const familyOf = (text) => parseRange(text)?.family;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A number back to the address it stands for, with IPv6 written short as RFC 5952 asks.
|
|
94
|
+
*
|
|
95
|
+
* @param {bigint} value
|
|
96
|
+
* @param {Family} family
|
|
97
|
+
*/
|
|
98
|
+
export function formatAddress(value, family) {
|
|
99
|
+
if (family === "ipv4") return [24n, 16n, 8n, 0n].map((shift) => String((value >> shift) & 255n)).join(".");
|
|
100
|
+
|
|
101
|
+
const groups = Array.from({ length: 8 }, (_, index) => ((value >> BigInt(112 - index * 16)) & 0xffffn).toString(16));
|
|
102
|
+
|
|
103
|
+
// The longest run of two or more zero groups becomes `::`.
|
|
104
|
+
let start = -1;
|
|
105
|
+
let length = 0;
|
|
106
|
+
for (let at = 0; at < 8;) {
|
|
107
|
+
let end = at;
|
|
108
|
+
while (end < 8 && groups[end] === "0") end++;
|
|
109
|
+
if (end - at > length && end - at > 1) {
|
|
110
|
+
start = at;
|
|
111
|
+
length = end - at;
|
|
112
|
+
}
|
|
113
|
+
at = end === at ? at + 1 : end;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (start < 0) return groups.join(":");
|
|
117
|
+
return `${groups.slice(0, start).join(":")}::${groups.slice(start + length).join(":")}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The network a prefix or interface address is in, such as `10.0.0.0/24` for `10.0.0.1/24`.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} text
|
|
124
|
+
*/
|
|
125
|
+
export function networkOf(text) {
|
|
126
|
+
const range = parseRange(text);
|
|
127
|
+
return range ? `${formatAddress(range.first, range.family)}/${range.length}` : text;
|
|
128
|
+
}
|