querysub 0.311.0 → 0.313.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 (69) 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 +9 -2
  14. package/src/-d-trust/NetworkTrust2.ts +38 -31
  15. package/src/-e-certs/EdgeCertController.ts +3 -4
  16. package/src/-e-certs/certAuthority.ts +1 -2
  17. package/src/-f-node-discovery/NodeDiscovery.ts +20 -13
  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/diagnostics/NodeViewer.tsx +3 -4
  35. package/src/diagnostics/logs/FastArchiveAppendable.ts +748 -0
  36. package/src/diagnostics/logs/FastArchiveController.ts +524 -0
  37. package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
  38. package/src/diagnostics/logs/LogViewer2.tsx +349 -0
  39. package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
  40. package/src/diagnostics/logs/diskLogger.ts +135 -305
  41. package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
  42. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
  43. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
  44. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
  45. package/src/diagnostics/logs/importLogsEntry.ts +38 -0
  46. package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
  47. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
  48. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +151 -0
  49. package/src/diagnostics/managementPages.tsx +7 -16
  50. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
  51. package/src/diagnostics/periodic.ts +5 -0
  52. package/src/diagnostics/watchdog.ts +2 -2
  53. package/src/functional/SocketChannel.ts +67 -0
  54. package/src/library-components/Input.tsx +1 -1
  55. package/src/library-components/InputLabel.tsx +5 -2
  56. package/src/misc.ts +111 -0
  57. package/src/src.d.ts +34 -1
  58. package/src/user-implementation/userData.ts +4 -3
  59. package/test.ts +13 -0
  60. package/testEntry2.ts +29 -0
  61. package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
  62. package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
  63. package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
  64. package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
  65. package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
  66. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
  67. package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
  68. package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
  69. package/src/diagnostics/logs/DiskLoggerPage.tsx +0 -613
@@ -1,723 +0,0 @@
1
-
2
- import preact from "preact"; import { qreact } from "../../../src/4-dom/qreact";
3
- import { css } from "typesafecss";
4
- import { cacheArgsEqual } from "socket-function/src/caching";
5
- import { entries, keys, sort, timeInDay, timeInHour, timeInMinute, timeInYear } from "socket-function/src/misc";
6
- import { URLParam } from "../../../src/library-components/URLParam";
7
- import { ATag } from "../../../src/library-components/ATag";
8
- import { Button, getAnimationFrames } from "../../../src/library-components/Button";
9
- import { performDrag2 } from "../../../src/library-components/drag";
10
- import { atomic, atomicObjectWrite } from "../../../src/2-proxy/PathValueProxyWatcher";
11
- import { formatNumber, formatPercent } from "socket-function/src/formatting/format";
12
- import { Counts, PickLogType, countColors, countTypes, showErrorURL, showFatalURL, showInfoURL, showWarningURL } from "./logFiltering";
13
- import { ButtonSelector } from "../../library-components/ButtonSelector";
14
- import { Icon } from "../../library-components/icons";
15
- import { MeasuredDiv } from "../../library-components/MeasuredDiv";
16
-
17
- type RangeSize = "lastYear" | "lastMonth" | "lastWeek" | "lastDay" | "lastHour";
18
- export let logNowTime = Date.now();
19
- const logFilterStart = new URLParam<number | RangeSize>("logFilterStartTime", "lastWeek");
20
- const logFilterEnd = new URLParam<number>("logFilterEndTime", 0);
21
- let rangeThreshold: { [K in RangeSize]: number } = {
22
- lastHour: timeInHour * 2,
23
- lastDay: timeInHour * 30,
24
- lastWeek: timeInDay * 10,
25
- lastMonth: timeInDay * 60,
26
- lastYear: Number.MAX_SAFE_INTEGER,
27
- };
28
- export function getLogFilterRange(config?: {
29
- start?: number | RangeSize;
30
- end?: number;
31
- }) {
32
- let startValue = config?.start || logFilterStart.value;
33
- let endTime = config?.end || logFilterEnd.value || (logNowTime + 15000);
34
- let startTime: number;
35
- let size: RangeSize;
36
- if (typeof startValue === "number") {
37
- startTime = startValue;
38
- if (startTime > endTime) {
39
- [startTime, endTime] = [endTime, startTime];
40
- }
41
- let curRange = endTime - startTime;
42
- size = entries(rangeThreshold).find(([key, value]) => value > curRange)?.[0] || "lastYear";
43
- } else {
44
- size = startValue;
45
- if (startValue === "lastYear") {
46
- startTime = endTime - timeInYear;
47
- } else if (startValue === "lastMonth") {
48
- startTime = endTime - timeInDay * 31;
49
- } else if (startValue === "lastWeek") {
50
- startTime = endTime - timeInDay * 7;
51
- } else if (startValue === "lastDay") {
52
- startTime = endTime - timeInDay;
53
- } else if (startValue === "lastHour") {
54
- startTime = endTime - timeInHour;
55
- } else {
56
- let unhandled: never = startValue;
57
- throw new Error("Invalid size: " + startValue);
58
- }
59
- }
60
- return { startTime, endTime, rangeSize: size };
61
- }
62
- export function resetLogFilter() {
63
- logNowTime = Date.now();
64
- }
65
-
66
- export type TimelineInfo = {
67
- startTime: number;
68
- endTime: number;
69
-
70
- count: number;
71
-
72
- fatalCount: number;
73
- errorCount: number;
74
- warnCount: number;
75
- infoCount: number;
76
- };
77
-
78
- export class LogTimeSelector extends qreact.Component<{
79
- infos: TimelineInfo[] | undefined;
80
- hotkeys?: boolean;
81
- }> {
82
- state = {
83
- width: 1000,
84
- height: 1000,
85
- // atomic
86
- drag: undefined as { startFraction: number; endFraction: number; } | undefined,
87
- };
88
- canvasObj: undefined | {
89
- canvas: HTMLCanvasElement;
90
- ctx: CanvasRenderingContext2D;
91
- };
92
- render() {
93
- let infos = this.props.infos;
94
-
95
- let timeObj = getLogFilterRange();
96
- function deltaTimes(fraction: number) {
97
- let time = (timeObj.endTime - timeObj.startTime) * fraction;
98
- logFilterStart.value = timeObj.startTime + time;
99
- logFilterEnd.value = timeObj.endTime + time;
100
- }
101
- let blockHisto = getBlockHistoBreakdown({
102
- infos,
103
- barCount: this.state.width,
104
- ...timeObj,
105
- showFatal: showFatalURL.value,
106
- showError: showErrorURL.value,
107
- showWarn: showWarningURL.value,
108
- showInfo: showInfoURL.value,
109
- });
110
- function formatDateTimeForInput(value: number) {
111
- value -= new Date(value).getTimezoneOffset() * 60 * 1000;
112
- return new Date(value).toISOString().slice(0, -1);
113
- }
114
- const drag = atomic(this.state.drag);
115
-
116
- let canvasObj = this.canvasObj;
117
- if (canvasObj) {
118
- if (
119
- canvasObj.canvas.width !== this.state.width
120
- || canvasObj.canvas.height !== this.state.height
121
- ) {
122
- canvasObj.canvas.width = this.state.width;
123
- canvasObj.canvas.height = this.state.height;
124
- }
125
- const context = canvasObj.ctx;
126
- const bars = blockHisto.bars;
127
- let width = this.state.width;
128
- let height = this.state.height;
129
- context.clearRect(0, 0, width, height);
130
- for (let i = 0; i < bars.length; i++) {
131
- let xFrac = i / bars.length;
132
- let isDragging = drag && drag.startFraction <= xFrac && xFrac <= drag.endFraction;
133
-
134
- let bar = bars[i];
135
- let yPos = height;
136
- for (let type of countTypes) {
137
- let color = countColors[type];
138
- let l = color.l;
139
- if (isDragging) {
140
- l += 30;
141
- }
142
- context.fillStyle = `hsl(${color.h}, ${color.s}%, ${l}%)`;
143
- let curHeight = bar.count[type] / blockHisto.maxBarCount * height * 0.95;
144
- if (curHeight > 0 && curHeight < 3) {
145
- curHeight = 3;
146
- }
147
- context.fillRect(i, yPos - curHeight, 1, curHeight);
148
- yPos -= curHeight;
149
- }
150
- }
151
- }
152
-
153
- function formatTitleNumber(config: {
154
- value: Counts;
155
- title: string;
156
- showHeight?: boolean;
157
- fixedWidth?: boolean;
158
- }) {
159
- const { value, title } = config;
160
- return (
161
- <div class={
162
- css
163
- .fontSize(60).boldStyle
164
- .center.hbox(10)
165
- .pad2(40)
166
- .fillHeight.relative.zIndex(0)
167
- .pointerEvents("none")
168
- .userSelect("none")
169
- .flexShrink0
170
- .relative.zIndex(1)
171
- + (config.showHeight && css.border("1px solid hsl(0, 0%, 20%)"))
172
- + (config.fixedWidth && css.width("20%"))
173
- }>
174
- {config.showHeight && <div
175
- class={
176
- css.fillWidth.bottom0.absolute.height(`${value.total / blockHisto.totalCount * 100}%`)
177
- .zIndex(-1)
178
- .transition("height 0.5s")
179
- .vbox0
180
- }
181
- >
182
- {countTypes.map(type => {
183
- let color = countColors[type];
184
- return (
185
- <div
186
- class={css
187
- .fillWidth.height(`${value[type] / value.total * 100}%`)
188
- .hsl(color.h, color.s, color.l)
189
- }
190
- />
191
- );
192
- })}
193
- </div>}
194
- <div>
195
- {formatNumber(value.total)}
196
- </div>
197
- <div class={css.vbox(10).fontSize(12).fontWeight("normal").color("hsl(0, 0%, 20%)")}>
198
- <div>{title}</div>
199
- <div>{formatPercent(value.total / blockHisto.totalCount)}</div>
200
- </div>
201
- </div>
202
- );
203
- }
204
- function renderUIHorizontal(title: string, count: Counts, key: string) {
205
- let isSelected = logFilterStart.value === key;
206
- return (
207
- <div class={
208
- css.relative.pad2(10, 4).color("white").border("")
209
- .hsla(0, 0, 20, 1)
210
- .border("1px solid hsl(0, 0%, 10%)")
211
- + (!isSelected && css.opacity(0.5))
212
- }>
213
- <div class={css.pos(0, 0).fillBoth.absolute.zIndex(0).hbox0}>
214
- {countTypes.map(type => {
215
- let color = countColors[type];
216
- let fraction = count[type] / count.total;
217
- return (
218
- <div
219
- class={
220
- css
221
- .fillHeight.width(`calc(max(${fraction > 0 ? 8 : 0}px, ${fraction * 100}%))`)
222
- .hsl(color.h, color.s, color.l)
223
- }
224
- />
225
- );
226
- })}
227
- </div>
228
- <div class={css.relative}>{title} ({formatNumber(count.total)})</div>
229
- </div>
230
- );
231
- }
232
-
233
- return (
234
- <div class={css.width("100%").vbox0}>
235
- <div class={css.hbox(10).fillWidth}>
236
- <ButtonSelector
237
- title="Show Last"
238
- value={logFilterStart.value}
239
- noDefault
240
- noUI
241
- onChange={value => {
242
- logFilterStart.value = value;
243
- logFilterEnd.value = 0;
244
- }}
245
- options={[
246
- { value: "lastYear", title: renderUIHorizontal("Year", blockHisto.counts.lastYear, "lastYear"), },
247
- { value: "lastMonth", title: renderUIHorizontal("Month", blockHisto.counts.lastMonth, "lastMonth"), },
248
- { value: "lastWeek", title: renderUIHorizontal("Week", blockHisto.counts.lastWeek, "lastWeek"), },
249
- { value: "lastDay", title: renderUIHorizontal("Day", blockHisto.counts.lastDay, "lastDay"), },
250
- { value: "lastHour", title: renderUIHorizontal("Hour", blockHisto.counts.lastHour, "lastHour"), },
251
- ]}
252
- />
253
- <div class={css.marginAuto} />
254
- <PickLogType infos={this.props.infos} />
255
- <div class={css.marginAuto} />
256
- <div class={css.hbox(10).flexShrink0.wrap}>
257
- {(logFilterEnd.value || logFilterStart.value !== logFilterStart.default) &&
258
- <Button onClick={() => {
259
- logFilterStart.reset();
260
- logFilterEnd.reset();
261
- }}>
262
- Reset to Present
263
- </Button>
264
- }
265
- <input
266
- type="datetime-local"
267
- value={formatDateTimeForInput(getLogFilterRange().startTime)}
268
- onInput={e => {
269
- let date = new Date(e.currentTarget.value);
270
- logFilterStart.value = date.getTime();
271
- }}
272
- />
273
- <span>to</span>
274
- <input
275
- type="datetime-local"
276
- value={formatDateTimeForInput(getLogFilterRange().endTime)}
277
- onInput={e => {
278
- let date = new Date(e.currentTarget.value);
279
- logFilterEnd.value = date.getTime();
280
- }}
281
- />
282
- </div>
283
- </div>
284
- <div class={css.fillWidth.hbox(10).pad2(10)}>
285
- {formatTitleNumber({ value: blockHisto.countBefore, title: "Before", showHeight: true, fixedWidth: true })}
286
- <div class={css.fillWidth.outline("1px solid hsl(0, 0%, 20%)")}>
287
- <div className={css.size("100%", 20).relative.overflowHidden}>
288
- {blockHisto.labels.map(label => {
289
- return (
290
- <ATag
291
- title={label.label}
292
- data-indexStart={label.indexStart}
293
- data-indexEnd={label.indexEnd}
294
- values={[
295
- { param: logFilterEnd, value: label.endTime },
296
- { param: logFilterStart, value: label.startTime },
297
- ]}
298
- class={
299
- css.width(label.indexEnd - label.indexStart)
300
- .absolute
301
- .left(label.indexStart)
302
- .bottom0
303
- .fillHeight
304
- .center
305
- .overflowHidden
306
- .textAlign("center")
307
- .whiteSpace("nowrap")
308
- .hsla(0, 0, 20, 1)
309
- .color("hsl(0, 0%, 90%)", "important")
310
- .boldStyle
311
- .border("1px solid hsl(0, 0%, 75%)")
312
- .borderTopWidth(0)
313
- .borderBottomWidth(0)
314
- }
315
- >
316
- {label.label}
317
- </ATag>
318
- );
319
- })}
320
- <Button
321
- flavor="noui"
322
- class={
323
- css.absolute.top0.left0
324
- .fillHeight
325
- .hbox0
326
- .background(`linear-gradient(
327
- 90deg,
328
- hsla(0, 0%, 20%, 1) 10%,
329
- hsla(0, 0%, 20%, 0.95),
330
- hsla(0, 0%, 20%, 0.5)
331
- )`)
332
- .cursor("pointer").userSelect("none").filter("brightness(1.5)", "hover")
333
- .width(70)
334
- .paddingLeft(10)
335
- .border("0")
336
- }
337
- hotkeys={["Global+ArrowLeft"]}
338
- onClick={() => deltaTimes(-0.008 * getAnimationFrames())}
339
- immediateRepeat
340
- >
341
- {Icon.chevronDoubleLeft({ fill: "hsl(0, 0%, 50%)", stroke: "hsl(0, 0%, 60%)" })}
342
- </Button>
343
-
344
- <Button
345
- flavor="noui"
346
- class={
347
- css.absolute.top0.right0
348
- .fillHeight
349
- .hbox0
350
- .background(`linear-gradient(
351
- 90deg,
352
- transparent 0%,
353
- transparent 60%,
354
- hsla(0, 0%, 20%, 0.5) 60%,
355
- hsla(0, 0%, 20%, 0.95) 70%,
356
- hsla(0, 0%, 20%, 1) 100%
357
- )`)
358
- .cursor("pointer").userSelect("none").filter("brightness(1.5)", "hover")
359
- .width(160)
360
- .border("0")
361
- .justifyContent("end")
362
- }
363
- hotkeys={["Global+ArrowRight"]}
364
- immediateRepeat
365
- onClick={() => deltaTimes(0.008 * getAnimationFrames())}
366
- >
367
- {Icon.chevronDoubleRight({ fill: "hsl(0, 0%, 50%)", stroke: "hsl(0, 0%, 60%)" })}
368
- </Button>
369
- </div>
370
- <MeasuredDiv
371
- class={
372
- css.size("100%", 100).relative.overflowHidden.flex.center
373
- }
374
- onNewSize={(width, height) => { this.state.width = width; this.state.height = height; }}
375
- onMouseDown={e => {
376
- e.preventDefault();
377
- let rect = e.currentTarget.getBoundingClientRect();
378
- let self = this;
379
- let startFraction = (e.clientX - rect.left) / rect.width;
380
- performDrag2({
381
- e,
382
- onMove(offset) {
383
- let endFraction = startFraction + offset.x / rect.width;
384
- let newStart = startFraction;
385
- let newEnd = endFraction;
386
- if (newEnd < newStart) {
387
- [newStart, newEnd] = [newEnd, newStart];
388
- }
389
- self.state.drag = atomicObjectWrite({ startFraction: newStart, endFraction: newEnd, });
390
- },
391
- onDone(offset) {
392
- let endFraction = startFraction + offset.x / rect.width;
393
- let timeRange = timeObj.endTime - timeObj.startTime;
394
- logFilterStart.value = timeObj.startTime + timeRange * startFraction;
395
- logFilterEnd.value = timeObj.startTime + timeRange * endFraction;
396
- },
397
- onFinally() {
398
- self.state.drag = undefined;
399
- },
400
- });
401
- }}
402
- onWheel={e => {
403
- e.preventDefault();
404
- e.stopPropagation();
405
- let timeRange = timeObj.endTime - timeObj.startTime;
406
- let deltaAmount = timeRange * 0.20 * (e.deltaY < 0 ? -1 : 1);
407
- logFilterStart.value = timeObj.startTime - deltaAmount;
408
- logFilterEnd.value = timeObj.endTime + deltaAmount;
409
- }}
410
- >
411
- {drag && <div
412
- class={
413
- css.left(`${drag.startFraction * 100}%`)
414
- .right(`${(1 - drag.endFraction) * 100}%`)
415
- .fillHeight
416
- .absolute
417
- .hsl(200, 50, 50)
418
- .opacity(0.5)
419
- }
420
- />}
421
- <canvas
422
- class={css.fillWidth.fillHeight.absolute}
423
- key="canvas"
424
- ref2={e => {
425
- if (this.canvasObj?.canvas === e) return;
426
- this.canvasObj = { canvas: e, ctx: e.getContext("2d")! };
427
- }}
428
- />
429
- {formatTitleNumber({ value: blockHisto.countInside, title: "Matched" })}
430
- </MeasuredDiv>
431
- <div className={css.size("100%", 20).relative.overflowHidden}>
432
- {blockHisto.labels.map(label => {
433
- return (
434
- <ATag
435
- title={label.label}
436
- data-indexStart={label.indexStart}
437
- data-indexEnd={label.indexEnd}
438
- values={[
439
- { param: logFilterEnd, value: label.endTime },
440
- { param: logFilterStart, value: label.startTime },
441
- ]}
442
- class={
443
- css.width(label.indexEnd - label.indexStart)
444
- .absolute
445
- .left(label.indexStart)
446
- .bottom0
447
- .fillHeight
448
- .center
449
- .overflowHidden
450
- .textAlign("center")
451
- .whiteSpace("nowrap")
452
- .hsla(0, 0, 20, 1)
453
- .color("hsl(0, 0%, 90%)", "important")
454
- .boldStyle
455
- .border("1px solid hsl(0, 0%, 75%)")
456
- .borderTopWidth(0)
457
- .borderBottomWidth(0)
458
- }
459
- >
460
- {label.label}
461
- </ATag>
462
- );
463
- })}
464
- </div>
465
- </div>
466
- {formatTitleNumber({ value: blockHisto.countAfter, title: "After", showHeight: true, fixedWidth: true })}
467
- </div>
468
- </div>
469
- );
470
- }
471
- }
472
-
473
- const getBlockHistoBreakdown = cacheArgsEqual(function getBlockHistoBreakdown(config: {
474
- infos: TimelineInfo[] | undefined;
475
- barCount: number;
476
- rangeSize: RangeSize;
477
- startTime: number;
478
- endTime: number;
479
-
480
- showFatal: boolean;
481
- showError: boolean;
482
- showWarn: boolean;
483
- showInfo: boolean;
484
- }): {
485
- totalCount: number;
486
- maxBarCount: number;
487
- bars: {
488
- startTime: number;
489
- endTime: number;
490
-
491
- count: Counts;
492
- }[];
493
- labels: {
494
- indexStart: number;
495
- indexEnd: number;
496
- startTime: number;
497
- endTime: number;
498
- label: string;
499
- }[];
500
-
501
- countBefore: Counts;
502
- countInside: Counts;
503
- countAfter: Counts;
504
-
505
- counts: { [key in RangeSize]: Counts; }
506
- } {
507
- let { infos, barCount, startTime, endTime } = config;
508
-
509
- let countBefore: Counts = { total: 0, fatal: 0, error: 0, warn: 0, info: 0 };
510
- let countInside: Counts = { total: 0, fatal: 0, error: 0, warn: 0, info: 0 };
511
- let countAfter: Counts = { total: 0, fatal: 0, error: 0, warn: 0, info: 0 };
512
- function addToCount(counts: Counts, checkShow: boolean, info: {
513
- count: number;
514
- fatalCount: number;
515
- errorCount: number;
516
- warnCount: number;
517
- infoCount: number;
518
- }) {
519
- if (!checkShow || config.showFatal) {
520
- counts.fatal += info.fatalCount;
521
- counts.total += info.fatalCount;
522
- }
523
-
524
- if (!checkShow || config.showError) {
525
- counts.error += info.errorCount;
526
- counts.total += info.errorCount;
527
- }
528
-
529
- if (!checkShow || config.showWarn) {
530
- counts.warn += info.warnCount;
531
- counts.total += info.warnCount;
532
- }
533
-
534
- if (!checkShow || config.showInfo) {
535
- counts.info += info.infoCount;
536
- counts.total += info.infoCount;
537
- }
538
- }
539
-
540
- let totalCount = 0;
541
- infos = infos || [];
542
- infos = infos.slice();
543
- sort(infos, a => a.startTime);
544
-
545
- let countsByType: { [key in RangeSize]: Counts } = {} as any;
546
- for (let key of keys(rangeThreshold)) {
547
- let timeRange = getLogFilterRange({ start: key, end: logNowTime });
548
- let counts = { total: 0, fatal: 0, error: 0, warn: 0, info: 0 };
549
- for (let info of infos) {
550
- if (!(info.endTime <= timeRange.startTime || info.startTime >= timeRange.endTime)) {
551
- addToCount(counts, false, info);
552
- }
553
- }
554
- countsByType[key] = counts;
555
- }
556
-
557
- infos = infos.filter(info => {
558
- totalCount += info.count;
559
- if (info.endTime <= startTime) {
560
- addToCount(countBefore, true, info);
561
- return false;
562
- }
563
- if (info.startTime >= endTime) {
564
- addToCount(countAfter, true, info);
565
- return false;
566
- }
567
- addToCount(countInside, true, info);
568
- return true;
569
- });
570
-
571
- let bars: {
572
- startTime: number;
573
- endTime: number;
574
- count: Counts;
575
- }[] = [];
576
- function getBarRange(barIndex: number) {
577
- let f = barIndex / barCount;
578
- let fEnd = (barIndex + 1) / barCount;
579
- let barStartTime = startTime * (1 - f) + endTime * f;
580
- let barEndTime = startTime * (1 - fEnd) + endTime * fEnd;
581
- return { startTime: barStartTime, endTime: barEndTime };
582
- }
583
- function getBarIndex(time: number) {
584
- let f = (time - startTime) / (endTime - startTime);
585
- return Math.floor(f * barCount);
586
- }
587
- for (let i = 0; i < barCount; i++) {
588
- bars.push({ ...getBarRange(i), count: { total: 0, fatal: 0, error: 0, warn: 0, info: 0 }, });
589
- }
590
- for (let info of infos) {
591
- let index = getBarIndex(info.startTime);
592
- if (index < 0) index = 0;
593
- if (index >= bars.length) continue;
594
- while (index < bars.length && bars[index].startTime < info.endTime) {
595
- addToCount(bars[index++].count, true, info);
596
- }
597
- }
598
-
599
- let maxBarCount = Math.max(...bars.map(a => a.count.total));
600
-
601
- const labels = createLabels({
602
- rangeSize: config.rangeSize,
603
- startTime,
604
- endTime,
605
- getBarIndex,
606
- });
607
-
608
- return {
609
- totalCount, maxBarCount, bars, labels, countBefore, countInside, countAfter, counts: countsByType,
610
- };
611
- }, 10);
612
- function createLabels(config: {
613
- rangeSize: RangeSize;
614
- startTime: number;
615
- endTime: number;
616
- getBarIndex: (time: number) => number;
617
- }) {
618
- const { rangeSize, startTime, endTime, getBarIndex } = config;
619
- let labels: {
620
- indexStart: number;
621
- indexEnd: number;
622
- startTime: number;
623
- endTime: number;
624
- label: string;
625
- }[] = [];
626
- let periodAverageSize = 0;
627
- let startOfPeriod: (date: Date) => Date;
628
- let periods = 0;
629
- let formatPeriodStart = (date: Date) => date.toDateString();
630
-
631
- const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
632
- const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
633
- function formatPlace(place: number) {
634
- if (place === 1) return "1st";
635
- if (place === 2) return "2nd";
636
- if (place === 3) return "3rd";
637
- return place + "th";
638
- }
639
- if (rangeSize === "lastYear") {
640
- periodAverageSize = timeInDay * 31;
641
- startOfPeriod = date => new Date(date.getFullYear(), date.getMonth(), 1);
642
- formatPeriodStart = (date: Date) => `${date.getFullYear()} ${MONTHS[date.getMonth()].slice(0, 3)}`;
643
- periods = 12;
644
- } else if (rangeSize === "lastMonth") {
645
- periodAverageSize = timeInDay * 7;
646
- startOfPeriod = date => {
647
- let dayOfWeek = date.getDay();
648
- let daysBack = dayOfWeek % 7;
649
- return new Date(date.getFullYear(), date.getMonth(), date.getDate() - daysBack);
650
- };
651
- formatPeriodStart = (date: Date) => {
652
- let endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 7);
653
- // April 15
654
- function formatInner(date: Date) {
655
- return `${MONTHS[date.getMonth()]} ${date.getDate()}`;
656
- }
657
- return formatInner(date) + " - " + formatInner(endDate);
658
- };
659
- periods = 31;
660
- } else if (rangeSize === "lastWeek") {
661
- periodAverageSize = timeInDay;
662
- startOfPeriod = date => new Date(date.getFullYear(), date.getMonth(), date.getDate());
663
- // Monday 5th
664
- formatPeriodStart = (date: Date) => `${WEEKDAYS[date.getDay()]} (${formatPlace(date.getDate())})`;
665
- periods = 7;
666
- } else if (rangeSize === "lastDay") {
667
- periodAverageSize = timeInHour;
668
- startOfPeriod = date => new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours());
669
- formatPeriodStart = (date: Date) => {
670
- // Ex, 6 PM
671
- let hour = date.getHours();
672
- let ampm = hour < 12 ? "AM" : "PM";
673
- hour = hour % 12;
674
- if (hour === 0) hour = 12;
675
- return hour + " " + ampm;
676
- };
677
- periods = 24;
678
- } else if (rangeSize === "lastHour") {
679
- periodAverageSize = timeInMinute * 10;
680
- startOfPeriod = date => new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), Math.floor(date.getMinutes() / 10) * 10);
681
- formatPeriodStart = (date: Date) => {
682
- let minutes = date.getMinutes();
683
- let minutesStr = minutes.toString().padStart(2, "0");
684
- // Render as PM/AM
685
- let hour = date.getHours();
686
- let ampm = hour < 12 ? "AM" : "PM";
687
- hour = hour % 12;
688
- if (hour === 0) hour = 12;
689
- return `${hour}:${minutesStr} ${ampm}`;
690
- };
691
- periods = 2;
692
- } else {
693
- let unhandled: never = rangeSize;
694
- throw new Error(`Unhandled rangeSize: ${rangeSize}`);
695
- }
696
-
697
- let date = new Date(endTime);
698
- date = new Date(+date + periodAverageSize * 1.5);
699
- date = startOfPeriod(date);
700
- let times: number[] = [];
701
- times.push(date.getTime());
702
- while (true) {
703
- date = startOfPeriod(new Date(+date - periodAverageSize * 0.5));
704
- times.push(date.getTime());
705
- if (date.getTime() <= startTime) break;
706
- }
707
- times.reverse();
708
- for (let i = 0; i < times.length - 1; i++) {
709
- let startTime = times[i];
710
- let endTime = times[i + 1];
711
- let firstBarIndex = getBarIndex(startTime);
712
- let endBarIndex = getBarIndex(endTime);
713
- labels.push({
714
- indexStart: firstBarIndex,
715
- indexEnd: endBarIndex,
716
- startTime,
717
- endTime,
718
- label: formatPeriodStart(new Date(startTime)),
719
- });
720
- }
721
-
722
- return labels;
723
- }