querysub 0.2.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 (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1,503 @@
1
+ import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
2
+ import { SocketFunction } from "socket-function/SocketFunction";
3
+ import { css } from "typesafecss";
4
+ import { getAllNodeIds, getBrowserUrlNode, getOwnNodeId, syncNodesNow } from "../../src/-f-node-discovery/NodeDiscovery";
5
+ import { errorToUndefined, logErrors, timeoutToError } from "../../src/errors";
6
+ import { Querysub } from "../../src/4-querysub/QuerysubController";
7
+ import { NodeCapabilitiesController, getControllerNodeIdList } from "../../src/-g-core-values/NodeCapabilities";
8
+ import { AuthorityPath, debug_getAuthorityPaths, debug_isReadReady, nodePathAuthority } from "../../src/0-path-value-core/NodePathAuthorities";
9
+ import { lazy } from "socket-function/src/caching";
10
+ import { atomicObjectWrite } from "../../src/2-proxy/PathValueProxyWatcher";
11
+ import { formatNumber } from "socket-function/src/formatting/format";
12
+ import { Button } from "../../src/library-components/Button";
13
+ import { URLParam } from "../../src/library-components/URLParam";
14
+ import { InputLabelURL } from "../../src/library-components/InputLabel";
15
+ import { ColumnType, ColumnsType, RowType, Table, TableType } from "../../src/5-diagnostics/Table";
16
+ import { MaybePromise } from "socket-function/src/types";
17
+ import { requiresNetworkTrustHook } from "../../src/-d-trust/NetworkTrust2";
18
+ import { getDomain, isNoNetwork } from "../../src/config";
19
+ import { NodeMetadataController } from "../../src/5-diagnostics/nodeMetadata";
20
+ import { isDefined } from "../../src/misc";
21
+ import { errorMessage, formatValue, toSpaceCase } from "../../src/5-diagnostics/GenericFormat";
22
+ import { ValueAuditController } from "../../src/5-diagnostics/memoryValueAudit";
23
+ import { getExternalIP } from "../../src/misc/networking";
24
+ import dns from "dns";
25
+ import { getNodeIdDomain, getNodeIdIP } from "socket-function/src/nodeCache";
26
+ import { debugGetRawEdgeCert } from "../../src/-e-certs/EdgeCertController";
27
+ import ws from "ws";
28
+ import https from "https";
29
+ import debugbreak from "debugbreak";
30
+ import { forwardPort } from "socket-function/src/forwardPort";
31
+ import { setRecord } from "../-b-authorities/dnsAuthority";
32
+ import tls from "tls";
33
+ import net from "net";
34
+ import { ButtonSelector } from "../library-components/ButtonSelector";
35
+ import { assertIsManagementUser } from "./managementPages";
36
+ import { SocketRegistered } from "socket-function/SocketFunctionTypes";
37
+
38
+
39
+ type NodeData = {
40
+ nodeId: string;
41
+
42
+ ip?: string;
43
+ devToolsURL?: string;
44
+
45
+ live_isReadReady?: number;
46
+ live_authorityPaths?: AuthorityPath[];
47
+ live_exposedControllers?: string[];
48
+ live_entryPoint?: string;
49
+ apiError?: string;
50
+ table?: {
51
+ columns: ColumnsType;
52
+ row: RowType;
53
+ };
54
+
55
+ };
56
+
57
+ module.hotreload = true;
58
+ const showNotReadyNodes = new URLParam("showNotReadyNodes", false);
59
+ const selectedGroup = new URLParam("viewGroup", "Default");
60
+ export class NodeViewer extends qreact.Component {
61
+ state = {
62
+ access: "verifying" as "verifying" | "no" | "yes",
63
+ error: "",
64
+
65
+ nodeIds: [] as string[],
66
+ nodes: {} as {
67
+ // atomic
68
+ [nodeId: string]: NodeData
69
+ },
70
+ };
71
+ populateNodeInfos = lazy(() => {
72
+ logErrors((async () => {
73
+ try {
74
+ const controller = NodeViewerController.nodes[getBrowserUrlNode()];
75
+ let nodeIds = await controller.getAllNodeIds();
76
+ Querysub.commit(() => this.state.nodeIds = nodeIds);
77
+
78
+ let ourIP = await controller.getCallerIP();
79
+
80
+ await Promise.allSettled(nodeIds.map(async nodeId => {
81
+ let data: NodeData = { nodeId };
82
+ try {
83
+ data.table = await controller.getMiscInfo(nodeId);
84
+ data.live_isReadReady = await controller.live_isReadReady(nodeId);
85
+ data.live_exposedControllers = await controller.getExposedControllers(nodeId);
86
+ data.live_entryPoint = await controller.live_getEntryPoint(nodeId);
87
+ data.live_authorityPaths = await controller.live_getAuthorityPaths(nodeId);
88
+ data.ip = await controller.getNodeIP(nodeId);
89
+ if (data.ip === ourIP || data.ip === "127.0.0.1") {
90
+ data.devToolsURL = await controller.getInspectURL(nodeId);
91
+ if (data.devToolsURL) {
92
+ data.devToolsURL = data.devToolsURL + "<inspect>";
93
+ }
94
+ } else {
95
+ data.devToolsURL = nodeId;
96
+ }
97
+ } catch (e: any) {
98
+ data.apiError = "Error: " + e.stack;
99
+ }
100
+ Querysub.commit(() => {
101
+ this.state.nodes[nodeId] = atomicObjectWrite(data);
102
+ });
103
+ }));
104
+ } catch (e: any) {
105
+ Querysub.commit(() => this.state.error = e.stack);
106
+ }
107
+ })());
108
+ });
109
+ componentWillMount(): void {
110
+ logErrors((async () => {
111
+ const controller = NodeViewerController.nodes[getBrowserUrlNode()];
112
+ let verifyAccess = await errorToUndefined(controller.verifyAccess());
113
+ Querysub.commit(() => this.state.access = verifyAccess ? "yes" : "no");
114
+
115
+ this.populateNodeInfos();
116
+ })());
117
+ }
118
+ renderTable(columns: string[], objs: { [key: string]: unknown }[]) {
119
+ return (
120
+ <table class={css.borderCollapse("collapse")}>
121
+ <tr>
122
+ {columns.map(x => <th>{x}</th>)}
123
+ </tr>
124
+ {objs.map(obj => (
125
+ <tr>
126
+ {columns.map(x => <td>{obj[x] as any}</td>)}
127
+ </tr>
128
+ ))}
129
+ </table>
130
+ );
131
+ }
132
+ render() {
133
+ if (this.state.access === "no") {
134
+ return <div>Requires superadmin to access</div>;
135
+ }
136
+ if (this.state.access === "verifying") {
137
+ return <div>Verifying access...</div>;
138
+ }
139
+ let nodeDatas = Object.values(this.state.nodes);
140
+ if (!showNotReadyNodes.value) {
141
+ nodeDatas = nodeDatas.filter(x => x.live_isReadReady);
142
+ }
143
+
144
+ function mergeTables(tables: TableType[]): TableType {
145
+ let columns: ColumnsType = {};
146
+ for (let table of tables) {
147
+ for (let [columnName, column] of Object.entries(table.columns)) {
148
+ columns[columnName] = column;
149
+ }
150
+ }
151
+ let rows = tables.flatMap(x => x.rows);
152
+ for (let column in columns) {
153
+ if (rows.every(x => {
154
+ let value = x[column];
155
+ return value === undefined || value === null || value === "" || Array.isArray(value) && value.length === 0;
156
+ })) {
157
+ delete columns[column];
158
+ }
159
+ }
160
+ return { columns, rows, };
161
+ }
162
+
163
+ let tables: { [key: string]: NodeData[] } = {};
164
+ tables["PathValueServer"] = [];
165
+ tables["FunctionRunner"] = [];
166
+ tables["Querysub"] = [];
167
+ for (let datum of nodeDatas) {
168
+ if (datum.live_authorityPaths?.length) {
169
+ tables["PathValueServer"].push(datum);
170
+ } else if (datum.live_exposedControllers?.includes("QuerysubController-6db5ef05-7563-4473-a440-2f64f03fe6ef")) {
171
+ tables["Querysub"].push(datum);
172
+ } else if (datum.live_entryPoint?.endsWith("function.js")) {
173
+ tables["FunctionRunner"].push(datum);
174
+ } else {
175
+ let entryName = datum.live_entryPoint?.replaceAll("\\", "/").split("/").pop() || "Unknown";
176
+ tables[entryName] = tables[entryName] || [];
177
+ tables[entryName].push(datum);
178
+ }
179
+ }
180
+
181
+ let builtinGroups = {
182
+ "Default": ["buttons", "devToolsURL", "ip", "uptime", "Heap", "All Memory", "Blocking Lag", "port", "threadId", "machineId", "apiError"],
183
+ };
184
+ // Column => group
185
+ let builtInGroupsLookup = new Map<string, string>();
186
+ for (let [group, columns] of Object.entries(builtinGroups)) {
187
+ for (let column of columns) {
188
+ builtInGroupsLookup.set(column, group);
189
+ }
190
+ }
191
+ let groupedTables: {
192
+ [groupTitle: string]: {
193
+ [key: string]: true;
194
+ };
195
+ } = {};
196
+ for (let [group, values] of Object.entries(builtinGroups)) {
197
+ groupedTables[group] = Object.fromEntries(values.map(x => [x, true]));
198
+ }
199
+ for (let [name, tableList] of Object.entries(tables)) {
200
+ for (let table of tableList) {
201
+ let row = table.table?.row || {};
202
+ for (let column in row) {
203
+ let groupTitle = builtInGroupsLookup.get(column) || "Other";
204
+ if (column.includes("|")) {
205
+ groupTitle = column.split("|")[0];
206
+ }
207
+ groupedTables[groupTitle] = groupedTables[groupTitle] || {};
208
+ groupedTables[groupTitle][column] = true;
209
+ }
210
+ }
211
+ }
212
+
213
+ let visibleColumns = Object.keys(groupedTables[selectedGroup.value] || {});
214
+
215
+ return (
216
+ <div class={css.pad2(10).vbox(10)}>
217
+ <ButtonSelector
218
+ options={Object.keys(groupedTables).map(x => ({ value: x, title: toSpaceCase(x) }))}
219
+ onChange={x => selectedGroup.value = x}
220
+ value={selectedGroup.value}
221
+ />
222
+ <div class={css.wrap.hbox(10)}>
223
+ <Button onClick={async () => {
224
+ await NodeViewerController.nodes[getBrowserUrlNode()].forceRefreshNodes();
225
+ this.populateNodeInfos.reset();
226
+ this.populateNodeInfos();
227
+ }}>Refresh</Button>
228
+ <InputLabelURL label="Show Not Ready" checkbox url={showNotReadyNodes} />
229
+ {this.state.error && <div class={errorMessage}>{this.state.error}</div>}
230
+ </div>
231
+ <div class={css.vbox(10)}>
232
+ {Object.entries(tables).map(([name, data]) => {
233
+ let finalTable = mergeTables(data.map(x => ({
234
+ columns: {
235
+ buttons: {
236
+ formatter: () => {
237
+ return <span class={css.hbox(6)}>
238
+ <Button onClick={() => NodeViewerController.nodes[getBrowserUrlNode()].diskAuditNow(x.nodeId)}>Disk Audit</Button>
239
+ <Button onClick={() => NodeViewerController.nodes[getBrowserUrlNode()].memoryAuditNow(x.nodeId)}>Memory Audit</Button>
240
+ </span>;
241
+ }
242
+ },
243
+ devToolsURL: {
244
+ formatter: (obj) => {
245
+ let str = String(obj);
246
+ if (str.startsWith("http")) return formatValue(obj);
247
+ return (
248
+ <button onClick={async () => {
249
+ const controller = NodeViewerController.nodes[getBrowserUrlNode()];
250
+ let url = await controller.getExternalInspectURL(str);
251
+
252
+ window.open(url, "_blank");
253
+ }}>
254
+ Inspect
255
+ </button>
256
+ );
257
+ },
258
+ },
259
+ ip: {},
260
+ ...x.table?.columns,
261
+ //capabilities: null,
262
+ apiError: { formatter: "error" },
263
+ },
264
+ rows: [{
265
+ buttons: true,
266
+ ...x.table?.row,
267
+ ...x,
268
+ }],
269
+ })));
270
+ finalTable.columns = Object.fromEntries(visibleColumns.filter(x => x in finalTable.columns).map(x => [x, finalTable.columns[x]]));
271
+ for (let [title, column] of Object.entries(finalTable.columns)) {
272
+ if (!column) continue;
273
+ column = { ...column };
274
+ finalTable.columns[title] = column;
275
+ column.title = column.title || toSpaceCase(title.split("|")[1] || title);
276
+ }
277
+ return (
278
+ <div class={css.hsl(75, 50, 75).pad2(10)}>
279
+ <h2>{name} ({data.length})</h2>
280
+ <Table
281
+ {...finalTable}
282
+ />
283
+ </div>
284
+ );
285
+ })}
286
+ </div>
287
+ </div>
288
+ );
289
+ }
290
+ }
291
+
292
+ class NodeViewerControllerBase {
293
+ public async verifyAccess() {
294
+ return true;
295
+ }
296
+
297
+ public async getNodeIP(nodeId: string) {
298
+ let domain = nodeId.split(":")[0];
299
+ // Resolve the domain to an IP
300
+ let result = await dns.promises.lookup(domain);
301
+ return result.address;
302
+ }
303
+
304
+ public async getCallerIP() {
305
+ let callerIP = getNodeIdIP(SocketFunction.getCaller().nodeId);
306
+ return (await dns.promises.lookup(callerIP)).address;
307
+ }
308
+
309
+ public async getExternalInspectURL(nodeId: string) {
310
+ let callerIP = getNodeIdIP(SocketFunction.getCaller().nodeId);
311
+ let ourIP = await getExternalIP();
312
+ let { externalPort, internalPort, internalInspectURL } = await NodeCapabilitiesController.nodes[nodeId].exposeExternalDebugPortOnce(ourIP);
313
+
314
+ let baseNodeIP = getNodeIdIP(nodeId);
315
+ let baseNodeIPResolved = (await dns.promises.lookup(baseNodeIP)).address;
316
+
317
+ // Listen on a websocket, and forward to externalPort:baseNodeIP
318
+ let cert = await debugGetRawEdgeCert();
319
+
320
+ let tlsServer = new tls.Server({
321
+ key: cert.key,
322
+ cert: cert.cert,
323
+ });
324
+ tlsServer.listen(0, "0.0.0.0");
325
+ await new Promise((resolve, reject) => {
326
+ tlsServer.once("listening", resolve);
327
+ tlsServer.once("error", reject);
328
+ });
329
+
330
+ let finalPort = (tlsServer.address() as any).port;
331
+
332
+ await forwardPort({ internalPort: finalPort, externalPort: finalPort, });
333
+ await forwardPort({ internalPort: externalPort, externalPort: externalPort, });
334
+
335
+ tlsServer.on("secureConnection", (socket) => {
336
+ if (socket.remoteAddress !== callerIP) {
337
+ console.error(`Rejecting connection from ${socket.remoteAddress}, expected ${callerIP}`);
338
+ socket.end();
339
+ return;
340
+ }
341
+
342
+ let internalConnection = net.createConnection({
343
+ host: baseNodeIPResolved,
344
+ port: externalPort,
345
+ });
346
+ socket.pipe(internalConnection);
347
+ internalConnection.pipe(socket);
348
+
349
+ socket.on("close", () => {
350
+ tlsServer.close();
351
+ });
352
+ });
353
+
354
+ // Ugh... what a nightmare...
355
+ let ourDomain = ourIP.replaceAll(".", "-") + "." + getDomain();
356
+ await setRecord("A", ourDomain, ourIP);
357
+
358
+ console.log(`${ourDomain}:${finalPort} => ${baseNodeIPResolved}:${externalPort} => 127.0.0.1:${internalPort}, for ${callerIP}`);
359
+
360
+ return internalInspectURL.replace(`ws=127.0.0.1:${internalPort}`, `wss=${ourDomain}:${finalPort}`);
361
+ }
362
+
363
+ public async getInspectURL(nodeId: string) {
364
+ return await NodeCapabilitiesController.nodes[nodeId].getInspectURL();
365
+ }
366
+
367
+ public async getAllNodeIds() {
368
+ return await getAllNodeIds();
369
+ }
370
+ public async forceRefreshNodes() {
371
+ await syncNodesNow();
372
+ }
373
+
374
+ public async getExposedControllers(nodeId: string) {
375
+ return await NodeCapabilitiesController.nodes[nodeId].getExposedControllers();
376
+ }
377
+
378
+ public async getControllerNodeIdList(controller: SocketRegistered<{}>) {
379
+ return await getControllerNodeIdList(controller);
380
+ }
381
+
382
+ public async live_isReadReady(nodeId: string) {
383
+ return debug_isReadReady(nodeId);
384
+ }
385
+ public async live_getAuthorityPaths(nodeId: string) {
386
+ return debug_getAuthorityPaths(nodeId);
387
+ }
388
+ public async live_getEntryPoint(nodeId: string) {
389
+ return NodeCapabilitiesController.nodes[nodeId].getEntryPoint();
390
+ }
391
+
392
+ public async getMiscInfo(nodeId: string): Promise<{
393
+ columns: ColumnsType;
394
+ row: RowType;
395
+ }> {
396
+ let row: RowType = {};
397
+ let columns: ColumnsType = {};
398
+ async function wrapAddTableValue(columnName: string, column: ColumnType, getValue: () => Promise<unknown>) {
399
+ columns[columnName] = column;
400
+ try {
401
+ let value = await timeoutToError(10000, getValue(), () => new Error(`Timeout after 10s`));
402
+ row[columnName] = value;
403
+ } catch (e: any) {
404
+ row[columnName] = "Error: " + e.stack;
405
+ }
406
+ }
407
+ let promises = [
408
+ wrapAddTableValue("paths|paths", {}, async () => {
409
+ let live_authorityPaths = await debug_getAuthorityPaths(nodeId);
410
+ return live_authorityPaths.map(x => JSON.stringify(x)).join(" | ");
411
+ }),
412
+ wrapAddTableValue("paths|functions", {}, async () => {
413
+ let live_functionRunnerShards = await NodeCapabilitiesController.nodes[nodeId].getFunctionRunnerShards();
414
+ return live_functionRunnerShards.map(x => `${x.domainName}[${x.shardRange.startFraction}-${x.shardRange.endFraction}]${x.secondaryShardRange && `+[${x.secondaryShardRange?.startFraction || 0}-${x.secondaryShardRange?.endFraction || 0}]` || ""}`).join(" | ");
415
+ }),
416
+ wrapAddTableValue("uptime", { formatter: "timeSpan" }, async () => {
417
+ let startupTime = await NodeCapabilitiesController.nodes[nodeId].getStartupTime();
418
+ return Date.now() - startupTime;
419
+ }),
420
+ wrapAddTableValue("port", {}, async () => {
421
+ return nodeId.split(":").at(-1);
422
+ }),
423
+ //
424
+ wrapAddTableValue("threadId", {
425
+ formatter: "link:/?page=user&showingmanagement&managementpage=LogViewer&filterThreadIds=$VALUE$<Thread Errors $VALUE$>"
426
+ }, async () => {
427
+ return nodeId.split(".").at(-4);
428
+ }),
429
+ wrapAddTableValue("machineId", {
430
+ formatter: "link:/?page=user&showingmanagement&managementpage=LogViewer&filterMachineIds=$VALUE$<Machine Errors =$VALUE$>"
431
+ }, async () => {
432
+ return nodeId.split(".").at(-3);
433
+ }),
434
+ wrapAddTableValue("miscAudits", { formatter: "varray:error" }, async () => {
435
+ let live_authorityPaths = await debug_getAuthorityPaths(nodeId);
436
+ let cached_authority = await nodePathAuthority.debug_getAuthority(nodeId);
437
+ let live_isReadReady = await debug_isReadReady(nodeId);
438
+ return [
439
+ live_isReadReady && !cached_authority && `Authority is missing in cache`,
440
+ JSON.stringify(live_authorityPaths) !== JSON.stringify(cached_authority?.authorityPaths) && `Paths are out of sync. Cache has ${JSON.stringify(cached_authority?.authorityPaths)} live has ${JSON.stringify(live_authorityPaths)}`,
441
+ live_isReadReady !== cached_authority?.isReadReady && `isReadReady is out of sync. Cache has ${cached_authority?.isReadReady} live has ${live_isReadReady}`,
442
+ ].filter(x => x);
443
+ }),
444
+ wrapAddTableValue("capabilities|capabilities", {}, async () => {
445
+ let live_exposedControllers = await NodeCapabilitiesController.nodes[nodeId].getExposedControllers();
446
+ return live_exposedControllers.map(x => x.split("-")[0]);
447
+ }),
448
+ ];
449
+ await Promise.allSettled(promises);
450
+ try {
451
+ let extraMetadatas = await timeoutToError(10000, NodeMetadataController.nodes[nodeId].getExtraMetadatas(), () => new Error(`Timeout after 10s`));
452
+ for (let { columnName, column, value } of extraMetadatas) {
453
+ columns[columnName] = column;
454
+ row[columnName] = value;
455
+ }
456
+ } catch (e: any) {
457
+ // Any errors almost certainly mean they aren't exposing any extra metadata, which is fine.
458
+ // columns["extraMetadatasError"] = { formatter: "error" };
459
+ // row["extraMetadatasError"] = "Error: " + e.stack;
460
+ }
461
+ return {
462
+ columns,
463
+ row,
464
+ };
465
+ }
466
+
467
+ public async diskAuditNow(nodeId: string) {
468
+ await ValueAuditController.nodes[nodeId].diskAuditNow();
469
+ }
470
+ public async memoryAuditNow(nodeId: string) {
471
+ await ValueAuditController.nodes[nodeId].memoryAuditNow();
472
+ }
473
+ }
474
+
475
+ export const NodeViewerController = SocketFunction.register(
476
+ "NodeViewerController-82b982f4-279e-4850-997e-ece8ac7604ee",
477
+ new NodeViewerControllerBase(),
478
+ () => ({
479
+ getNodeIP: {},
480
+ getInspectURL: {},
481
+ getCallerIP: {},
482
+ getExternalInspectURL: {},
483
+ verifyAccess: {},
484
+ getAllNodeIds: {},
485
+ getExposedControllers: {},
486
+ getControllerNodeIdList: {},
487
+ live_isReadReady: {},
488
+ live_getAuthorityPaths: {},
489
+ live_getEntryPoint: {},
490
+ getMiscInfo: {},
491
+
492
+ diskAuditNow: {},
493
+ memoryAuditNow: {},
494
+ forceRefreshNodes: {},
495
+ }),
496
+ () => ({
497
+ hooks: [assertIsManagementUser],
498
+ }),
499
+ {
500
+ // Auto expose, for getMiscInfo. This is protected via assertIsManagementUser anyways.
501
+ //noAutoExpose: true,
502
+ }
503
+ );
@@ -0,0 +1,62 @@
1
+ import diskusage from "diskusage";
2
+ import { readDirRecursive } from "../fs";
3
+ import fs from "fs";
4
+ import { sort } from "socket-function/src/misc";
5
+
6
+ export type SizeSpec = {
7
+ time: number;
8
+ bytes: number;
9
+ };
10
+ export class SizeLimiter {
11
+ constructor(private config: {
12
+ diskRoot: string;
13
+ maxBytes: number;
14
+ minBytes: number;
15
+ // Ex, 5%
16
+ maxDiskFraction: number;
17
+ // Max the disk can be full. We still respect minBytes, this just makes us be more respectful of space
18
+ // usage if the disk is about to run out of space.
19
+ // Ex, 95%
20
+ maxTotalDiskFraction: number;
21
+ maxFiles: number;
22
+ }) { }
23
+
24
+ public async limit<T extends SizeSpec>(files: T[]): Promise<{
25
+ keep: T[];
26
+ remove: T[];
27
+ availableBytes: number;
28
+ availableFiles: number;
29
+ }> {
30
+ const { diskRoot, maxBytes, minBytes, maxDiskFraction, maxTotalDiskFraction, maxFiles } = this.config;
31
+
32
+ let minCandidates: number[] = [];
33
+ let usage = await diskusage.check(diskRoot);
34
+
35
+ minCandidates.push(usage.free - usage.total * (1 - maxTotalDiskFraction));
36
+ minCandidates.push(usage.total * maxDiskFraction);
37
+ minCandidates.push(maxBytes);
38
+
39
+ let remainingSpace = Math.max(minBytes, Math.min(...minCandidates));
40
+
41
+ files = files.slice();
42
+ sort(files, x => -x.time);
43
+
44
+ let keepIndex = 0;
45
+ let usedBytes = 0;
46
+ for (let i = 0; i < files.length && i < maxFiles; i++) {
47
+ usedBytes += files[i].bytes;
48
+ if (usedBytes > remainingSpace) {
49
+ break;
50
+ }
51
+ keepIndex = i;
52
+ }
53
+ return {
54
+ keep: files.slice(0, keepIndex + 1),
55
+ remove: files.slice(keepIndex + 1),
56
+ availableBytes: remainingSpace - usedBytes,
57
+ availableFiles: maxFiles - keepIndex - 1,
58
+ };
59
+ }
60
+ }
61
+
62
+
@@ -0,0 +1,18 @@
1
+ import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
2
+ import { Querysub } from "../../src/4-querysub/QuerysubController";
3
+ import { formatTime } from "socket-function/src/formatting/format";
4
+ import { getTrueTimeOffset } from "socket-function/time/trueTimeShim";
5
+ import { css } from "typesafecss";
6
+
7
+ export class TimeDebug extends qreact.Component {
8
+ render() {
9
+ Querysub.timeDelayed(100);
10
+ return (
11
+ <div class={css.pad2(10).vbox(10)}>
12
+ <div>System Clock Offset: {formatTime(getTrueTimeOffset())}</div>
13
+ <div>Current Time: {Date.now()}</div>
14
+ <div>{new Date(Date.now()).toLocaleString()}</div>
15
+ </div>
16
+ );
17
+ }
18
+ }