@takodotid/circuit 0.34.2 → 0.35.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.
Files changed (240) hide show
  1. package/README.md +13 -14
  2. package/dist/chunks/index-20kcjxjg.js +174 -0
  3. package/dist/chunks/index-2r4bbhac.js +977 -0
  4. package/dist/chunks/index-n7w3tg5y.js +149 -0
  5. package/dist/chunks/new-w3zys5y4.js +172 -0
  6. package/dist/chunks/server-gzsf5me5.js +259 -0
  7. package/dist/cli/index.js +11347 -9018
  8. package/dist/helpers/index.js +594 -0
  9. package/dist/index.js +9 -28
  10. package/dist/types/core/{addresses.d.ts → address-space.d.ts} +23 -7
  11. package/dist/types/core/address.d.ts +44 -0
  12. package/dist/types/core/cabling.d.ts +41 -0
  13. package/dist/types/core/cross-checks.d.ts +28 -0
  14. package/dist/types/core/define.d.ts +25 -3
  15. package/dist/types/core/project-files.d.ts +12 -0
  16. package/dist/types/core/schema-docs.d.ts +13 -4
  17. package/dist/types/core/secrets.d.ts +62 -21
  18. package/dist/types/core/{graph.d.ts → topology.d.ts} +32 -12
  19. package/dist/types/core/utils.d.ts +8 -0
  20. package/dist/types/core/validator.d.ts +19 -0
  21. package/dist/types/helpers/bgp.d.ts +97 -0
  22. package/dist/types/helpers/checks.d.ts +32 -0
  23. package/dist/types/helpers/exchange.d.ts +49 -0
  24. package/dist/types/helpers/filters.d.ts +51 -0
  25. package/dist/types/helpers/index.d.ts +10 -0
  26. package/dist/types/helpers/route-server.d.ts +52 -0
  27. package/dist/types/index.d.ts +5 -3
  28. package/dist/types/integrations/librenms.d.ts +60 -0
  29. package/dist/types/integrations/netbox.d.ts +89 -0
  30. package/dist/types/integrations/peeringdb.d.ts +24 -0
  31. package/dist/types/integrations/prometheus.d.ts +24 -0
  32. package/dist/types/integrations/registries.d.ts +8 -0
  33. package/dist/types/platforms/bgp-wait.d.ts +18 -0
  34. package/dist/types/platforms/catalog.d.ts +109 -0
  35. package/dist/types/platforms/derive.d.ts +17 -0
  36. package/dist/types/platforms/fortios/firewall.d.ts +45 -0
  37. package/dist/types/platforms/fortios/index.d.ts +134 -0
  38. package/dist/types/platforms/fortios/names.d.ts +52 -0
  39. package/dist/types/platforms/fortios/plan.d.ts +104 -0
  40. package/dist/types/platforms/fortios/render.d.ts +75 -0
  41. package/dist/types/platforms/fortios/routing.d.ts +56 -0
  42. package/dist/types/platforms/fortios/tree.d.ts +37 -0
  43. package/dist/types/platforms/index.d.ts +13 -0
  44. package/dist/types/platforms/junos/bgp.d.ts +44 -0
  45. package/dist/types/platforms/junos/index.d.ts +114 -0
  46. package/dist/types/platforms/junos/render.d.ts +154 -0
  47. package/dist/types/platforms/line-diff.d.ts +67 -0
  48. package/dist/types/platforms/platform.d.ts +120 -0
  49. package/dist/types/platforms/raisecom-ros/index.d.ts +71 -0
  50. package/dist/types/platforms/raisecom-ros/render.d.ts +41 -0
  51. package/dist/types/platforms/routeros/export.d.ts +59 -0
  52. package/dist/types/platforms/routeros/index.d.ts +96 -0
  53. package/dist/types/{adapters/devices → platforms}/routeros/models.d.ts +4 -0
  54. package/dist/types/platforms/routeros/plan.d.ts +54 -0
  55. package/dist/types/platforms/routeros/render.d.ts +240 -0
  56. package/dist/types/platforms/vrp/index.d.ts +83 -0
  57. package/dist/types/platforms/vrp/render.d.ts +96 -0
  58. package/dist/types/schema/common.d.ts +12 -6
  59. package/dist/types/schema/index.d.ts +1 -0
  60. package/dist/types/schema/interfaces.d.ts +14 -6
  61. package/dist/types/schema/policy.d.ts +18 -10
  62. package/dist/types/schema/routing.d.ts +14 -1
  63. package/dist/types/schema/system.d.ts +16 -6
  64. package/dist/types/schema/time-zones.d.ts +2 -0
  65. package/dist/types/transport/console.d.ts +79 -23
  66. package/dist/types/transport/path-check.d.ts +32 -0
  67. package/dist/types/transport/ssh.d.ts +17 -15
  68. package/package.json +17 -11
  69. package/src/cli/graph-format.ts +91 -0
  70. package/src/cli/index.ts +1074 -484
  71. package/src/cli/new.ts +208 -175
  72. package/src/cli/rename.ts +97 -91
  73. package/src/core/address-space.ts +320 -0
  74. package/src/core/address.ts +202 -0
  75. package/src/core/cabling.ts +154 -0
  76. package/src/core/cross-checks.ts +364 -0
  77. package/src/core/define.ts +26 -3
  78. package/src/core/project-files.ts +35 -0
  79. package/src/core/schema-docs.ts +138 -130
  80. package/src/core/secrets.ts +170 -76
  81. package/src/core/topology.ts +656 -0
  82. package/src/core/utils.ts +37 -0
  83. package/src/core/validator.ts +407 -0
  84. package/src/helpers/bgp.ts +301 -0
  85. package/src/helpers/checks.ts +178 -0
  86. package/src/helpers/exchange.ts +117 -0
  87. package/src/helpers/filters.ts +107 -0
  88. package/src/helpers/index.ts +12 -0
  89. package/src/helpers/route-server.ts +212 -0
  90. package/src/index.ts +6 -3
  91. package/src/integrations/librenms.ts +291 -0
  92. package/src/integrations/netbox.ts +620 -0
  93. package/src/integrations/peeringdb.ts +173 -0
  94. package/src/integrations/prometheus.ts +90 -0
  95. package/src/integrations/registries.ts +25 -0
  96. package/src/platforms/bgp-wait.ts +41 -0
  97. package/src/platforms/catalog.ts +69 -0
  98. package/src/platforms/derive.ts +117 -0
  99. package/src/{adapters/devices → platforms}/fortios/TRAPS.md +6 -0
  100. package/src/platforms/fortios/firewall.ts +377 -0
  101. package/src/platforms/fortios/index.ts +1060 -0
  102. package/src/platforms/fortios/names.ts +130 -0
  103. package/src/platforms/fortios/plan.ts +929 -0
  104. package/src/platforms/fortios/render.ts +779 -0
  105. package/src/platforms/fortios/routing.ts +476 -0
  106. package/src/platforms/fortios/tree.ts +129 -0
  107. package/src/platforms/index.ts +36 -0
  108. package/src/{adapters/devices → platforms}/junos/TRAPS.md +8 -0
  109. package/src/platforms/junos/bgp.ts +293 -0
  110. package/src/platforms/junos/index.ts +1006 -0
  111. package/src/platforms/junos/render.ts +967 -0
  112. package/src/platforms/line-diff.ts +244 -0
  113. package/src/platforms/platform.ts +122 -0
  114. package/src/platforms/raisecom-ros/TRAPS.md +40 -0
  115. package/src/platforms/raisecom-ros/index.ts +731 -0
  116. package/src/platforms/raisecom-ros/render.ts +328 -0
  117. package/src/{adapters/devices → platforms}/routeros/TRAPS.md +16 -2
  118. package/src/platforms/routeros/export.ts +342 -0
  119. package/src/platforms/routeros/index.ts +636 -0
  120. package/src/{adapters/devices → platforms}/routeros/models.ts +2 -0
  121. package/src/platforms/routeros/plan.ts +444 -0
  122. package/src/platforms/routeros/render.ts +1483 -0
  123. package/src/platforms/vrp/TRAPS.md +45 -0
  124. package/src/platforms/vrp/index.ts +652 -0
  125. package/src/platforms/vrp/render.ts +615 -0
  126. package/src/schema/common.ts +15 -6
  127. package/src/schema/index.ts +1 -0
  128. package/src/schema/interfaces.ts +15 -5
  129. package/src/schema/policy.ts +22 -10
  130. package/src/schema/routing.ts +16 -1
  131. package/src/schema/system.ts +16 -6
  132. package/src/schema/time-zones.ts +482 -0
  133. package/src/studio/data.ts +172 -168
  134. package/src/studio/git.ts +95 -90
  135. package/src/studio/public/app.js +161 -150
  136. package/src/studio/public/compare.js +219 -212
  137. package/src/studio/public/diff.js +77 -75
  138. package/src/studio/public/dom.js +118 -108
  139. package/src/studio/public/evaluate.js +584 -545
  140. package/src/studio/public/export.js +120 -118
  141. package/src/studio/public/graph.js +1343 -1266
  142. package/src/studio/public/net.js +115 -109
  143. package/src/studio/public/tables.js +1135 -1083
  144. package/src/studio/public/tools.js +561 -529
  145. package/src/studio/server.ts +202 -196
  146. package/src/transport/console.ts +363 -271
  147. package/src/transport/path-check.ts +91 -0
  148. package/src/transport/ssh.ts +98 -94
  149. package/templates/colocation/checks.ts +11 -6
  150. package/templates/colocation/edge-01.ts +9 -9
  151. package/templates/colocation/routing.ts +2 -2
  152. package/templates/colocation/tenancy.ts +3 -3
  153. package/templates/colocation/tor-01.ts +3 -3
  154. package/templates/edge-router/checks.ts +4 -4
  155. package/templates/edge-router/edge-01.ts +5 -5
  156. package/templates/edge-router/routing.ts +2 -2
  157. package/templates/project/AGENTS.md +5 -1
  158. package/templates/single-site/checks.ts +2 -2
  159. package/dist/chunks/index-47debt16.js +0 -956
  160. package/dist/chunks/index-v8zgr7wm.js +0 -128
  161. package/dist/chunks/new-ny89s7y7.js +0 -166
  162. package/dist/chunks/server-tjbb3rqc.js +0 -251
  163. package/dist/presets/index.js +0 -394
  164. package/dist/types/adapters/devices/catalog.d.ts +0 -86
  165. package/dist/types/adapters/devices/derive.d.ts +0 -7
  166. package/dist/types/adapters/devices/fortios/firewall.d.ts +0 -23
  167. package/dist/types/adapters/devices/fortios/index.d.ts +0 -58
  168. package/dist/types/adapters/devices/fortios/names.d.ts +0 -48
  169. package/dist/types/adapters/devices/fortios/plan.d.ts +0 -28
  170. package/dist/types/adapters/devices/fortios/render.d.ts +0 -38
  171. package/dist/types/adapters/devices/fortios/routing.d.ts +0 -32
  172. package/dist/types/adapters/devices/fortios/tree.d.ts +0 -33
  173. package/dist/types/adapters/devices/index.d.ts +0 -3
  174. package/dist/types/adapters/devices/junos/bgp.d.ts +0 -11
  175. package/dist/types/adapters/devices/junos/index.d.ts +0 -42
  176. package/dist/types/adapters/devices/junos/render.d.ts +0 -52
  177. package/dist/types/adapters/devices/lines.d.ts +0 -56
  178. package/dist/types/adapters/devices/raisecom-ros/index.d.ts +0 -28
  179. package/dist/types/adapters/devices/routeros/export.d.ts +0 -39
  180. package/dist/types/adapters/devices/routeros/index.d.ts +0 -30
  181. package/dist/types/adapters/devices/routeros/plan.d.ts +0 -4
  182. package/dist/types/adapters/devices/routeros/render.d.ts +0 -100
  183. package/dist/types/adapters/devices/types.d.ts +0 -40
  184. package/dist/types/adapters/devices/vrp/index.d.ts +0 -27
  185. package/dist/types/adapters/registries/index.d.ts +0 -3
  186. package/dist/types/adapters/registries/librenms.d.ts +0 -15
  187. package/dist/types/adapters/registries/peeringdb.d.ts +0 -16
  188. package/dist/types/adapters/registries/prometheus.d.ts +0 -16
  189. package/dist/types/core/across.d.ts +0 -4
  190. package/dist/types/core/addr.d.ts +0 -34
  191. package/dist/types/core/files.d.ts +0 -6
  192. package/dist/types/core/merge.d.ts +0 -4
  193. package/dist/types/core/time.d.ts +0 -2
  194. package/dist/types/core/validate.d.ts +0 -2
  195. package/dist/types/presets/index.d.ts +0 -183
  196. package/src/adapters/devices/catalog.ts +0 -41
  197. package/src/adapters/devices/derive.ts +0 -59
  198. package/src/adapters/devices/fortios/firewall.ts +0 -362
  199. package/src/adapters/devices/fortios/index.ts +0 -923
  200. package/src/adapters/devices/fortios/names.ts +0 -115
  201. package/src/adapters/devices/fortios/plan.ts +0 -888
  202. package/src/adapters/devices/fortios/render.ts +0 -768
  203. package/src/adapters/devices/fortios/routing.ts +0 -444
  204. package/src/adapters/devices/fortios/tree.ts +0 -121
  205. package/src/adapters/devices/index.ts +0 -9
  206. package/src/adapters/devices/junos/bgp.ts +0 -269
  207. package/src/adapters/devices/junos/index.ts +0 -862
  208. package/src/adapters/devices/junos/render.ts +0 -872
  209. package/src/adapters/devices/lines.ts +0 -212
  210. package/src/adapters/devices/raisecom-ros/TRAPS.md +0 -25
  211. package/src/adapters/devices/raisecom-ros/index.ts +0 -693
  212. package/src/adapters/devices/routeros/export.ts +0 -321
  213. package/src/adapters/devices/routeros/index.ts +0 -346
  214. package/src/adapters/devices/routeros/plan.ts +0 -398
  215. package/src/adapters/devices/routeros/render.ts +0 -1490
  216. package/src/adapters/devices/types.ts +0 -38
  217. package/src/adapters/devices/vrp/TRAPS.md +0 -26
  218. package/src/adapters/devices/vrp/index.ts +0 -865
  219. package/src/adapters/registries/index.ts +0 -19
  220. package/src/adapters/registries/librenms.ts +0 -92
  221. package/src/adapters/registries/peeringdb.ts +0 -169
  222. package/src/adapters/registries/prometheus.ts +0 -71
  223. package/src/cli/graph.ts +0 -89
  224. package/src/core/across.ts +0 -350
  225. package/src/core/addr.ts +0 -174
  226. package/src/core/addresses.ts +0 -316
  227. package/src/core/files.ts +0 -32
  228. package/src/core/graph.ts +0 -640
  229. package/src/core/merge.ts +0 -19
  230. package/src/core/time.ts +0 -15
  231. package/src/core/validate.ts +0 -348
  232. package/src/presets/index.ts +0 -579
  233. /package/dist/types/{adapters/devices → platforms}/fortios/models.d.ts +0 -0
  234. /package/dist/types/{adapters/devices → platforms}/junos/models.d.ts +0 -0
  235. /package/dist/types/{adapters/devices → platforms}/raisecom-ros/models.d.ts +0 -0
  236. /package/dist/types/{adapters/devices → platforms}/vrp/models.d.ts +0 -0
  237. /package/src/{adapters/devices → platforms}/fortios/models.ts +0 -0
  238. /package/src/{adapters/devices → platforms}/junos/models.ts +0 -0
  239. /package/src/{adapters/devices → platforms}/raisecom-ros/models.ts +0 -0
  240. /package/src/{adapters/devices → platforms}/vrp/models.ts +0 -0
@@ -2,242 +2,249 @@
2
2
  /// <reference lib="dom" />
3
3
  // Comparing the network now with another branch, tag or commit: which devices changed and how their config differs, what the graph gains and loses, and what validate finds newly or no longer.
4
4
 
5
- import { go, h, link } from "./dom.js";
6
- import { compareNetworks } from "./evaluate.js";
7
- import { LAYER_NAMES, setComparison } from "./graph.js";
8
- import { deviceLink, empty, heading, lineDiff, prose, table } from "./tables.js";
5
+ import { Dom } from "./dom.js";
6
+ import { Evaluate } from "./evaluate.js";
7
+ import { Graph } from "./graph.js";
8
+ import { Tables } from "./tables.js";
9
9
 
10
10
  /** @typedef {import("../data").StudioData} StudioData */
11
11
  /** @typedef {import("../git").Commit} Commit */
12
12
  /** @typedef {{ commit: Commit, network: StudioData & { error?: undefined }, error?: undefined } | { error: string }} Comparison */
13
13
 
14
- /** Comparisons asked for, by the name asked for, kept while the page is open. @type {Map<string, Promise<Comparison>>} */
15
- const asked = new Map();
16
-
17
- /** @type {Promise<{ names: string[], commits: Commit[] } | { error: string }> | undefined} */
18
- let references;
19
-
20
- /**
21
- * Read JSON from the server, or what went wrong.
22
- *
23
- * @template T
24
- * @param {string} path
25
- * @returns {Promise<T | { error: string }>}
26
- */
27
- export async function fetchJson(path) {
28
- try {
29
- return await (await fetch(path, { cache: "no-store" })).json();
30
- } catch (error) {
31
- return { error: error instanceof Error ? error.message : String(error) };
14
+ export class Compare {
15
+ /** Comparisons asked for, by the name asked for, kept while the page is open. @type {Map<string, Promise<Comparison>>} */
16
+ static #asked = new Map();
17
+
18
+ /** @type {Promise<{ names: string[], commits: Commit[] } | { error: string }> | undefined} */
19
+ static #references;
20
+
21
+ /**
22
+ * Read JSON from the server, or what went wrong.
23
+ *
24
+ * @template T
25
+ * @param {string} path
26
+ * @returns {Promise<T | { error: string }>}
27
+ */
28
+ static async fetchJson(path) {
29
+ try {
30
+ return await (await fetch(path, { cache: "no-store" })).json();
31
+ } catch (error) {
32
+ return { error: error instanceof Error ? error.message : String(error) };
33
+ }
32
34
  }
33
- }
34
35
 
35
- /**
36
- * A commit in a line: its short name, subject, author and day.
37
- *
38
- * @param {Commit} commit
39
- */
40
- export const describeCommit = (commit) =>
41
- `${commit.short}, ${commit.subject}, by ${commit.author} on ${new Date(commit.date).toLocaleDateString()}`;
42
-
43
- /**
44
- * The picker: a branch, tag or recent commit, or any name typed.
45
- *
46
- * @param {string | undefined} current
47
- */
48
- function picker(current) {
49
- const input = h("input", { type: "text", value: current ?? "", placeholder: "main, a tag, or a commit", spellcheck: "false" });
50
- const list = h("select", {}, h("option", { value: "" }, "Pick one…"));
51
- list.addEventListener("change", () => {
52
- if (list.value) go(`compare/${encodeURIComponent(list.value)}`);
53
- });
54
-
55
- references ??= fetchJson("refs");
56
- void references.then((found) => {
57
- if ("error" in found) return list.replaceChildren(h("option", { value: "" }, "No git history here"));
58
- const names = found.names.map((name) => h("option", { value: name }, name));
59
- const commits = found.commits.map((commit) => h("option", { value: commit.commit }, describeCommit(commit)));
60
- list.append(h("optgroup", { label: "Branches and tags" }, ...names), h("optgroup", { label: "Latest commits" }, ...commits));
61
- });
62
-
63
- const compare = h("button", { type: "submit", class: "primary" }, "Compare");
64
- compare.addEventListener("click", (event) => {
65
- event.preventDefault();
66
- if (input.value.trim()) go(`compare/${encodeURIComponent(input.value.trim())}`);
67
- });
68
-
69
- return h(
70
- "form",
71
- { class: "tool-form" },
72
- h("label", { class: "field" }, h("span", {}, "Branch, tag or commit"), list),
73
- h("label", { class: "field" }, h("span", {}, "Or a name"), input),
74
- compare
75
- );
76
- }
36
+ /**
37
+ * A commit in a line: its short name, subject, author and day.
38
+ *
39
+ * @param {Commit} commit
40
+ */
41
+ static #describeCommit(commit) {
42
+ return `${commit.short}, ${commit.subject}, by ${commit.author} on ${new Date(commit.date).toLocaleDateString()}`;
43
+ }
77
44
 
78
- /**
79
- * @param {HTMLElement} area
80
- * @param {string} name
81
- * @param {Commit} commit
82
- * @param {StudioData} before
83
- * @param {StudioData} after
84
- */
85
- function showDifferences(area, name, commit, before, after) {
86
- const found = compareNetworks(before, after);
87
- const { devices, edges, findings } = found;
88
- const nothing =
89
- !devices.added.length && !devices.removed.length && !devices.changed.length && !edges.added.length && !edges.removed.length;
90
-
91
- const layers = [...new Set([...edges.added, ...edges.removed].map((edge) => edge.layer))];
92
- const showGraph = link("Show the changes in the graph", () => {
93
- setComparison({ name, added: edges.added, removed: edges.removed });
94
- go(`graph/${layers[0] ?? "l1"}`);
95
- });
96
-
97
- area.append(
98
- h("p", {}, "Your files now, beside ", h("strong", {}, name), `: ${describeCommit(commit)}.`),
99
- nothing
100
- ? empty("Nothing differs: every device would run the same, and the graph is the same.")
101
- : h(
102
- "p",
103
- {},
104
- `${devices.changed.length} changed, ${devices.added.length} added and ${devices.removed.length} removed ${devices.changed.length + devices.added.length + devices.removed.length === 1 ? "device" : "devices"}; ${edges.added.length} ${edges.added.length === 1 ? "line" : "lines"} of the graph added and ${edges.removed.length} gone. `,
105
- layers.length ? showGraph : undefined
106
- )
107
- );
108
-
109
- if (devices.added.length || devices.removed.length) {
110
- area.append(
111
- heading("Devices added and removed"),
112
- table({
113
- head: ["Device", "Change"].map((label) => ({ label })),
114
- rows: [
115
- ...devices.added.map((device) => ({ cells: [deviceLink(device), "added"] })),
116
- ...devices.removed.map((device) => ({ cells: [device, "removed"] })),
117
- ],
118
- })
45
+ /**
46
+ * The picker: a branch, tag or recent commit, or any name typed.
47
+ *
48
+ * @param {string | undefined} current
49
+ */
50
+ static #picker(current) {
51
+ const input = Dom.h("input", { type: "text", value: current ?? "", placeholder: "main, a tag, or a commit", spellcheck: "false" });
52
+ const list = Dom.h("select", {}, Dom.h("option", { value: "" }, "Pick one…"));
53
+ list.addEventListener("change", () => {
54
+ if (list.value) Dom.go(`compare/${encodeURIComponent(list.value)}`);
55
+ });
56
+
57
+ Compare.#references ??= Compare.fetchJson("refs");
58
+ void Compare.#references.then((found) => {
59
+ if ("error" in found) return list.replaceChildren(Dom.h("option", { value: "" }, "No git history here"));
60
+ const names = found.names.map((name) => Dom.h("option", { value: name }, name));
61
+ const commits = found.commits.map((commit) => Dom.h("option", { value: commit.commit }, Compare.#describeCommit(commit)));
62
+ list.append(
63
+ Dom.h("optgroup", { label: "Branches and tags" }, ...names),
64
+ Dom.h("optgroup", { label: "Latest commits" }, ...commits)
65
+ );
66
+ });
67
+
68
+ const compare = Dom.h("button", { type: "submit", class: "primary" }, "Compare");
69
+ compare.addEventListener("click", (event) => {
70
+ event.preventDefault();
71
+ if (input.value.trim()) Dom.go(`compare/${encodeURIComponent(input.value.trim())}`);
72
+ });
73
+
74
+ return Dom.h(
75
+ "form",
76
+ { class: "tool-form" },
77
+ Dom.h("label", { class: "field" }, Dom.h("span", {}, "Branch, tag or commit"), list),
78
+ Dom.h("label", { class: "field" }, Dom.h("span", {}, "Or a name"), input),
79
+ compare
119
80
  );
120
81
  }
121
82
 
122
- if (devices.changed.length) {
83
+ /**
84
+ * @param {HTMLElement} area
85
+ * @param {string} name
86
+ * @param {Commit} commit
87
+ * @param {StudioData} before
88
+ * @param {StudioData} after
89
+ */
90
+ static #showDifferences(area, name, commit, before, after) {
91
+ const found = Evaluate.compareNetworks(before, after);
92
+ const { devices, edges, findings } = found;
93
+ const nothing =
94
+ !devices.added.length && !devices.removed.length && !devices.changed.length && !edges.added.length && !edges.removed.length;
95
+
96
+ const layers = [...new Set([...edges.added, ...edges.removed].map((edge) => edge.layer))];
97
+ const showGraph = Dom.link("Show the changes in the graph", () => {
98
+ Graph.setComparison({ name, added: edges.added, removed: edges.removed });
99
+ Dom.go(`graph/${layers[0] ?? "l1"}`);
100
+ });
101
+
123
102
  area.append(
124
- heading("Config that changes"),
125
- prose(
126
- "What each device would run, ",
127
- h("span", { class: "removed" }, "then -"),
128
- " and ",
129
- h("span", { class: "added" }, "now +"),
130
- "."
131
- )
103
+ Dom.h("p", {}, "Your files now, beside ", Dom.h("strong", {}, name), `: ${Compare.#describeCommit(commit)}.`),
104
+ nothing
105
+ ? Tables.empty("Nothing differs: every device would run the same, and the graph is the same.")
106
+ : Dom.h(
107
+ "p",
108
+ {},
109
+ `${devices.changed.length} changed, ${devices.added.length} added and ${devices.removed.length} removed ${devices.changed.length + devices.added.length + devices.removed.length === 1 ? "device" : "devices"}; ${edges.added.length} ${edges.added.length === 1 ? "line" : "lines"} of the graph added and ${edges.removed.length} gone. `,
110
+ layers.length ? showGraph : undefined
111
+ )
132
112
  );
133
113
 
134
- for (const device of devices.changed) {
114
+ if (devices.added.length || devices.removed.length) {
115
+ area.append(
116
+ Tables.heading("Devices added and removed"),
117
+ Tables.of({
118
+ head: ["Device", "Change"].map((label) => ({ label })),
119
+ rows: [
120
+ ...devices.added.map((device) => ({ cells: [Tables.deviceLink(device), "added"] })),
121
+ ...devices.removed.map((device) => ({ cells: [device, "removed"] })),
122
+ ],
123
+ })
124
+ );
125
+ }
126
+
127
+ if (devices.changed.length) {
135
128
  area.append(
136
- h(
137
- "details",
138
- { class: "change" },
139
- h("summary", {}, device),
140
- lineDiff(before.rendered[device] ?? "", after.rendered[device] ?? "")
129
+ Tables.heading("Config that changes"),
130
+ Tables.prose(
131
+ "What each device would run, ",
132
+ Dom.h("span", { class: "removed" }, "then -"),
133
+ " and ",
134
+ Dom.h("span", { class: "added" }, "now +"),
135
+ "."
141
136
  )
142
137
  );
138
+
139
+ for (const device of devices.changed) {
140
+ area.append(
141
+ Dom.h(
142
+ "details",
143
+ { class: "change" },
144
+ Dom.h("summary", {}, device),
145
+ Tables.lineDiff(before.rendered[device] ?? "", after.rendered[device] ?? "")
146
+ )
147
+ );
148
+ }
143
149
  }
144
- }
145
150
 
146
- if (edges.added.length || edges.removed.length) {
147
- const nameOf = (/** @type {string} */ id) =>
148
- [...after.graph.nodes, ...before.graph.nodes].find((node) => node.id === id)?.label ?? id;
151
+ if (edges.added.length || edges.removed.length) {
152
+ const nameOf = (/** @type {string} */ id) =>
153
+ [...after.graph.nodes, ...before.graph.nodes].find((node) => node.id === id)?.label ?? id;
149
154
 
150
- area.append(
151
- heading("The graph"),
152
- table({
153
- csv: `compare-${name}-graph`,
154
- head: ["Layer", "Change", "What", "From", "To"].map((label) => ({ label })),
155
- rows: [
156
- ...edges.added.map((edge) => ({
157
- cells: [
158
- LAYER_NAMES[edge.layer],
159
- h("span", { class: "added" }, "added"),
160
- edge.label,
161
- nameOf(edge.from),
162
- nameOf(edge.to),
163
- ],
164
- })),
165
- ...edges.removed.map((edge) => ({
166
- cells: [
167
- LAYER_NAMES[edge.layer],
168
- h("span", { class: "removed" }, "gone"),
169
- edge.label,
170
- nameOf(edge.from),
171
- nameOf(edge.to),
172
- ],
173
- })),
174
- ],
175
- })
176
- );
155
+ area.append(
156
+ Tables.heading("The graph"),
157
+ Tables.of({
158
+ csv: `compare-${name}-graph`,
159
+ head: ["Layer", "Change", "What", "From", "To"].map((label) => ({ label })),
160
+ rows: [
161
+ ...edges.added.map((edge) => ({
162
+ cells: [
163
+ Graph.LAYER_NAMES[edge.layer],
164
+ Dom.h("span", { class: "added" }, "added"),
165
+ edge.label,
166
+ nameOf(edge.from),
167
+ nameOf(edge.to),
168
+ ],
169
+ })),
170
+ ...edges.removed.map((edge) => ({
171
+ cells: [
172
+ Graph.LAYER_NAMES[edge.layer],
173
+ Dom.h("span", { class: "removed" }, "gone"),
174
+ edge.label,
175
+ nameOf(edge.from),
176
+ nameOf(edge.to),
177
+ ],
178
+ })),
179
+ ],
180
+ })
181
+ );
182
+ }
183
+
184
+ if (findings.added.length || findings.resolved.length) {
185
+ area.append(
186
+ Dom.h("h3", {}, "Found by ", Dom.h("code", {}, "circuit validate")),
187
+ Tables.of({
188
+ head: ["", "Device", "Message"].map((label) => ({ label })),
189
+ rows: [
190
+ ...findings.added.map((one) => ({
191
+ cells: [Dom.h("span", { class: "removed" }, `new ${one.level}`), one.device ?? "the network", one.message],
192
+ })),
193
+ ...findings.resolved.map((one) => ({
194
+ cells: [Dom.h("span", { class: "added" }, `no longer`), one.device ?? "the network", one.message],
195
+ })),
196
+ ],
197
+ })
198
+ );
199
+ }
177
200
  }
178
201
 
179
- if (findings.added.length || findings.resolved.length) {
180
- area.append(
181
- h("h3", {}, "Found by ", h("code", {}, "circuit validate")),
182
- table({
183
- head: ["", "Device", "Message"].map((label) => ({ label })),
184
- rows: [
185
- ...findings.added.map((one) => ({
186
- cells: [h("span", { class: "removed" }, `new ${one.level}`), one.device ?? "the network", one.message],
187
- })),
188
- ...findings.resolved.map((one) => ({
189
- cells: [h("span", { class: "added" }, `no longer`), one.device ?? "the network", one.message],
190
- })),
191
- ],
192
- })
202
+ /**
203
+ * Draw the comparison the address names, such as `["compare", "main"]`, into a container.
204
+ *
205
+ * @param {string[]} route
206
+ * @param {HTMLElement} container
207
+ * @param {StudioData} now
208
+ */
209
+ static show(route, container, now) {
210
+ container.replaceChildren();
211
+ const [, name] = route;
212
+
213
+ container.append(
214
+ Tables.heading("Compare", "h2"),
215
+ Tables.prose(
216
+ "Your files now, beside another branch, tag or commit: which devices change and how, what the graph gains and loses, and what ",
217
+ Dom.h("code", {}, "circuit validate"),
218
+ " finds newly or no longer. Studio reads the other commit with the Circuit installed now."
219
+ ),
220
+ Compare.#picker(name)
193
221
  );
194
- }
195
- }
196
222
 
197
- /**
198
- * Draw the comparison the address names, such as `["compare", "main"]`, into a container.
199
- *
200
- * @param {string[]} route
201
- * @param {HTMLElement} container
202
- * @param {StudioData} now
203
- */
204
- export function showCompare(route, container, now) {
205
- container.replaceChildren();
206
- const [, name] = route;
207
-
208
- container.append(
209
- heading("Compare", "h2"),
210
- prose(
211
- "Your files now, beside another branch, tag or commit: which devices change and how, what the graph gains and loses, and what ",
212
- h("code", {}, "circuit validate"),
213
- " finds newly or no longer. Studio reads the other commit with the Circuit installed now."
214
- ),
215
- picker(name)
216
- );
217
-
218
- if (!name) return;
219
-
220
- const area = h("div", {}, empty(`Reading the network at ${name}…`));
221
- container.append(area);
222
-
223
- if (!asked.has(name)) asked.set(name, /** @type {Promise<Comparison>} */ (fetchJson(`compare?ref=${encodeURIComponent(name)}`)));
224
- const shown = location.hash;
225
-
226
- void asked.get(name)?.then((result) => {
227
- // The reader may have gone elsewhere while it was read.
228
- if (location.hash !== shown) return;
229
- area.replaceChildren();
230
-
231
- if (result.error !== undefined) {
232
- asked.delete(name);
233
- return area.append(empty(`It could not be read: ${result.error}`));
234
- }
223
+ if (!name) return;
235
224
 
236
- if (result.network.error !== undefined) {
237
- asked.delete(name);
238
- return area.append(empty(`Its config could not be read: ${result.network.error}`));
239
- }
225
+ const area = Dom.h("div", {}, Tables.empty(`Reading the network at ${name}…`));
226
+ container.append(area);
227
+
228
+ if (!Compare.#asked.has(name))
229
+ Compare.#asked.set(name, /** @type {Promise<Comparison>} */ (Compare.fetchJson(`compare?ref=${encodeURIComponent(name)}`)));
230
+ const shown = location.hash;
240
231
 
241
- showDifferences(area, name, result.commit, result.network, now);
242
- });
232
+ void Compare.#asked.get(name)?.then((result) => {
233
+ // The reader may have gone elsewhere while it was read.
234
+ if (location.hash !== shown) return;
235
+ area.replaceChildren();
236
+
237
+ if (result.error !== undefined) {
238
+ Compare.#asked.delete(name);
239
+ return area.append(Tables.empty(`It could not be read: ${result.error}`));
240
+ }
241
+
242
+ if (result.network.error !== undefined) {
243
+ Compare.#asked.delete(name);
244
+ return area.append(Tables.empty(`Its config could not be read: ${result.network.error}`));
245
+ }
246
+
247
+ Compare.#showDifferences(area, name, result.commit, result.network, now);
248
+ });
249
+ }
243
250
  }
@@ -3,95 +3,97 @@
3
3
 
4
4
  /** @typedef {{ kind: "same" | "removed" | "added", text: string }} DiffLine */
5
5
 
6
- /** Past this many differences, two texts are too far apart to show line by line. */
7
- const MOST_DIFFERENCES = 4000;
8
-
9
- /**
10
- * The lines of `before` and `after` in order, each marked as kept, removed or added. Undefined when they differ in more than a few thousand lines.
11
- *
12
- * @param {readonly string[]} before
13
- * @param {readonly string[]} after
14
- * @returns {DiffLine[] | undefined}
15
- */
16
- export function diffLines(before, after) {
17
- const n = before.length;
18
- const m = after.length;
19
- const limit = Math.min(n + m, MOST_DIFFERENCES);
20
- const offset = limit + 1;
21
-
22
- // How far along `before` each diagonal reaches, and a copy of that for each number of differences, to walk back through.
23
- const reach = new Int32Array(2 * offset + 1);
24
- /** @type {Int32Array[]} */
25
- const trace = [];
6
+ export class Diff {
7
+ /** Past this many differences, two texts are too far apart to show line by line. */
8
+ static #MOST_DIFFERENCES = 4000;
26
9
 
27
10
  /**
28
- * Where diagonal k reached after d differences.
11
+ * The lines of `before` and `after` in order, each marked as kept, removed or added. Undefined when they differ in more than a few thousand lines.
29
12
  *
30
- * @param {number} d
31
- * @param {number} k
13
+ * @param {readonly string[]} before
14
+ * @param {readonly string[]} after
15
+ * @returns {DiffLine[] | undefined}
32
16
  */
33
- const reached = (d, k) => /** @type {Int32Array} */ (trace[d])[k + d] ?? 0;
34
-
35
- for (let d = 0; d <= limit; d++) {
36
- for (let k = -d; k <= d; k += 2) {
37
- const down = k === -d || (k !== d && (reach[k - 1 + offset] ?? 0) < (reach[k + 1 + offset] ?? 0));
38
- let x = down ? (reach[k + 1 + offset] ?? 0) : (reach[k - 1 + offset] ?? 0) + 1;
39
- let y = x - k;
40
-
41
- while (x < n && y < m && before[x] === after[y]) {
42
- x++;
43
- y++;
17
+ static lines(before, after) {
18
+ const n = before.length;
19
+ const m = after.length;
20
+ const limit = Math.min(n + m, Diff.#MOST_DIFFERENCES);
21
+ const offset = limit + 1;
22
+
23
+ // How far along `before` each diagonal reaches, and a copy of that for each number of differences, to walk back through.
24
+ const reach = new Int32Array(2 * offset + 1);
25
+ /** @type {Int32Array[]} */
26
+ const trace = [];
27
+
28
+ /**
29
+ * Where diagonal k reached after d differences.
30
+ *
31
+ * @param {number} d
32
+ * @param {number} k
33
+ */
34
+ const reached = (d, k) => /** @type {Int32Array} */ (trace[d])[k + d] ?? 0;
35
+
36
+ for (let d = 0; d <= limit; d++) {
37
+ for (let k = -d; k <= d; k += 2) {
38
+ const down = k === -d || (k !== d && (reach[k - 1 + offset] ?? 0) < (reach[k + 1 + offset] ?? 0));
39
+ let x = down ? (reach[k + 1 + offset] ?? 0) : (reach[k - 1 + offset] ?? 0) + 1;
40
+ let y = x - k;
41
+
42
+ while (x < n && y < m && before[x] === after[y]) {
43
+ x++;
44
+ y++;
45
+ }
46
+
47
+ reach[k + offset] = x;
48
+
49
+ if (x >= n && y >= m) {
50
+ trace.push(reach.slice(offset - d, offset + d + 1));
51
+ return Diff.#walkBack(before, after, trace, reached);
52
+ }
44
53
  }
45
54
 
46
- reach[k + offset] = x;
47
-
48
- if (x >= n && y >= m) {
49
- trace.push(reach.slice(offset - d, offset + d + 1));
50
- return walkBack(before, after, trace, reached);
51
- }
55
+ trace.push(reach.slice(offset - d, offset + d + 1));
52
56
  }
53
57
 
54
- trace.push(reach.slice(offset - d, offset + d + 1));
58
+ return undefined;
55
59
  }
56
60
 
57
- return undefined;
58
- }
61
+ /**
62
+ * The edits, found by walking back from the end through what each step reached.
63
+ *
64
+ * @param {readonly string[]} before
65
+ * @param {readonly string[]} after
66
+ * @param {Int32Array[]} trace
67
+ * @param {(d: number, k: number) => number} reached
68
+ * @returns {DiffLine[]}
69
+ */
70
+ static #walkBack(before, after, trace, reached) {
71
+ /** @type {DiffLine[]} */
72
+ const lines = [];
73
+ let x = before.length;
74
+ let y = after.length;
75
+
76
+ for (let d = trace.length - 1; d > 0; d--) {
77
+ const k = x - y;
78
+ const down = k === -d || (k !== d && reached(d - 1, k - 1) < reached(d - 1, k + 1));
79
+ const previousK = down ? k + 1 : k - 1;
80
+ const previousX = reached(d - 1, previousK);
81
+ const previousY = previousX - previousK;
82
+
83
+ while (x > previousX && y > previousY) {
84
+ lines.push({ kind: "same", text: before[--x] ?? "" });
85
+ y--;
86
+ }
87
+
88
+ if (down) lines.push({ kind: "added", text: after[--y] ?? "" });
89
+ else lines.push({ kind: "removed", text: before[--x] ?? "" });
90
+ }
59
91
 
60
- /**
61
- * The edits, found by walking back from the end through what each step reached.
62
- *
63
- * @param {readonly string[]} before
64
- * @param {readonly string[]} after
65
- * @param {Int32Array[]} trace
66
- * @param {(d: number, k: number) => number} reached
67
- * @returns {DiffLine[]}
68
- */
69
- function walkBack(before, after, trace, reached) {
70
- /** @type {DiffLine[]} */
71
- const lines = [];
72
- let x = before.length;
73
- let y = after.length;
74
-
75
- for (let d = trace.length - 1; d > 0; d--) {
76
- const k = x - y;
77
- const down = k === -d || (k !== d && reached(d - 1, k - 1) < reached(d - 1, k + 1));
78
- const previousK = down ? k + 1 : k - 1;
79
- const previousX = reached(d - 1, previousK);
80
- const previousY = previousX - previousK;
81
-
82
- while (x > previousX && y > previousY) {
92
+ while (x > 0 && y > 0) {
83
93
  lines.push({ kind: "same", text: before[--x] ?? "" });
84
94
  y--;
85
95
  }
86
96
 
87
- if (down) lines.push({ kind: "added", text: after[--y] ?? "" });
88
- else lines.push({ kind: "removed", text: before[--x] ?? "" });
97
+ return lines.reverse();
89
98
  }
90
-
91
- while (x > 0 && y > 0) {
92
- lines.push({ kind: "same", text: before[--x] ?? "" });
93
- y--;
94
- }
95
-
96
- return lines.reverse();
97
99
  }