querysub 0.327.0 → 0.329.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.
- package/bin/error-email.js +8 -0
- package/bin/error-im.js +8 -0
- package/package.json +4 -3
- package/src/-a-archives/archivesBackBlaze.ts +20 -0
- package/src/-a-archives/archivesCborT.ts +52 -0
- package/src/-a-archives/archivesDisk.ts +5 -5
- package/src/-a-archives/archivesJSONT.ts +19 -5
- package/src/-a-archives/archivesLimitedCache.ts +118 -7
- package/src/-a-archives/archivesPrivateFileSystem.ts +3 -0
- package/src/-g-core-values/NodeCapabilities.ts +26 -11
- package/src/0-path-value-core/auditLogs.ts +4 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +7 -0
- package/src/3-path-functions/PathFunctionRunner.ts +2 -2
- package/src/4-querysub/Querysub.ts +1 -1
- package/src/5-diagnostics/GenericFormat.tsx +2 -2
- package/src/config.ts +15 -3
- package/src/deployManager/machineApplyMainCode.ts +10 -8
- package/src/deployManager/machineSchema.ts +4 -3
- package/src/deployManager/setupMachineMain.ts +3 -2
- package/src/diagnostics/logs/FastArchiveAppendable.ts +86 -53
- package/src/diagnostics/logs/FastArchiveController.ts +11 -2
- package/src/diagnostics/logs/FastArchiveViewer.tsx +205 -48
- package/src/diagnostics/logs/LogViewer2.tsx +78 -34
- package/src/diagnostics/logs/TimeRangeSelector.tsx +8 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +5 -4
- package/src/diagnostics/logs/diskLogger.ts +70 -23
- package/src/diagnostics/logs/errorNotifications/ErrorDigestPage.tsx +409 -0
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +94 -67
- package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +37 -3
- package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +50 -16
- package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +174 -0
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +291 -0
- package/src/diagnostics/logs/errorNotifications/errorLoopEntry.tsx +7 -0
- package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +185 -68
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +10 -19
- package/src/diagnostics/managementPages.tsx +33 -15
- package/src/email_ims_notifications/discord.tsx +203 -0
- package/src/{email → email_ims_notifications}/postmark.tsx +3 -3
- package/src/fs.ts +9 -0
- package/src/functional/SocketChannel.ts +9 -0
- package/src/functional/throttleRender.ts +134 -0
- package/src/library-components/ATag.tsx +2 -2
- package/src/library-components/SyncedController.ts +3 -3
- package/src/misc.ts +18 -0
- package/src/misc2.ts +106 -0
- package/src/user-implementation/SecurityPage.tsx +11 -5
- package/src/user-implementation/userData.ts +57 -23
- package/testEntry2.ts +14 -5
- package/src/user-implementation/setEmailKey.ts +0 -25
- /package/src/{email → email_ims_notifications}/sendgrid.tsx +0 -0
|
@@ -5,7 +5,7 @@ import { DatumStats, FastArchiveAppendable } from "./FastArchiveAppendable";
|
|
|
5
5
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
6
6
|
import { URLParam } from "../../library-components/URLParam";
|
|
7
7
|
import { TimeRangeSelector, getTimeRange } from "./TimeRangeSelector";
|
|
8
|
-
import { formatNumber, formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
8
|
+
import { formatNumber, formatTime, formatVeryNiceDateTime, formatDateTime } from "socket-function/src/formatting/format";
|
|
9
9
|
import { list, sort, throttleFunction, timeInHour } from "socket-function/src/misc";
|
|
10
10
|
import { css } from "typesafecss";
|
|
11
11
|
import { logErrors } from "../../errors";
|
|
@@ -18,15 +18,16 @@ import { LOG_LIMIT_FLAG } from "./diskLogger";
|
|
|
18
18
|
import { MaybePromise, canHaveChildren } from "socket-function/src/types";
|
|
19
19
|
import { niceParse } from "../../niceStringify";
|
|
20
20
|
import { FileMetadata } from "./FastArchiveController";
|
|
21
|
+
import { throttleRender } from "../../functional/throttleRender";
|
|
21
22
|
|
|
22
23
|
const RENDER_INTERVAL = 1000;
|
|
23
24
|
|
|
24
25
|
const HISTOGRAM_RERENDER_INTERVAL = 10000;
|
|
25
26
|
|
|
26
27
|
export const filterParam = new URLParam("filter", "");
|
|
27
|
-
const cacheBustParam = new URLParam("cacheBust", 0);
|
|
28
|
+
export const cacheBustParam = new URLParam("cacheBust", 0);
|
|
28
29
|
const caseInsensitiveParam = new URLParam("caseInsensitive", false);
|
|
29
|
-
|
|
30
|
+
const hideAllDataParam = new URLParam("hideAllData", false);
|
|
30
31
|
|
|
31
32
|
export class FastArchiveViewer<T> extends qreact.Component<{
|
|
32
33
|
fastArchives: FastArchiveAppendable<T>[];
|
|
@@ -49,6 +50,14 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
49
50
|
error: t.atomic<string | undefined>(undefined),
|
|
50
51
|
pendingSyncInitializations: t.atomic<number>(0),
|
|
51
52
|
|
|
53
|
+
// Current sync parameters - set before synchronizeData is called
|
|
54
|
+
currentSyncParams: t.atomic<{
|
|
55
|
+
filterString: string;
|
|
56
|
+
startTime: number;
|
|
57
|
+
endTime: number;
|
|
58
|
+
fastArchivePaths: string[];
|
|
59
|
+
} | undefined>(undefined),
|
|
60
|
+
|
|
52
61
|
renderSeqNum: t.atomic<number>(0),
|
|
53
62
|
});
|
|
54
63
|
|
|
@@ -138,6 +147,15 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
138
147
|
|
|
139
148
|
let scannedValueCount = 0;
|
|
140
149
|
|
|
150
|
+
// Store current sync parameters BEFORE calling synchronizeData
|
|
151
|
+
ifLatest(() => {
|
|
152
|
+
this.state.currentSyncParams = {
|
|
153
|
+
filterString,
|
|
154
|
+
startTime: timeRange.startTime,
|
|
155
|
+
endTime: timeRange.endTime,
|
|
156
|
+
fastArchivePaths: fastArchives.map(archive => archive.rootPath),
|
|
157
|
+
};
|
|
158
|
+
});
|
|
141
159
|
|
|
142
160
|
ifLatest(() => {
|
|
143
161
|
this.state.error = undefined;
|
|
@@ -413,6 +431,39 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
413
431
|
}
|
|
414
432
|
sort(progressEntries, x => x[1].initialTime);
|
|
415
433
|
|
|
434
|
+
// Group progress bars by the text after the pipe sign
|
|
435
|
+
const groupedProgress = new Map<string, {
|
|
436
|
+
displayName: string;
|
|
437
|
+
entries: Array<[string, { section: string, value: number, max: number, initialTime: number, lastSetTime: number }]>;
|
|
438
|
+
totalValue: number;
|
|
439
|
+
totalMax: number;
|
|
440
|
+
earliestTime: number;
|
|
441
|
+
latestTime: number;
|
|
442
|
+
}>();
|
|
443
|
+
|
|
444
|
+
for (const [key, progress] of progressEntries) {
|
|
445
|
+
const pipeIndex = progress.section.indexOf("|");
|
|
446
|
+
const displayName = pipeIndex >= 0 ? progress.section.substring(pipeIndex + 1) : progress.section;
|
|
447
|
+
|
|
448
|
+
if (!groupedProgress.has(displayName)) {
|
|
449
|
+
groupedProgress.set(displayName, {
|
|
450
|
+
displayName,
|
|
451
|
+
entries: [],
|
|
452
|
+
totalValue: 0,
|
|
453
|
+
totalMax: 0,
|
|
454
|
+
earliestTime: progress.initialTime,
|
|
455
|
+
latestTime: progress.lastSetTime,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const group = groupedProgress.get(displayName)!;
|
|
460
|
+
group.entries.push([key, progress]);
|
|
461
|
+
group.totalValue += progress.value;
|
|
462
|
+
group.totalMax += progress.max;
|
|
463
|
+
group.earliestTime = Math.min(group.earliestTime, progress.initialTime);
|
|
464
|
+
group.latestTime = Math.max(group.latestTime, progress.lastSetTime);
|
|
465
|
+
}
|
|
466
|
+
|
|
416
467
|
return (
|
|
417
468
|
<div className={css.hbox(10).wrap.alignItems("start").fillWidth}>
|
|
418
469
|
{!this.state.finished && (
|
|
@@ -425,33 +476,41 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
425
476
|
Cancel
|
|
426
477
|
</div>
|
|
427
478
|
)}
|
|
428
|
-
{
|
|
429
|
-
const fraction = clamp(
|
|
479
|
+
{Array.from(groupedProgress.values()).map((group) => {
|
|
480
|
+
const fraction = clamp(group.totalMax > 0 ? (group.totalValue / group.totalMax) * 1 : 1, 0, 1);
|
|
430
481
|
let progressTime = 0;
|
|
431
|
-
if (
|
|
432
|
-
progressTime =
|
|
482
|
+
if (group.totalValue >= group.totalMax) {
|
|
483
|
+
progressTime = group.latestTime;
|
|
433
484
|
} else {
|
|
434
485
|
progressTime = getNowTime();
|
|
435
486
|
}
|
|
436
|
-
const elapsedTime = progressTime -
|
|
487
|
+
const elapsedTime = progressTime - group.earliestTime;
|
|
488
|
+
|
|
489
|
+
// Create tooltip showing breakdown by original prefix
|
|
490
|
+
const tooltipContent = group.entries.map(([key, progress]) => {
|
|
491
|
+
const pipeIndex = progress.section.indexOf("|");
|
|
492
|
+
const prefix = pipeIndex >= 0 ? progress.section.substring(0, pipeIndex) : key;
|
|
493
|
+
return `${prefix}: ${formatNumber(progress.value)}/${formatNumber(progress.max)}`;
|
|
494
|
+
}).join("\n");
|
|
437
495
|
|
|
438
496
|
return (
|
|
439
|
-
<div key={
|
|
497
|
+
<div key={group.displayName} className={
|
|
440
498
|
// NOTE: There is no point making this proportionally to the time, because we stream the data, so the time overlaps, making everything take most of the time.
|
|
441
499
|
css.vbox(4)
|
|
442
|
-
}
|
|
500
|
+
}
|
|
501
|
+
title={tooltipContent}
|
|
502
|
+
>
|
|
443
503
|
<div className={css.vbox(2)}>
|
|
444
504
|
<div className={css.fontSize(14).colorhsl(0, 0, 20)}>
|
|
445
|
-
{
|
|
505
|
+
{group.displayName}
|
|
446
506
|
</div>
|
|
447
507
|
<div className={css.fontSize(12).colorhsl(0, 0, 40).width(150)}>
|
|
448
|
-
{formatNumber(
|
|
508
|
+
{formatNumber(group.totalValue)} {group.totalValue < group.totalMax && `/ ${formatNumber(group.totalMax)}`} ({formatTime(elapsedTime)})
|
|
449
509
|
</div>
|
|
450
510
|
</div>
|
|
451
511
|
<div className={css.fillWidth.height(8).bord2(200, 20, 80).hsl(200, 10, 95)}>
|
|
452
512
|
<div
|
|
453
|
-
className={css.height(8).hsl(200, 50, 70).transition("width 200ms")}
|
|
454
|
-
style={{ width: `${fraction * 100}%` }}
|
|
513
|
+
className={css.height(8).hsl(200, 50, 70).width(`${fraction * 100}%`).transition("width 200ms")}
|
|
455
514
|
/>
|
|
456
515
|
</div>
|
|
457
516
|
</div>
|
|
@@ -471,6 +530,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
471
530
|
let countsArray = Array.from(this.histogramAllDataCounts);
|
|
472
531
|
let selectedArray = Array.from(this.histogramSelectedDataCounts);
|
|
473
532
|
const maxAllCount = countsArray.reduce((a, b) => Math.max(a, b), 0);
|
|
533
|
+
const maxSelectedCount = selectedArray.reduce((a, b) => Math.max(a, b), 0);
|
|
534
|
+
const maxCountForScale = hideAllDataParam.value ? maxSelectedCount : maxAllCount;
|
|
474
535
|
const selected = css.hsla(120, 40, 50, 1);
|
|
475
536
|
const all = css.hsla(200, 40, 70, 1);
|
|
476
537
|
|
|
@@ -489,17 +550,19 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
489
550
|
className={css.relative.fillHeight.fillWidth.filter("brightness(1.1)", "hover")}
|
|
490
551
|
title={`${formatNumber(selectedCount)} filtered (${formatNumber(selectedSize)}B)\n${formatNumber(count)} all (${formatNumber(allSize)}B)\n${formatVeryNiceDateTime(this.histogramStartTime + index * this.histogramBucketTime)}`}
|
|
491
552
|
>
|
|
492
|
-
{count > 0 && <div
|
|
553
|
+
{count > 0 && !hideAllDataParam.value && <div
|
|
493
554
|
className={
|
|
494
555
|
all.absolute.bottom(0).left(0).right(1)
|
|
495
|
-
.height
|
|
556
|
+
.transition("height 200ms")
|
|
557
|
+
.height(`calc(max(3px, ${(count / maxCountForScale) * 100}%))`)
|
|
496
558
|
}
|
|
497
559
|
/>}
|
|
498
560
|
|
|
499
561
|
{selectedCount > 0 && <div
|
|
500
562
|
className={
|
|
501
563
|
selected.absolute.bottom(0).left(0).right(1)
|
|
502
|
-
.height
|
|
564
|
+
.transition("height 200ms")
|
|
565
|
+
.height(`calc(max(3px, ${(selectedCount / maxCountForScale) * 100}%))`)
|
|
503
566
|
}
|
|
504
567
|
/>}
|
|
505
568
|
</div>
|
|
@@ -507,19 +570,71 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
507
570
|
})}
|
|
508
571
|
</div>
|
|
509
572
|
<div className={css.hbox(20).fontSize(12)}>
|
|
510
|
-
|
|
511
|
-
<div className={
|
|
512
|
-
|
|
513
|
-
|
|
573
|
+
{!hideAllDataParam.value && (
|
|
574
|
+
<div className={css.hbox(5)}>
|
|
575
|
+
<div className={all.size(16, 16)} />
|
|
576
|
+
<span>All data (max {formatNumber(maxAllCount)} | {formatNumber(countsArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.allSize)}B)</span>
|
|
577
|
+
</div>
|
|
578
|
+
)}
|
|
514
579
|
<div className={css.hbox(5)}>
|
|
515
580
|
<div className={selected.size(16, 16)} />
|
|
516
|
-
<span>Filtered data ({formatNumber(selectedArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.matchedSize)}B)</span>
|
|
581
|
+
<span>Filtered data (max {formatNumber(maxSelectedCount)} | {formatNumber(selectedArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.matchedSize)}B)</span>
|
|
517
582
|
</div>
|
|
583
|
+
<InputLabelURL
|
|
584
|
+
label="Only show filtered data"
|
|
585
|
+
checkbox
|
|
586
|
+
url={hideAllDataParam}
|
|
587
|
+
flavor="small"
|
|
588
|
+
/>
|
|
518
589
|
</div>
|
|
519
590
|
</div>
|
|
520
591
|
);
|
|
521
592
|
}
|
|
522
593
|
|
|
594
|
+
private getOutdatedInfo(): string[] {
|
|
595
|
+
const currentTimeRange = getTimeRange();
|
|
596
|
+
const currentFilterString = filterParam.value;
|
|
597
|
+
const currentArchivePaths = this.props.fastArchives.map(archive => archive.rootPath);
|
|
598
|
+
|
|
599
|
+
// Get stored sync parameters
|
|
600
|
+
const storedParams = this.state.currentSyncParams;
|
|
601
|
+
|
|
602
|
+
// If we have no stored params yet, not outdated (first run or loading)
|
|
603
|
+
if (!storedParams) {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
let warnings: string[] = [];
|
|
608
|
+
|
|
609
|
+
// Check for parameter differences
|
|
610
|
+
if (storedParams.filterString !== currentFilterString) {
|
|
611
|
+
warnings.push("filter");
|
|
612
|
+
}
|
|
613
|
+
if (storedParams.startTime !== currentTimeRange.startTime) {
|
|
614
|
+
warnings.push("start time");
|
|
615
|
+
}
|
|
616
|
+
if (storedParams.endTime !== currentTimeRange.endTime) {
|
|
617
|
+
warnings.push("end time");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Check for missing and extra archives
|
|
621
|
+
const currentArchivePathsSet = new Set(currentArchivePaths);
|
|
622
|
+
const storedArchivePathsSet = new Set(storedParams.fastArchivePaths);
|
|
623
|
+
|
|
624
|
+
for (let storedPath of storedParams.fastArchivePaths) {
|
|
625
|
+
if (!currentArchivePathsSet.has(storedPath)) {
|
|
626
|
+
warnings.push(`Missing ${storedPath}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
for (let currentPath of currentArchivePaths) {
|
|
630
|
+
if (!storedArchivePathsSet.has(currentPath)) {
|
|
631
|
+
warnings.push(`Extra ${currentPath}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return warnings;
|
|
636
|
+
}
|
|
637
|
+
|
|
523
638
|
public handleDownload = throttleFunction(500, () => {
|
|
524
639
|
console.log("handleDownload");
|
|
525
640
|
Querysub.onCommitFinished(() => {
|
|
@@ -527,6 +642,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
527
642
|
});
|
|
528
643
|
});
|
|
529
644
|
render() {
|
|
645
|
+
if (throttleRender({ key: "FastArchiveViewer", frameDelay: 30 })) return undefined;
|
|
646
|
+
|
|
530
647
|
let totalFileCount = 0;
|
|
531
648
|
let totalBackblazeByteCount = 0;
|
|
532
649
|
let totalLocalByteCount = 0;
|
|
@@ -550,6 +667,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
550
667
|
|
|
551
668
|
const infoDisplay = (hue: number) => css.pad2(12).bord2(hue, 50, 50).hsl(hue, 50, 95).colorhsl(hue, 50, 40);
|
|
552
669
|
|
|
670
|
+
|
|
671
|
+
|
|
553
672
|
return (
|
|
554
673
|
<div className={css.vbox(20).pad2(20).fillBoth}>
|
|
555
674
|
<div className={css.hbox(20).fillWidth}>
|
|
@@ -594,32 +713,71 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
594
713
|
flavor="small"
|
|
595
714
|
/>
|
|
596
715
|
<div className={css.vbox(10)}>
|
|
597
|
-
{this.state.runCount > 0 && (
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
716
|
+
{this.state.runCount > 0 && (() => {
|
|
717
|
+
const outdatedWarnings = this.getOutdatedInfo();
|
|
718
|
+
return (
|
|
719
|
+
<div className={css.hbox(10).wrap}>
|
|
720
|
+
<div
|
|
721
|
+
className={infoDisplay(120)}
|
|
722
|
+
title={this.state.fileMetadata.map(x => x?.files || []).flat().map(x =>
|
|
723
|
+
`${x.path} (${formatNumber(x.size)})`
|
|
724
|
+
).join("\n")}
|
|
725
|
+
>
|
|
726
|
+
File count: {formatNumber(totalFileCount)}, Backblaze size: {formatNumber(totalBackblazeByteCount)}B (compressed), Disk size: {formatNumber(totalLocalByteCount)}B (uncompressed)
|
|
727
|
+
</div>
|
|
728
|
+
{outdatedWarnings.length > 0 && (
|
|
729
|
+
<div
|
|
730
|
+
className={infoDisplay(30).button}
|
|
731
|
+
onClick={() => {
|
|
732
|
+
void this.handleDownload();
|
|
733
|
+
}}
|
|
734
|
+
title={outdatedWarnings.join(", ")}
|
|
735
|
+
>
|
|
736
|
+
<div className={css.vbox(4)}>
|
|
737
|
+
<div className={css.boldStyle}>Search parameters outdated - Click to run updated search. Outdated:</div>
|
|
738
|
+
<div className={css.fontSize(12)}>
|
|
739
|
+
{outdatedWarnings.join(" | ")}
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
)}
|
|
744
|
+
</div>
|
|
745
|
+
);
|
|
746
|
+
})()}
|
|
607
747
|
{this.state.runCount === 0 && (
|
|
608
|
-
<div className={infoDisplay(200)}
|
|
609
|
-
|
|
748
|
+
<div className={infoDisplay(200).button} onClick={() => {
|
|
749
|
+
void this.handleDownload();
|
|
750
|
+
}}>
|
|
751
|
+
No data downloaded yet. Click here to download data.
|
|
610
752
|
</div>
|
|
611
753
|
)}
|
|
612
|
-
{this.state.finished &&
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
754
|
+
{this.state.finished && (() => {
|
|
755
|
+
if (readLocalTimes.length === 0) return null;
|
|
756
|
+
|
|
757
|
+
const timeRange = getTimeRange();
|
|
758
|
+
const earliestTime = Math.min(...readLocalTimes);
|
|
759
|
+
|
|
760
|
+
if (earliestTime >= timeRange.endTime) return null;
|
|
761
|
+
|
|
762
|
+
return (
|
|
763
|
+
<div
|
|
764
|
+
className={infoDisplay(0).button}
|
|
765
|
+
onClick={() => {
|
|
766
|
+
Querysub.commit(() => {
|
|
767
|
+
cacheBustParam.value = Date.now();
|
|
768
|
+
});
|
|
769
|
+
void this.handleDownload();
|
|
770
|
+
}}
|
|
771
|
+
>
|
|
772
|
+
<div className={css.hbox(8).alignItems("center").wrap}>
|
|
773
|
+
<span>Future data has been read</span>
|
|
774
|
+
<span className={css.fontSize(18).boldStyle}>{formatTime(timeRange.endTime - earliestTime)} into the future</span>
|
|
775
|
+
<span>({formatDateTime(earliestTime)})</span>
|
|
776
|
+
<span>Clear here to load possibly missing data data.</span>
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
);
|
|
780
|
+
})()}
|
|
623
781
|
{!this.state.finished && <LoaderAurora />}
|
|
624
782
|
{this.limitedScanCount > 0 && (
|
|
625
783
|
<div className={infoDisplay(60).boldStyle.button}
|
|
@@ -628,7 +786,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
628
786
|
void this.handleDownload();
|
|
629
787
|
}}
|
|
630
788
|
>
|
|
631
|
-
Click here to see {formatNumber(this.limitedScanCount)} scanned logs were rate limited at a file level.
|
|
789
|
+
Click here to see {formatNumber(this.limitedScanCount)} scanned logs were rate limited at a file level. This means lines might be missing. If this happens a lot, use pass {`{ [LOG_LINE_LIMIT_ID]: "INSERT RANDOM GUID HERE" }`} in the error/warn message to limit only the spamming line.
|
|
632
790
|
</div>
|
|
633
791
|
)}
|
|
634
792
|
{this.limitedMatchCount > 0 && filterParam.value && (
|
|
@@ -638,7 +796,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
638
796
|
void this.handleDownload();
|
|
639
797
|
}}
|
|
640
798
|
>
|
|
641
|
-
Click here to see {formatNumber(this.limitedMatchCount)} matched logs were rate limited at a file level.
|
|
799
|
+
Click here to see {formatNumber(this.limitedMatchCount)} matched logs were rate limited at a file level. This means lines might be missing. If this happens a lot, use pass {`{ [LOG_LINE_LIMIT_ID]: "INSERT RANDOM GUID HERE" }`} in the error/warn message to limit only the spamming line.
|
|
642
800
|
</div>
|
|
643
801
|
)}
|
|
644
802
|
{this.state.pendingSyncInitializations > 0 && (
|
|
@@ -665,7 +823,6 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
665
823
|
}
|
|
666
824
|
}
|
|
667
825
|
|
|
668
|
-
|
|
669
826
|
class LoaderAurora extends qreact.Component {
|
|
670
827
|
render() {
|
|
671
828
|
return (
|
|
@@ -4,7 +4,7 @@ import { css } from "../../4-dom/css";
|
|
|
4
4
|
import { URLParam } from "../../library-components/URLParam";
|
|
5
5
|
import { InputLabelURL } from "../../library-components/InputLabel";
|
|
6
6
|
import { Button } from "../../library-components/Button";
|
|
7
|
-
import { formatDateTime, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
7
|
+
import { formatDateTime, formatDateTimeDetailed, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
8
8
|
import { TimeRangeSelector, endTimeParam, getTimeRange, startTimeParam } from "./TimeRangeSelector";
|
|
9
9
|
import { FastArchiveAppendable } from "./FastArchiveAppendable";
|
|
10
10
|
import { t } from "../../2-proxy/schema2";
|
|
@@ -13,8 +13,8 @@ import { logErrors } from "../../errors";
|
|
|
13
13
|
import { batchFunction, runInSerial } from "socket-function/src/batching";
|
|
14
14
|
import { Querysub } from "../../4-querysub/QuerysubController";
|
|
15
15
|
import { sort, timeInDay, timeInHour } from "socket-function/src/misc";
|
|
16
|
-
import { FastArchiveViewer, filterParam } from "./FastArchiveViewer";
|
|
17
|
-
import { LogDatum, getLoggers, LOG_LIMIT_FLAG } from "./diskLogger";
|
|
16
|
+
import { FastArchiveViewer, cacheBustParam, filterParam } from "./FastArchiveViewer";
|
|
17
|
+
import { LogDatum, getLoggers, LOG_LIMIT_FLAG, LOG_LINE_LIMIT_FLAG } from "./diskLogger";
|
|
18
18
|
import { ColumnType, Table, TableType } from "../../5-diagnostics/Table";
|
|
19
19
|
import { formatDateJSX } from "../../misc/formatJSX";
|
|
20
20
|
import { InputPicker } from "../../library-components/InputPicker";
|
|
@@ -24,8 +24,10 @@ import { ObjectDisplay } from "./ObjectDisplay";
|
|
|
24
24
|
import { endTime } from "../misc-pages/archiveViewerShared";
|
|
25
25
|
import { ErrorSuppressionUI } from "./errorNotifications/ErrorSuppressionUI";
|
|
26
26
|
import { FileMetadata } from "./FastArchiveController";
|
|
27
|
-
import { SuppressionListController, getSuppressEntryChecker, getSuppressionFull } from "./errorNotifications/ErrorNotificationController";
|
|
27
|
+
import { RecentErrorsController, SuppressionListController, getSuppressEntryChecker, getSuppressionFull } from "./errorNotifications/ErrorNotificationController";
|
|
28
28
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
29
|
+
import { throttleRender } from "../../functional/throttleRender";
|
|
30
|
+
import { isNode } from "typesafecss";
|
|
29
31
|
|
|
30
32
|
const RENDER_INTERVAL = 1000;
|
|
31
33
|
|
|
@@ -36,6 +38,7 @@ const enableInfosURL = new URLParam("enableInfos", true);
|
|
|
36
38
|
const enableWarningsURL = new URLParam("enableWarnings", true);
|
|
37
39
|
const enableErrorsURL = new URLParam("enableErrors", true);
|
|
38
40
|
const selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
|
|
41
|
+
const useRelativeTimeURL = new URLParam("useRelativeTime", true);
|
|
39
42
|
|
|
40
43
|
export const errorNotifyToggleURL = new URLParam("errorNotifyToggle", false);
|
|
41
44
|
|
|
@@ -70,10 +73,12 @@ export class LogViewer2 extends qreact.Component {
|
|
|
70
73
|
private fastArchiveViewer: FastArchiveViewer<LogDatum> | undefined = undefined;
|
|
71
74
|
|
|
72
75
|
rerun() {
|
|
76
|
+
cacheBustParam.value = Date.now();
|
|
73
77
|
void this.fastArchiveViewer?.handleDownload();
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
render() {
|
|
81
|
+
if (throttleRender({ key: "LogViewer2", frameDelay: 30 })) return undefined;
|
|
77
82
|
|
|
78
83
|
this.state.datumsSeqNum;
|
|
79
84
|
|
|
@@ -119,11 +124,17 @@ export class LogViewer2 extends qreact.Component {
|
|
|
119
124
|
let now = Date.now();
|
|
120
125
|
startTimeParam.value = now - timeInDay * 7;
|
|
121
126
|
endTimeParam.value = now + timeInHour * 2;
|
|
122
|
-
filterParam.value = "";
|
|
123
127
|
}
|
|
124
128
|
this.rerun();
|
|
125
129
|
}}
|
|
126
130
|
/>
|
|
131
|
+
{errorNotifyToggleURL.value && <div className={css.hbox(10)}>
|
|
132
|
+
<Button onClick={() => {
|
|
133
|
+
void RecentErrorsController(SocketFunction.browserNodeId()).raiseTestError.promise("Test error notification");
|
|
134
|
+
}}>
|
|
135
|
+
Test Error Notification (won't rerun search, but it should show up in the title)
|
|
136
|
+
</Button>
|
|
137
|
+
</div>}
|
|
127
138
|
</div>
|
|
128
139
|
|
|
129
140
|
{!errorNotifyToggleURL.value && <div className={css.hbox(10)}>
|
|
@@ -242,6 +253,13 @@ export class LogViewer2 extends qreact.Component {
|
|
|
242
253
|
anyLimited = true;
|
|
243
254
|
}
|
|
244
255
|
}
|
|
256
|
+
let lineLimited = false;
|
|
257
|
+
for (let i = 0; i < Math.min(1000, this.datums.length); i++) {
|
|
258
|
+
let datum = this.datums[i];
|
|
259
|
+
if (datum[LOG_LINE_LIMIT_FLAG]) {
|
|
260
|
+
lineLimited = true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
245
263
|
let selectedFields: string[] = [];
|
|
246
264
|
for (let field of Object.keys(defaultSelectedFields)) {
|
|
247
265
|
if (atomic(selectedFieldsURL.value[field]) === undefined) {
|
|
@@ -265,15 +283,25 @@ export class LogViewer2 extends qreact.Component {
|
|
|
265
283
|
if (anyLimited) {
|
|
266
284
|
columns[LOG_LIMIT_FLAG] = {
|
|
267
285
|
title: "Limited",
|
|
268
|
-
formatter: x => x && <div className={css.hsl(0, 50, 50).colorhsl(0, 50, 95).boldStyle.pad2(10).ellipsis}
|
|
269
|
-
|
|
286
|
+
formatter: x => x && <div className={css.hsl(0, 50, 50).colorhsl(0, 50, 95).boldStyle.pad2(10).ellipsis}
|
|
287
|
+
title="File throttled (other line may be lost lost!)">
|
|
288
|
+
FILE throttled
|
|
289
|
+
</div> || undefined
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (lineLimited) {
|
|
293
|
+
columns[LOG_LINE_LIMIT_FLAG] = {
|
|
294
|
+
title: "Limited",
|
|
295
|
+
formatter: x => x && <div className={css.hsl(60, 50, 50).colorhsl(60, 50, 95).boldStyle.pad2(10).ellipsis}
|
|
296
|
+
title="Line throttled (no lines lost, just count for this line is lower than the actual count)">
|
|
297
|
+
Line throttled
|
|
270
298
|
</div> || undefined
|
|
271
299
|
};
|
|
272
300
|
}
|
|
273
301
|
for (let field of selectedFields) {
|
|
274
302
|
let column: ColumnType<unknown, LogDatum> = {};
|
|
275
303
|
if (field === "time") {
|
|
276
|
-
column.formatter = (x: unknown) => formatDateTime(Number(x))
|
|
304
|
+
column.formatter = (x: unknown) => useRelativeTimeURL.value ? formatDateJSX(Number(x)) : <span title={formatDateTimeDetailed(Number(x))}>{formatDateTime(Number(x))}</span>;
|
|
277
305
|
}
|
|
278
306
|
if (!column.formatter) {
|
|
279
307
|
column.formatter = (x: unknown) => <ObjectDisplay value={x} />;
|
|
@@ -302,38 +330,54 @@ export class LogViewer2 extends qreact.Component {
|
|
|
302
330
|
}
|
|
303
331
|
};
|
|
304
332
|
return <>
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
<div className={css.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
<div className={css.hbox(10)}>
|
|
334
|
+
<InputPicker
|
|
335
|
+
label={<div className={css.hbox(10)}>
|
|
336
|
+
<div className={css.flexShrink0}>
|
|
337
|
+
Selected Fields
|
|
338
|
+
</div>
|
|
339
|
+
<Button onClick={() => {
|
|
340
|
+
let newValues = { ...selectedFieldsURL.value };
|
|
341
|
+
for (let key of Object.keys(newValues)) {
|
|
342
|
+
newValues[key] = key in defaultSelectedFields;
|
|
343
|
+
}
|
|
344
|
+
selectedFieldsURL.value = newValues;
|
|
345
|
+
}}>
|
|
346
|
+
Reset
|
|
347
|
+
</Button>
|
|
348
|
+
</div>}
|
|
349
|
+
picked={selectedFields}
|
|
350
|
+
options={Array.from(fieldNames).map(x => ({ value: x, label: x }))}
|
|
351
|
+
addPicked={x => {
|
|
311
352
|
let newValues = { ...selectedFieldsURL.value };
|
|
312
|
-
|
|
313
|
-
newValues[key] = key in defaultSelectedFields;
|
|
314
|
-
}
|
|
353
|
+
newValues[x] = true;
|
|
315
354
|
selectedFieldsURL.value = newValues;
|
|
316
|
-
}}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
355
|
+
}}
|
|
356
|
+
removePicked={x => {
|
|
357
|
+
let newValues = { ...selectedFieldsURL.value };
|
|
358
|
+
newValues[x] = false;
|
|
359
|
+
selectedFieldsURL.value = newValues;
|
|
360
|
+
}}
|
|
361
|
+
/>
|
|
362
|
+
<InputLabelURL
|
|
363
|
+
label="Use Relative Time"
|
|
364
|
+
checkbox
|
|
365
|
+
url={useRelativeTimeURL}
|
|
366
|
+
onChangeValue={() => {
|
|
367
|
+
Querysub.commit(() => {
|
|
368
|
+
this.state.datumsSeqNum++;
|
|
369
|
+
});
|
|
370
|
+
}}
|
|
371
|
+
/>
|
|
372
|
+
</div>
|
|
373
|
+
{this.datums.length === 0 && <div className={css.hsl(40, 50, 50).colorhsl(60, 50, 100).boldStyle.pad2(10).ellipsis}>
|
|
374
|
+
No logs matched, either increase the time range or decrease the filter specificity.
|
|
375
|
+
</div>}
|
|
333
376
|
<Table
|
|
334
377
|
rows={this.datums}
|
|
335
378
|
columns={columns}
|
|
336
379
|
lineLimit={4}
|
|
380
|
+
initialLimit={10}
|
|
337
381
|
characterLimit={400}
|
|
338
382
|
getRowAttributes={row => {
|
|
339
383
|
let hue = -1;
|
|
@@ -82,6 +82,14 @@ export class TimeRangeSelector extends qreact.Component {
|
|
|
82
82
|
>
|
|
83
83
|
Set to future data
|
|
84
84
|
</Button>
|
|
85
|
+
<Button
|
|
86
|
+
hue={110} onClick={() => {
|
|
87
|
+
startTimeParam.value = now - timeInHour;
|
|
88
|
+
endTimeParam.value = now + timeInHour * 2;
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
Set to last hour
|
|
92
|
+
</Button>
|
|
85
93
|
<Button
|
|
86
94
|
hue={110} onClick={() => {
|
|
87
95
|
startTimeParam.value = now - timeInDay;
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { lazy } from "socket-function/src/caching";
|
|
3
3
|
import { getExternalIP } from "socket-function/src/networking";
|
|
4
|
-
import { decodeNodeId } from "../../-a-auth/certs";
|
|
4
|
+
import { decodeNodeId, getOwnMachineId, getOwnThreadId } from "../../-a-auth/certs";
|
|
5
5
|
import { getOwnNodeId } from "../../-f-node-discovery/NodeDiscovery";
|
|
6
6
|
import { logErrors } from "../../errors";
|
|
7
7
|
import { addGlobalContext } from "./diskLogger";
|
|
8
8
|
import child_process from "child_process";
|
|
9
|
+
import { getNodeIdLocation } from "socket-function/src/nodeCache";
|
|
9
10
|
|
|
10
11
|
export function addBuiltInContext() {
|
|
11
12
|
addGlobalContext(() => {
|
|
12
13
|
let nodeId = getOwnNodeId();
|
|
13
|
-
let nodeParts =
|
|
14
|
+
let nodeParts = getNodeIdLocation(nodeId);
|
|
14
15
|
return {
|
|
15
|
-
__machineId:
|
|
16
|
+
__machineId: getOwnMachineId(),
|
|
16
17
|
__mountId: SocketFunction.mountedNodeId,
|
|
17
|
-
__threadId:
|
|
18
|
+
__threadId: getOwnThreadId(),
|
|
18
19
|
__port: nodeParts?.port,
|
|
19
20
|
__nodeId: nodeId,
|
|
20
21
|
__entry: process.argv[1],
|