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,572 @@
1
+ import { css, isNode } from "typesafecss";
2
+
3
+ import { SocketFunction } from "socket-function/SocketFunction";
4
+ import { getSourceVSCodeLink, qreact } from "../../4-dom/qreact";
5
+ import { assertIsManagementUser } from "../managementPages";
6
+ import { LogFile, LogObj, getLogBuffer, getLogFiles, parseLogBuffer } from "./diskLogger";
7
+ import { t } from "../../4-querysub/Querysub";
8
+ import { URLParam } from "../../library-components/URLParam";
9
+ import { getSyncedController } from "../../library-components/SyncedController";
10
+ import { getBrowserUrlNode } from "../../-f-node-discovery/NodeDiscovery";
11
+ import { formatDateTime, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
12
+ import { Anchor } from "../../library-components/ATag";
13
+ import { cache, cacheLimited, cacheShallowConfigArgEqual } from "socket-function/src/caching";
14
+ import { InputLabel, InputLabelURL } from "../../library-components/InputLabel";
15
+ import { DropdownSelector } from "../../library-components/DropdownSelector";
16
+ import { Table, TableType } from "../../5-diagnostics/Table";
17
+ import { LogType } from "../errorLogs/ErrorLogCore";
18
+ import { canHaveChildren } from "socket-function/src/types";
19
+ import { measureBlock } from "socket-function/src/profiling/measure";
20
+ import { binarySearchBasic, list, sort, timeInDay, timeInHour } from "socket-function/src/misc";
21
+ import { NodeViewerController } from "../NodeViewer";
22
+ import { red } from "socket-function/src/formatting/logColors";
23
+ import { errorMessage, formatValue, genericFormat, warnMessage } from "../../5-diagnostics/GenericFormat";
24
+ import { hslToRGB } from "socket-function/src/formatting/colors";
25
+ import { InputPicker } from "../../library-components/InputPicker";
26
+ import { ObjectDisplay } from "./ObjectDisplay";
27
+ import { parseAnsiColors, rgbToHsl } from "./ansiFormat";
28
+ import { ShowMore } from "../../library-components/ShowMore";
29
+ import { getNodeIdLocation } from "socket-function/src/nodeCache";
30
+ import { decodeNodeId, encodeNodeId, getMachineId } from "../../-a-auth/certs";
31
+ import { Button } from "../../library-components/Button";
32
+ import { TimeRangeSelector } from "../../library-components/TimeRangeSelector";
33
+
34
+ // TODO: Realtime log mode, by reading from the previous length forward, to add buffers
35
+ // to what we already read.
36
+ // - ALSO, correctly handle cut off messages? Or not, if the writes are atomic
37
+ // it might be fine...
38
+
39
+ // TODO: Allow showing context for logs, showing filter logs PLUS +/- a certain number of lines
40
+ // - Also grouping them, so we can see which are part of which.
41
+ // - Maybe only showing +/- within a time range as well, and maybe only the same file
42
+
43
+ // TODO: Parse objects in strings and display them nicely
44
+ // - I don't know if we even want to add filter support, but... making them more readable
45
+ // should be easy enough, and might help with reading objects in error messages?
46
+
47
+ // TODO: Track line locations from diskLog
48
+ // - Probably by injecting ids? Although how would they be persistent? Hmm... Maybe line numbers
49
+ // is fine? I think we map the typescript (I hope we do), so they should be correct?
50
+
51
+ module.hotreload = true;
52
+ module.hotreload = true;
53
+
54
+ let selectedNodeId = new URLParam("nodeId", "");
55
+
56
+ let filterURL = new URLParam("filter", "");
57
+ let sortOldestFirst = new URLParam("oldestFirst", false);
58
+ let selectedFields = new URLParam("selectedFields", "");
59
+
60
+ let startTimeURL = new URLParam("startTime", undefined as number | undefined);
61
+ let endTimeURL = new URLParam("endTime", undefined as number | undefined);
62
+
63
+ let inspectTimeURL = new URLParam("inspectTime", "");
64
+
65
+ export class DiskLoggerPage extends qreact.Component {
66
+ state = t.state({
67
+ x: t.number
68
+ });
69
+ render() {
70
+ let selectedNodesIds = selectedNodeId.value.split("|").filter(x => x);
71
+ const nodeController = nodeViewerController(getBrowserUrlNode());
72
+ let nodeIds = nodeController.getControllerNodeIdList(DiskLoggerController) || [];
73
+
74
+ if (selectedNodesIds.length === 0) {
75
+ // Group by machineId
76
+ let grouped = new Map<string, string[]>();
77
+ for (let nodeId of nodeIds) {
78
+ let obj = decodeNodeId(nodeId);
79
+ if (!obj) continue;
80
+ let { machineId } = obj;
81
+ let list = grouped.get(machineId);
82
+ if (!list) {
83
+ grouped.set(machineId, list = []);
84
+ }
85
+ list.push(nodeId);
86
+ }
87
+ return (
88
+ <div class={css.pad2(10).vbox(10)}>
89
+ <h1>Node Ids ({nodeIds.length})</h1>
90
+ <div class={css.hbox(40).wrap}>
91
+ {Array.from(grouped.entries()).map(([machineId, list]) =>
92
+
93
+ <div class={css.vbox(10).wrap.maxWidth("30vw").pad2(5).hsla(0, 0, 0, 0.1)}>
94
+ <div class={css.fontSize(30)}>{getMachineId(machineId)}</div>
95
+ <div class={css.hbox(20, 5).wrap}>
96
+ {list.map(nodeId =>
97
+ <Anchor
98
+ class={css.hbox(10)}
99
+ values={[selectedNodeId.getOverride(nodeId)]}
100
+ >
101
+ {nodeId}
102
+ </Anchor>
103
+ )}
104
+ </div>
105
+ </div>
106
+ )}
107
+ </div>
108
+ </div>
109
+ );
110
+ }
111
+
112
+ let startTime = Number(startTimeURL.value) || (Date.now() - timeInDay);
113
+ let endTime = Number(endTimeURL.value) || (Date.now() + timeInDay);
114
+ const controller = diskLoggerController(getBrowserUrlNode());
115
+ let files: (LogFile & { nodeId: string })[] = [];
116
+ let loadingCount = 0;
117
+
118
+ for (let nodeId of selectedNodesIds) {
119
+ try {
120
+ let newFiles = controller.getRemoteLogFiles(nodeId);
121
+ if (newFiles) {
122
+ files.push(...newFiles.map(x => ({ ...x, nodeId })));
123
+ } else {
124
+ loadingCount++;
125
+ }
126
+ } catch (e) {
127
+ console.log("Error reading files", nodeId, e);
128
+ }
129
+ }
130
+ let allFiles = files;
131
+ files = files.filter(file => file.startTime <= endTime && file.endTime >= startTime);
132
+ let buffers: Buffer[] = [];
133
+
134
+ let firstTime = Number.MAX_SAFE_INTEGER;
135
+ let lastTime = Number.MIN_SAFE_INTEGER;
136
+
137
+ for (let file of files) {
138
+ try {
139
+ if (file.startTime < firstTime) firstTime = file.startTime;
140
+ if (file.endTime > lastTime) lastTime = file.endTime;
141
+ let buffer = controller.getRemoteLogBuffer(file.nodeId, file.path);
142
+ if (buffer) {
143
+ buffers.push(buffer);
144
+ } else {
145
+ loadingCount++;
146
+ }
147
+ } catch (e) {
148
+ console.log("Error reading buffer", file, e);
149
+ }
150
+ }
151
+ if (firstTime === Number.MAX_SAFE_INTEGER) firstTime = startTime;
152
+ if (lastTime === Number.MIN_SAFE_INTEGER) lastTime = endTime;
153
+
154
+ let logs: LogObj[] = loadingCount ? [] : parseLogBufferCached(JSON.stringify(files), buffers);
155
+
156
+ const defaultFields = ["time", "type", "actions", "param0"];
157
+ let selectedValues = selectedFields.value.split("|").filter(x => x);
158
+ let fullSelectedFields = defaultFields.concat(selectedValues);
159
+
160
+ let { table, allFields, filteredCount, totalCount } = processLogs({
161
+ logs,
162
+ startTime, endTime,
163
+ order: sortOldestFirst.value ? "oldestFirst" : "newestFirst",
164
+ filter: filterURL.value,
165
+ selectedFields: fullSelectedFields,
166
+ inspectTime: inspectTimeURL.value,
167
+ });
168
+
169
+ function addPath(path: string[]) {
170
+ let newSelected = Array.from(new Set([...selectedValues, path.join(".")])).join("|");
171
+ selectedFields.value = newSelected;
172
+ }
173
+
174
+
175
+ for (let column of Object.values(table.columns)) {
176
+ if (!column) continue;
177
+ if (!column.formatter) {
178
+ column.formatter = (value: unknown) => <ObjectDisplay
179
+ value={value}
180
+ excludedFields={fullSelectedFields}
181
+ onClickValue={(path, value) => {
182
+ if (!canHaveChildren(value)) {
183
+ // Add to search as well
184
+ if (filterURL.value.trim()) {
185
+ filterURL.value += " | ";
186
+ }
187
+ filterURL.value += String(value);
188
+ }
189
+ }}
190
+ />;
191
+ }
192
+ }
193
+
194
+ table.columns["actions"] = {
195
+ formatter(value, context) {
196
+ let file = (context as any)?.row.__FILE__;
197
+ if (!file) return undefined;
198
+ let line = (context as any)?.row.__LINE__;
199
+ return (
200
+ <div>
201
+ <button onClick={() => {
202
+ let path = `vscode://file/${file}`;
203
+ if (typeof line === "number") {
204
+ path += `:${line + 1}`;
205
+ }
206
+ window.open(path);
207
+ }}>Open In VS Code</button>
208
+ </div>
209
+ );
210
+ },
211
+ };
212
+ table.columns["time"] = {
213
+ formatter(value, context) {
214
+ return (
215
+ <div
216
+ className={css.button + (css.background("hsla(0, 0%, 100%, 0.2)", "hover"))}
217
+ onClick={() => {
218
+ inspectTimeURL.value = String(value) + "|" + 10 + "|" + 10;
219
+ filterURL.value = "";
220
+ }}
221
+ >
222
+ {genericFormat(value)}
223
+ </div>
224
+ );
225
+ },
226
+ };
227
+
228
+ table.columns["type"] = {
229
+ formatter(value, context) {
230
+ let inner = (() => {
231
+ if (value === "error") return <span className={errorMessage}>{value}</span>;
232
+ if (value === "warn") return <span className={warnMessage}>{value}</span>;
233
+ if (value === "info" || value === "log") return <span className={css.hsl(210, 50, 50).pad2(6, 4).color("white")}>{value}</span>;
234
+ return formatValue(value, undefined, context);
235
+ })();
236
+ return <div><div className={css.width(100).hbox0.center}>{inner}</div></div>;
237
+ }
238
+ };
239
+ table.columns["param0"] = {
240
+ title: "Message",
241
+ formatter(value, context) {
242
+ return <ObjectDisplay value={value} />;
243
+ },
244
+ };
245
+
246
+ table.columns["remaining"] = {
247
+ formatter(value, context) {
248
+ return <ObjectDisplay
249
+ value={context?.row}
250
+ excludedFields={fullSelectedFields}
251
+ onClickKey={path => addPath(path)}
252
+ onClickValue={(path, value) => {
253
+ addPath(path);
254
+ if (!canHaveChildren(value)) {
255
+ // Add to search as well
256
+ if (filterURL.value.trim()) {
257
+ filterURL.value += " | ";
258
+ }
259
+ filterURL.value += String(value);
260
+ }
261
+ }}
262
+ />;
263
+ },
264
+ };
265
+
266
+ let [inspectTime, inspectBeforeCount, inspectAfterCount] = inspectTimeURL.value.split("|").map(x => Number(x));
267
+ if (inspectTime) {
268
+ startTime = inspectTime;
269
+ endTime = inspectTime;
270
+ }
271
+
272
+ return (
273
+ <div class={css.pad2(10).vbox(10)}>
274
+ <Anchor values={[selectedNodeId.getOverride("")]}><h2>Back</h2></Anchor>
275
+ <InputPicker
276
+ picked={selectedNodesIds}
277
+ addPicked={x => selectedNodeId.value = [...selectedNodesIds, x].join("|")}
278
+ removePicked={x => selectedNodeId.value = selectedNodesIds.filter(y => y !== x).join("|")}
279
+ options={nodeIds.map(x => ({ value: x, label: x }))}
280
+ />
281
+ {loadingCount > 0 && <h1>Loading {loadingCount} files</h1>}
282
+ <div className={css.fontSize(28).boldStyle}>
283
+ {selectedNodesIds.length} nodes, {files.length} files, filtered logs {formatNumber(filteredCount)} / {formatNumber(totalCount)}
284
+ </div>
285
+ <ShowMore maxHeight={40}>
286
+ <div class={css.vbox(4)}>
287
+ {files.map(file =>
288
+ <div class={css.hbox(4)}>
289
+ <span>{formatNumber(file.size)}B</span>
290
+ <span>{formatDateTime(file.startTime)} - {formatDateTime(file.endTime)}</span>
291
+ <span>({file.path})</span>
292
+ </div>
293
+ )}
294
+ </div>
295
+ </ShowMore>
296
+ {!inspectTime && <>
297
+ <TimeRangeSelector
298
+ start={startTimeURL} end={endTimeURL}
299
+ defaultStart={startTime} defaultEnd={endTime}
300
+ firstTime={firstTime} lastTime={lastTime}
301
+ />
302
+ <div class={css.hbox(20)}>
303
+ <Button onClick={() => { startTimeURL.reset(); endTimeURL.reset(); }}>Reset Time Filter</Button>
304
+ <div class={css.hbox(10)}>
305
+ <InputLabelURL label="Start Time" url={startTimeURL} isDatetime valueDefault={startTime} />
306
+ <span>({formatNiceDateTime(startTime)})</span>
307
+ </div>
308
+ <div class={css.hbox(10)}>
309
+ <InputLabelURL label="End Time" url={endTimeURL} isDatetime valueDefault={endTime} />
310
+ <span>({formatNiceDateTime(endTime)})</span>
311
+ </div>
312
+ </div>
313
+ <div className={css.hbox(20)}>
314
+ <h2>{formatTime(Date.now() - startTime)} AGO</h2> to <h2>{formatTime(Date.now() - endTime)} AGO</h2>
315
+ </div>
316
+ </> ||
317
+ <div class={css.hbox(20)}>
318
+ <h2>{formatTime(Date.now() - inspectTime)} AGO</h2>
319
+ <h2>{formatNiceDateTime(inspectTime)}</h2>
320
+ <InputLabel
321
+ label="Inspect Time"
322
+ value={inspectTime}
323
+ isDatetime
324
+ onChange={x => inspectTimeURL.value = x + "|" + inspectBeforeCount + "|" + inspectAfterCount}
325
+ />
326
+ <button onClick={() => {
327
+ inspectTimeURL.value = "";
328
+ }}>
329
+ Reset Inspect
330
+ </button>
331
+ </div>
332
+ }
333
+
334
+ <InputLabelURL fillWidth label="Filter: ['|' to match any conditions, '&' to match all, '!' for negation]" url={filterURL} />
335
+ <InputLabelURL label="Sort Oldest First" checkbox url={sortOldestFirst} />
336
+ <InputPicker
337
+ label="Selected Fields"
338
+ picked={selectedValues}
339
+ options={Array.from(allFields.keys()).map(x => ({ value: x, label: x }))}
340
+ addPicked={x => selectedFields.value = [...selectedValues, x].join("|")}
341
+ removePicked={x => selectedFields.value = selectedValues.filter(y => y !== x).join("|")}
342
+ />
343
+ {!!inspectTime && (
344
+ <InputLabel
345
+ label="Before Count"
346
+ value={inspectBeforeCount}
347
+ onChangeValue={x => inspectTimeURL.value = inspectTime + "|" + x + "|" + inspectAfterCount}
348
+ number
349
+ />
350
+ )}
351
+ <Table {...table} initialLimit={30} />
352
+ {!!inspectTime && (
353
+ <InputLabel
354
+ label="After Count"
355
+ value={inspectAfterCount}
356
+ onChangeValue={x => inspectTimeURL.value = inspectTime + "|" + inspectBeforeCount + "|" + x}
357
+ number
358
+ />
359
+ )}
360
+ </div>
361
+ );
362
+ }
363
+ }
364
+
365
+
366
+ let remainingCacheSize = 1024 * 1024 * 1024 * 2;
367
+ let logBufferCache = new Map<string, {
368
+ size: number;
369
+ logs: LogObj[];
370
+ lastAccess: number;
371
+ }>();
372
+ function parseLogBufferCached(hash: string, buffers: Buffer[]): LogObj[] {
373
+ let cached = logBufferCache.get(hash);
374
+ if (cached) {
375
+ cached.lastAccess = Date.now();
376
+ return cached.logs;
377
+ }
378
+ let logs = buffers.map(x => parseLogBuffer(x)).flat();
379
+ let size = buffers.map(x => x.length).reduce((a, b) => a + b, 0);
380
+ remainingCacheSize -= size;
381
+ while (remainingCacheSize < 0 && logBufferCache.size > 0) {
382
+ let oldest = [...logBufferCache.entries()].sort((a, b) => a[1].lastAccess - b[1].lastAccess)[0];
383
+ logBufferCache.delete(oldest[0]);
384
+ remainingCacheSize += oldest[1].size;
385
+ }
386
+ logBufferCache.set(hash, {
387
+ size,
388
+ logs,
389
+ lastAccess: Date.now()
390
+ });
391
+ return logs;
392
+ }
393
+
394
+
395
+
396
+ const processLogs = cacheShallowConfigArgEqual((config: {
397
+ logs: LogObj[];
398
+ startTime: number; endTime: number;
399
+ order: "oldestFirst" | "newestFirst";
400
+ filter: string;
401
+ selectedFields: string[];
402
+ inspectTime: string;
403
+ }): {
404
+ table: TableType<LogObj>;
405
+ allFields: Map<string, number>;
406
+ totalCount: number;
407
+ filteredCount: number;
408
+ } => {
409
+ return measureBlock(function processLogs() {
410
+ let { logs, order, filter, startTime, endTime, inspectTime, selectedFields } = config;
411
+ logs = logs.slice();
412
+ let totalCount = logs.length;
413
+
414
+ let logMatchesFilter: (log: LogObj) => boolean = () => true;
415
+
416
+ if (filter) {
417
+ filter = filter.toLowerCase();
418
+ let filterParts = filter.split("|").map(x => x.trim()).map(x => x.split("&").map(x => x.trim()));
419
+ // Extract any negation
420
+ let negationQueries: string[] = [];
421
+ filterParts = filterParts.map(filter => filter.filter(x => {
422
+ if (x.startsWith("!")) {
423
+ negationQueries.push(x.slice(1));
424
+ return false;
425
+ }
426
+ return true;
427
+ })).filter(x => x.length > 0);
428
+ function logMatches(filter: string, log: LogObj) {
429
+ for (let [key, value] of Object.entries(log)) {
430
+ if (key.toLowerCase().includes(filter)) return true;
431
+ if (canHaveChildren(value) && JSON.stringify(value).toLowerCase().includes(filter)) return true;
432
+ if (String(value).toLowerCase().includes(filter)) return true;
433
+ }
434
+ return false;
435
+ }
436
+ logMatchesFilter = function logMatchesFilter(log: LogObj) {
437
+ return (
438
+ (filterParts.length === 0 || filterParts.some(filter => filter.every(filter => logMatches(filter, log))))
439
+ && !negationQueries.some(filter => logMatches(filter, log))
440
+ );
441
+ };
442
+ }
443
+
444
+ function applySort() {
445
+ measureBlock(function filterSort() {
446
+ if (order === "oldestFirst") {
447
+ sort(logs, x => x.time);
448
+ } else {
449
+ sort(logs, x => -x.time);
450
+ }
451
+ });
452
+ }
453
+
454
+
455
+ if (inspectTime) {
456
+ applySort();
457
+ // time|before|after
458
+ let [time, before, after] = inspectTime.split("|").map(x => Number(x));
459
+ applySort();
460
+ measureBlock(function filterLogs() {
461
+ // Binary search to find the closest index
462
+ let sortOrder = order === "oldestFirst" ? 1 : -1;
463
+ let index = binarySearchBasic(logs, x => x.time * sortOrder, time * sortOrder);
464
+ let startIndex: number;
465
+ let endIndex: number;
466
+
467
+ let newLogs: LogObj[] = [];
468
+ if (index < 0) {
469
+ index = ~index;
470
+ startIndex = index - 1;
471
+ endIndex = index;
472
+ } else {
473
+ startIndex = index - 1;
474
+ endIndex = index + 1;
475
+ if (logMatchesFilter(logs[index])) {
476
+ newLogs.push(logs[index]);
477
+ }
478
+ }
479
+
480
+ while (before > 0 && startIndex > 0) {
481
+ startIndex--;
482
+ let logBefore = logs[startIndex];
483
+ if (logMatchesFilter(logBefore)) {
484
+ newLogs.unshift(logBefore);
485
+ before--;
486
+ }
487
+ }
488
+
489
+ while (after > 0 && endIndex < logs.length) {
490
+ let logAfter = logs[endIndex];
491
+ if (logMatchesFilter(logAfter)) {
492
+ newLogs.push(logAfter);
493
+ after--;
494
+ }
495
+ endIndex++;
496
+ }
497
+
498
+ logs = newLogs;
499
+ });
500
+ } else {
501
+ measureBlock(function filterLogs() {
502
+ logs = logs.filter(log =>
503
+ startTime <= log.time && log.time <= endTime &&
504
+ logMatchesFilter(log)
505
+ );
506
+ });
507
+ applySort();
508
+ }
509
+
510
+ let filteredCount = logs.length;
511
+
512
+ let fields = new Map<string, number>();
513
+ for (let log of logs) {
514
+ for (let key in log) {
515
+ fields.set(key, (fields.get(key) || 0) + 1);
516
+ }
517
+ }
518
+
519
+ let table: TableType<LogObj> = {
520
+ rows: logs,
521
+ columns: {},
522
+ };
523
+
524
+ for (let key of selectedFields) {
525
+ table.columns[key] = {};
526
+ }
527
+
528
+ return { table, allFields: fields, totalCount, filteredCount };
529
+ });
530
+ }, 10);
531
+
532
+
533
+
534
+
535
+ class DiskLoggerControllerBase {
536
+ // Have to forward it, as backend nodes don't have real domains, so HTTPS won't
537
+ // work from the browser for them.
538
+ public async getRemoteLogFiles(nodeId: string): Promise<LogFile[]> {
539
+ return await DiskLoggerController.nodes[nodeId].getLogFiles();
540
+ }
541
+ public async getRemoteLogBuffer(nodeId: string, path: string): Promise<Buffer | undefined> {
542
+ return await DiskLoggerController.nodes[nodeId].getLogBuffer(path);
543
+ }
544
+
545
+ public async getLogFiles(): Promise<LogFile[]> {
546
+ return await getLogFiles();
547
+ }
548
+ public async getLogBuffer(path: string): Promise<Buffer | undefined> {
549
+ return await getLogBuffer(path);
550
+ }
551
+ }
552
+
553
+ export const DiskLoggerController = SocketFunction.register(
554
+ "DiskLoggerController-f76a6fdf-3bd5-4bd4-a183-55a8be0a5a32",
555
+ new DiskLoggerControllerBase(),
556
+ () => ({
557
+ getRemoteLogFiles: {},
558
+ getRemoteLogBuffer: {},
559
+
560
+ getLogFiles: {},
561
+ getLogBuffer: {},
562
+ }),
563
+ () => ({
564
+ hooks: [assertIsManagementUser],
565
+ }),
566
+ {
567
+ }
568
+ );
569
+
570
+ let diskLoggerController = getSyncedController(DiskLoggerController);
571
+
572
+ let nodeViewerController = getSyncedController(NodeViewerController);