querysub 0.312.0 → 0.314.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 (70) hide show
  1. package/.cursorrules +1 -1
  2. package/costsBenefits.txt +4 -1
  3. package/package.json +3 -2
  4. package/spec.txt +23 -18
  5. package/src/-0-hooks/hooks.ts +1 -1
  6. package/src/-a-archives/archives.ts +16 -3
  7. package/src/-a-archives/archivesBackBlaze.ts +51 -3
  8. package/src/-a-archives/archivesLimitedCache.ts +175 -0
  9. package/src/-a-archives/archivesPrivateFileSystem.ts +299 -0
  10. package/src/-a-auth/certs.ts +58 -31
  11. package/src/-b-authorities/cdnAuthority.ts +2 -2
  12. package/src/-b-authorities/dnsAuthority.ts +3 -2
  13. package/src/-c-identity/IdentityController.ts +3 -2
  14. package/src/-d-trust/NetworkTrust2.ts +17 -19
  15. package/src/-e-certs/EdgeCertController.ts +19 -81
  16. package/src/-e-certs/certAuthority.ts +7 -2
  17. package/src/-f-node-discovery/NodeDiscovery.ts +9 -7
  18. package/src/-g-core-values/NodeCapabilities.ts +6 -1
  19. package/src/0-path-value-core/NodePathAuthorities.ts +1 -1
  20. package/src/0-path-value-core/PathValueCommitter.ts +3 -3
  21. package/src/0-path-value-core/PathValueController.ts +3 -3
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +15 -37
  23. package/src/0-path-value-core/pathValueCore.ts +4 -3
  24. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  25. package/src/4-dom/qreact.tsx +4 -3
  26. package/src/4-querysub/Querysub.ts +2 -2
  27. package/src/4-querysub/QuerysubController.ts +2 -2
  28. package/src/5-diagnostics/GenericFormat.tsx +1 -0
  29. package/src/5-diagnostics/Table.tsx +3 -0
  30. package/src/5-diagnostics/diskValueAudit.ts +2 -1
  31. package/src/5-diagnostics/nodeMetadata.ts +0 -1
  32. package/src/deployManager/components/MachineDetailPage.tsx +9 -1
  33. package/src/deployManager/components/ServiceDetailPage.tsx +10 -1
  34. package/src/deployManager/setupMachineMain.ts +8 -1
  35. package/src/diagnostics/NodeViewer.tsx +5 -6
  36. package/src/diagnostics/logs/FastArchiveAppendable.ts +757 -0
  37. package/src/diagnostics/logs/FastArchiveController.ts +524 -0
  38. package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
  39. package/src/diagnostics/logs/LogViewer2.tsx +349 -0
  40. package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
  41. package/src/diagnostics/logs/diskLogger.ts +135 -305
  42. package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
  43. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
  44. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
  45. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
  46. package/src/diagnostics/logs/importLogsEntry.ts +38 -0
  47. package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
  48. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
  49. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +153 -0
  50. package/src/diagnostics/managementPages.tsx +7 -16
  51. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
  52. package/src/diagnostics/periodic.ts +5 -0
  53. package/src/diagnostics/watchdog.ts +2 -2
  54. package/src/functional/SocketChannel.ts +67 -0
  55. package/src/library-components/Input.tsx +1 -1
  56. package/src/library-components/InputLabel.tsx +5 -2
  57. package/src/misc.ts +111 -0
  58. package/src/src.d.ts +34 -1
  59. package/src/user-implementation/userData.ts +4 -3
  60. package/test.ts +13 -0
  61. package/testEntry2.ts +29 -0
  62. package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
  63. package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
  64. package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
  65. package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
  66. package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
  67. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
  68. package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
  69. package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
  70. package/src/diagnostics/logs/DiskLoggerPage.tsx +0 -613
@@ -0,0 +1,349 @@
1
+ module.hotreload = true;
2
+ import { qreact } from "../../4-dom/qreact";
3
+ import { css } from "../../4-dom/css";
4
+ import { URLParam } from "../../library-components/URLParam";
5
+ import { InputLabelURL } from "../../library-components/InputLabel";
6
+ import { Button } from "../../library-components/Button";
7
+ import { formatDateTime, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
8
+ import { TimeRangeSelector, endTimeParam, getTimeRange, startTimeParam } from "./TimeRangeSelector";
9
+ import { FastArchiveAppendable } from "./FastArchiveAppendable";
10
+ import { t } from "../../2-proxy/schema2";
11
+ import { measureFnc } from "socket-function/src/profiling/measure";
12
+ import { logErrors } from "../../errors";
13
+ import { batchFunction, runInSerial } from "socket-function/src/batching";
14
+ import { Querysub } from "../../4-querysub/QuerysubController";
15
+ import { sort, timeInDay, timeInHour } from "socket-function/src/misc";
16
+ import { FastArchiveViewer } from "./FastArchiveViewer";
17
+ import { LogDatum, getLoggers, LOG_LIMIT_FLAG } from "./diskLogger";
18
+ import { ColumnType, Table, TableType } from "../../5-diagnostics/Table";
19
+ import { formatDateJSX } from "../../misc/formatJSX";
20
+ import { InputPicker } from "../../library-components/InputPicker";
21
+ import { JSXFormatter } from "../../5-diagnostics/GenericFormat";
22
+ import { atomic } from "../../2-proxy/PathValueProxyWatcher";
23
+ import { ObjectDisplay } from "./ObjectDisplay";
24
+ import { endTime } from "../misc-pages/archiveViewerShared";
25
+ import { ErrorSuppressionUI } from "./errorNotifications/ErrorSuppressionUI";
26
+ import { FileMetadata } from "./FastArchiveController";
27
+ import { SuppressionListController, getSuppressEntryChecker } from "./errorNotifications/ErrorNotificationController";
28
+ import { SocketFunction } from "socket-function/SocketFunction";
29
+
30
+ const RENDER_INTERVAL = 1000;
31
+
32
+ export const testLogs = new FastArchiveAppendable("test/");
33
+
34
+ const enableLogsURL = new URLParam("enableLogs", false);
35
+ const enableInfosURL = new URLParam("enableInfos", true);
36
+ const enableWarningsURL = new URLParam("enableWarnings", true);
37
+ const enableErrorsURL = new URLParam("enableErrors", true);
38
+ const selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
39
+
40
+ export const errorNotifyToggleURL = new URLParam("errorNotifyToggle", false);
41
+
42
+ const defaultSelectedFields = {
43
+ param0: true,
44
+ time: true,
45
+ __NAME__: true,
46
+
47
+ //__machineId: true,
48
+ __threadId: true,
49
+ __entry: true,
50
+ //__DIR__: true,
51
+ };
52
+
53
+ export class LogViewer2 extends qreact.Component {
54
+ state = t.state({
55
+ datumsSeqNum: t.atomic<number>(0),
56
+ });
57
+
58
+ private example: string | undefined = undefined;
59
+ private datumCount = 0;
60
+ private matchedSize = 0;
61
+ private notMatchedSize = 0;
62
+ private errors = 0;
63
+ private notMatchedCount = 0;
64
+ private datums: LogDatum[] = [];
65
+
66
+ private lastRenderTime = 0;
67
+ private suppressionCounts = new Map<string, number>();
68
+ private expiredSuppressionCounts = new Map<string, number>();
69
+ private fastArchiveViewer: FastArchiveViewer<LogDatum> | undefined = undefined;
70
+
71
+ rerun() {
72
+ void this.fastArchiveViewer?.handleDownload();
73
+ }
74
+
75
+ render() {
76
+
77
+ this.state.datumsSeqNum;
78
+
79
+ let logs: FastArchiveAppendable<LogDatum>[] = [];
80
+ let loggers = getLoggers();
81
+ if (!loggers) {
82
+ return `Loggers not available?`;
83
+ }
84
+ let { logLogs, warnLogs, infoLogs, errorLogs } = loggers;
85
+ if (errorNotifyToggleURL.value) {
86
+ logs.push(errorLogs);
87
+ logs.push(warnLogs);
88
+ } else {
89
+ if (enableLogsURL.value) {
90
+ logs.push(logLogs);
91
+ }
92
+ if (enableInfosURL.value) {
93
+ logs.push(infoLogs);
94
+ }
95
+ if (enableWarningsURL.value) {
96
+ logs.push(warnLogs);
97
+ }
98
+ if (enableErrorsURL.value) {
99
+ logs.push(errorLogs);
100
+ }
101
+ }
102
+
103
+ const timeRange = getTimeRange();
104
+ return (
105
+ <div className={css.vbox(20).pad2(20).fillBoth}>
106
+ <div className={css.hbox(10)}>
107
+ <div className={css.fontSize(14)}>
108
+ Log Viewer
109
+ </div>
110
+ <InputLabelURL
111
+ label="Error Notification Mode" checkbox url={errorNotifyToggleURL}
112
+ onChangeValue={(newValue) => {
113
+ if (newValue) {
114
+ let now = Date.now();
115
+ startTimeParam.value = now - timeInDay * 7;
116
+ endTimeParam.value = now + timeInHour * 2;
117
+ }
118
+ this.rerun();
119
+ }}
120
+ />
121
+ </div>
122
+
123
+ {!errorNotifyToggleURL.value && <div className={css.hbox(10)}>
124
+ <Button hue={enableLogsURL.value ? 120 : undefined} onClick={() => enableLogsURL.value = !enableLogsURL.value}>
125
+ Logs
126
+ </Button>
127
+ <Button hue={enableInfosURL.value ? 120 : undefined} onClick={() => enableInfosURL.value = !enableInfosURL.value}>
128
+ Infos
129
+ </Button>
130
+ <Button hue={enableWarningsURL.value ? 120 : undefined} onClick={() => enableWarningsURL.value = !enableWarningsURL.value}>
131
+ Warnings
132
+ </Button>
133
+ <Button hue={enableErrorsURL.value ? 120 : undefined} onClick={() => enableErrorsURL.value = !enableErrorsURL.value}>
134
+ Errors
135
+ </Button>
136
+ </div>}
137
+
138
+ <FastArchiveViewer
139
+ ref2={x => this.fastArchiveViewer = x}
140
+ fastArchives={logs}
141
+ onStart={() => {
142
+ this.datumCount = 0;
143
+ this.notMatchedCount = 0;
144
+ this.errors = 0;
145
+ this.notMatchedSize = 0;
146
+ this.matchedSize = 0;
147
+ this.example = undefined;
148
+ this.datums = [];
149
+ this.suppressionCounts = new Map();
150
+ this.expiredSuppressionCounts = new Map();
151
+ }}
152
+ getWantData={async () => {
153
+ if (!Querysub.fastRead(() => errorNotifyToggleURL.value)) return undefined;
154
+ let suppressionList = await SuppressionListController(SocketFunction.browserNodeId()).getSuppressionList.promise();
155
+ let now = Date.now();
156
+ // Newest first, so when we add a new entry we can see what it matches, even if it takes it away from older entries
157
+ sort(suppressionList, x => -x.lastUpdateTime);
158
+ let checkers = suppressionList.map(x => getSuppressEntryChecker(x));
159
+ this.suppressionCounts = new Map(suppressionList.map(x => [x.key, 0]));
160
+ this.expiredSuppressionCounts = new Map(suppressionList.map(x => [x.key, 0]));
161
+ let updateCounts = batchFunction({ delay: 1000 }, () => {
162
+ Querysub.commit(() => {
163
+ this.state.datumsSeqNum++;
164
+ });
165
+ });
166
+ return (posStart, posEnd, data, file) => {
167
+ for (let checker of checkers) {
168
+ if (checker.fnc(data, posStart, posEnd)) {
169
+ if (checker.entry.expiresAt < now) {
170
+ let count = this.expiredSuppressionCounts.get(checker.entry.key) || 0;
171
+ count++;
172
+ this.expiredSuppressionCounts.set(checker.entry.key, count);
173
+ void updateCounts(undefined);
174
+ } else {
175
+ let count = this.suppressionCounts.get(checker.entry.key) || 0;
176
+ count++;
177
+ this.suppressionCounts.set(checker.entry.key, count);
178
+ void updateCounts(undefined);
179
+ return false;
180
+ }
181
+ }
182
+ }
183
+ return true;
184
+ };
185
+ }}
186
+ onDatums={(source, datums, file) => {
187
+ this.datumCount += datums.length;
188
+ if (!this.example && datums.length > 0) {
189
+ this.example = JSON.stringify(datums[0]);
190
+ }
191
+ for (let datum of datums) {
192
+ let time = datum.time;
193
+ if (time && (time < timeRange.startTime || time > timeRange.endTime)) {
194
+ continue;
195
+ }
196
+ datum.__metadata = file;
197
+ this.datums.push(datum);
198
+ }
199
+ }}
200
+ onStats={(source, stats, file) => {
201
+ this.matchedSize += stats.matchedSize;
202
+ this.notMatchedSize += stats.notMatchedSize;
203
+ this.errors += stats.errors;
204
+ this.notMatchedCount += stats.notMatchedCount;
205
+
206
+ let now = Date.now();
207
+ if (now - this.lastRenderTime > RENDER_INTERVAL) {
208
+ this.lastRenderTime = now;
209
+ Querysub.commit(() => {
210
+ this.state.datumsSeqNum++;
211
+ });
212
+ }
213
+ }}
214
+ onFinish={() => {
215
+ Querysub.commit(() => {
216
+ sort(this.datums, x => -(x.time || 0));
217
+ this.state.datumsSeqNum++;
218
+ });
219
+ }}
220
+ >
221
+ {errorNotifyToggleURL.value && <ErrorSuppressionUI
222
+ dataSeqNum={this.state.datumsSeqNum}
223
+ suppressionCounts={this.suppressionCounts}
224
+ expiredSuppressionCounts={this.expiredSuppressionCounts}
225
+ datums={this.datums}
226
+ rerunFilters={() => this.rerun()}
227
+ />}
228
+ {(() => {
229
+ let fieldNames = new Set<string>();
230
+ let anyLimited = false;
231
+ for (let i = 0; i < Math.min(1000, this.datums.length); i++) {
232
+ let datum = this.datums[i];
233
+ for (let key of Object.keys(datum)) {
234
+ fieldNames.add(key);
235
+ }
236
+ if (datum[LOG_LIMIT_FLAG]) {
237
+ anyLimited = true;
238
+ }
239
+ }
240
+ let selectedFields: string[] = [];
241
+ for (let field of Object.keys(defaultSelectedFields)) {
242
+ if (atomic(selectedFieldsURL.value[field]) === undefined) {
243
+ selectedFields.push(field);
244
+ }
245
+ }
246
+ for (let [key, value] of Object.entries(selectedFieldsURL.value)) {
247
+ if (value && !selectedFields.includes(key)) {
248
+ selectedFields.splice(1, 0, key);
249
+ }
250
+ }
251
+ let columns: TableType<LogDatum>["columns"] = {};
252
+ columns["log"] = {
253
+ title: "Log",
254
+ formatter: (x, context) => {
255
+ return <Button onClick={() => console.log(context?.row)}>
256
+ Log
257
+ </Button>;
258
+ }
259
+ };
260
+ if (anyLimited) {
261
+ columns[LOG_LIMIT_FLAG] = {
262
+ title: "Limited",
263
+ formatter: x => x && <div className={css.hsl(0, 50, 50).colorhsl(0, 50, 95).boldStyle.pad2(10).ellipsis}>
264
+ Log Line Throttled
265
+ </div> || undefined
266
+ };
267
+ }
268
+ for (let field of selectedFields) {
269
+ let column: ColumnType<unknown, LogDatum> = {};
270
+ if (field === "time") {
271
+ column.formatter = (x: unknown) => formatDateTime(Number(x));
272
+ }
273
+ if (!column.formatter) {
274
+ column.formatter = (x: unknown) => <ObjectDisplay value={x} />;
275
+ }
276
+ columns[field] = column;
277
+ }
278
+ columns["fields"] = {
279
+ title: "Fields",
280
+ formatter: (x, context) => {
281
+ if (!context?.row) return undefined;
282
+ let datum = context.row;
283
+ let fields = Object.keys(datum);
284
+ fields = fields.filter(x => !(x in columns) && !x.startsWith("_"));
285
+ return <div className={css.hbox(4, 4).wrap}>
286
+ {fields.map(x => <div
287
+ className={css.pad2(4, 0).hsl(0, 0, 80).button}
288
+ onClick={() => {
289
+ let newValues = { ...selectedFieldsURL.value };
290
+ newValues[x] = true;
291
+ selectedFieldsURL.value = newValues;
292
+ }}
293
+ >
294
+ {x}
295
+ </div>)}
296
+ </div>;
297
+ }
298
+ };
299
+ return <>
300
+ <InputPicker
301
+ label={<div className={css.hbox(10)}>
302
+ <div className={css.flexShrink0}>
303
+ Selected Fields
304
+ </div>
305
+ <Button onClick={() => {
306
+ let newValues = { ...selectedFieldsURL.value };
307
+ for (let key of Object.keys(newValues)) {
308
+ newValues[key] = key in defaultSelectedFields;
309
+ }
310
+ selectedFieldsURL.value = newValues;
311
+ }}>
312
+ Reset
313
+ </Button>
314
+ </div>}
315
+ picked={selectedFields}
316
+ options={Array.from(fieldNames).map(x => ({ value: x, label: x }))}
317
+ addPicked={x => {
318
+ let newValues = { ...selectedFieldsURL.value };
319
+ newValues[x] = true;
320
+ selectedFieldsURL.value = newValues;
321
+ }}
322
+ removePicked={x => {
323
+ let newValues = { ...selectedFieldsURL.value };
324
+ newValues[x] = false;
325
+ selectedFieldsURL.value = newValues;
326
+ }}
327
+ />
328
+ <Table
329
+ rows={this.datums}
330
+ columns={columns}
331
+ lineLimit={4}
332
+ characterLimit={400}
333
+ getRowAttributes={row => {
334
+ let hue = -1;
335
+ if (row.__LOG_TYPE === "warn") hue = 40;
336
+ if (row.__LOG_TYPE === "error") hue = 0;
337
+ if (row.__LOG_TYPE === "info") hue = 200;
338
+ return {
339
+ className: hue !== -1 && css.hsl(hue, 40, 50).colorhsl(hue, 0, 100) || undefined,
340
+ };
341
+ }}
342
+ />
343
+ </>;
344
+ })()}
345
+ </FastArchiveViewer>
346
+ </div>
347
+ );
348
+ }
349
+ }
@@ -0,0 +1,94 @@
1
+ module.hotreload = true;
2
+
3
+ import { qreact } from "../../4-dom/qreact";
4
+ import { URLParam } from "../../library-components/URLParam";
5
+ import { css } from "../../4-dom/css";
6
+ import { formatTime } from "socket-function/src/formatting/format";
7
+ import { InputLabel } from "../../library-components/InputLabel";
8
+ import { Button } from "../../library-components/Button";
9
+ import { Querysub } from "../../4-querysub/QuerysubController";
10
+ import { timeInHour, timeInMinute } from "socket-function/src/misc";
11
+
12
+ // URL parameters for time range
13
+ export const startTimeParam = new URLParam("startTime", undefined as number | undefined);
14
+ export const endTimeParam = new URLParam("endTime", undefined as number | undefined);
15
+
16
+ let now = Date.now();
17
+ /** Get the time range with default values if not set */
18
+ export function getTimeRange(): { startTime: number; endTime: number } {
19
+ const defaultStart = now - timeInHour * 24;
20
+ const defaultEnd = now + timeInHour;
21
+
22
+ return {
23
+ startTime: startTimeParam.value ?? defaultStart,
24
+ endTime: endTimeParam.value ?? defaultEnd,
25
+ };
26
+ }
27
+
28
+ export class TimeRangeSelector extends qreact.Component {
29
+ render() {
30
+ const timeRange = getTimeRange();
31
+
32
+ const resetToLastDay = () => {
33
+ now = Date.now();
34
+ startTimeParam.reset();
35
+ endTimeParam.reset();
36
+ };
37
+
38
+ const formatDateTimeLocal = (timestamp: number) => {
39
+ // Convert to local datetime-local format: YYYY-MM-DDTHH:MM
40
+ return new Date(timestamp - new Date().getTimezoneOffset() * 60000)
41
+ .toISOString()
42
+ .slice(0, 16);
43
+ };
44
+
45
+ const parseDateTimeLocal = (dateTimeString: string) => {
46
+ // Parse datetime-local format and convert to timestamp
47
+ return new Date(dateTimeString).getTime();
48
+ };
49
+
50
+
51
+ let now = Querysub.nowDelayed(timeInMinute);
52
+ return (
53
+ <div className={css.vbox(12).pad2(16).bord2(200, 20, 80).hsl(200, 10, 98)}>
54
+ <div className={css.hbox(12).wrap}>
55
+ <div className={css.hbox(8).wrap.opacity(0.8).width(200)}>
56
+ From {formatTime(now - timeRange.startTime)} AGO to {timeRange.endTime > now ? "now" : `${formatTime(now - timeRange.endTime)} AGO`}
57
+ </div>
58
+ <InputLabel
59
+ label="Start Time"
60
+ type="datetime-local"
61
+ value={formatDateTimeLocal(timeRange.startTime)}
62
+ onChange={e => {
63
+ startTimeParam.value = parseDateTimeLocal(e.currentTarget.value);
64
+ }}
65
+ outerClass={!startTimeParam.value && css.opacity(0.5) || ""}
66
+ />
67
+ <InputLabel
68
+ label="End Time"
69
+ type="datetime-local"
70
+ value={formatDateTimeLocal(timeRange.endTime)}
71
+ onChange={e => {
72
+ endTimeParam.value = parseDateTimeLocal(e.currentTarget.value);
73
+ }}
74
+ outerClass={!endTimeParam.value && css.opacity(0.5) || ""}
75
+ />
76
+ {(endTimeParam.value || startTimeParam.value) && <Button
77
+ hue={110} onClick={resetToLastDay}
78
+ >
79
+ Reset to Last Day
80
+ </Button>}
81
+ {(!startTimeParam.value || !endTimeParam.value) && <Button
82
+ hue={110}
83
+ onClick={() => {
84
+ startTimeParam.value = timeRange.startTime;
85
+ endTimeParam.value = timeRange.endTime;
86
+ }}
87
+ >
88
+ Save Time Range
89
+ </Button>}
90
+ </div>
91
+ </div>
92
+ );
93
+ }
94
+ }