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.
Files changed (50) hide show
  1. package/bin/error-email.js +8 -0
  2. package/bin/error-im.js +8 -0
  3. package/package.json +4 -3
  4. package/src/-a-archives/archivesBackBlaze.ts +20 -0
  5. package/src/-a-archives/archivesCborT.ts +52 -0
  6. package/src/-a-archives/archivesDisk.ts +5 -5
  7. package/src/-a-archives/archivesJSONT.ts +19 -5
  8. package/src/-a-archives/archivesLimitedCache.ts +118 -7
  9. package/src/-a-archives/archivesPrivateFileSystem.ts +3 -0
  10. package/src/-g-core-values/NodeCapabilities.ts +26 -11
  11. package/src/0-path-value-core/auditLogs.ts +4 -2
  12. package/src/2-proxy/PathValueProxyWatcher.ts +7 -0
  13. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  14. package/src/4-querysub/Querysub.ts +1 -1
  15. package/src/5-diagnostics/GenericFormat.tsx +2 -2
  16. package/src/config.ts +15 -3
  17. package/src/deployManager/machineApplyMainCode.ts +10 -8
  18. package/src/deployManager/machineSchema.ts +4 -3
  19. package/src/deployManager/setupMachineMain.ts +3 -2
  20. package/src/diagnostics/logs/FastArchiveAppendable.ts +86 -53
  21. package/src/diagnostics/logs/FastArchiveController.ts +11 -2
  22. package/src/diagnostics/logs/FastArchiveViewer.tsx +205 -48
  23. package/src/diagnostics/logs/LogViewer2.tsx +78 -34
  24. package/src/diagnostics/logs/TimeRangeSelector.tsx +8 -0
  25. package/src/diagnostics/logs/diskLogGlobalContext.ts +5 -4
  26. package/src/diagnostics/logs/diskLogger.ts +70 -23
  27. package/src/diagnostics/logs/errorNotifications/ErrorDigestPage.tsx +409 -0
  28. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +94 -67
  29. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +37 -3
  30. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +50 -16
  31. package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +174 -0
  32. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +291 -0
  33. package/src/diagnostics/logs/errorNotifications/errorLoopEntry.tsx +7 -0
  34. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +185 -68
  35. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +10 -19
  36. package/src/diagnostics/managementPages.tsx +33 -15
  37. package/src/email_ims_notifications/discord.tsx +203 -0
  38. package/src/{email → email_ims_notifications}/postmark.tsx +3 -3
  39. package/src/fs.ts +9 -0
  40. package/src/functional/SocketChannel.ts +9 -0
  41. package/src/functional/throttleRender.ts +134 -0
  42. package/src/library-components/ATag.tsx +2 -2
  43. package/src/library-components/SyncedController.ts +3 -3
  44. package/src/misc.ts +18 -0
  45. package/src/misc2.ts +106 -0
  46. package/src/user-implementation/SecurityPage.tsx +11 -5
  47. package/src/user-implementation/userData.ts +57 -23
  48. package/testEntry2.ts +14 -5
  49. package/src/user-implementation/setEmailKey.ts +0 -25
  50. /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
- {progressEntries.map(([key, progress]) => {
429
- const fraction = clamp(progress.max > 0 ? (progress.value / progress.max) * 1 : 1, 0, 1);
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 (progress.value >= progress.max) {
432
- progressTime = progress.lastSetTime;
482
+ if (group.totalValue >= group.totalMax) {
483
+ progressTime = group.latestTime;
433
484
  } else {
434
485
  progressTime = getNowTime();
435
486
  }
436
- const elapsedTime = progressTime - progress.initialTime;
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={key} className={
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
- {progress.section}
505
+ {group.displayName}
446
506
  </div>
447
507
  <div className={css.fontSize(12).colorhsl(0, 0, 40).width(150)}>
448
- {formatNumber(progress.value)} {progress.value < progress.max && `/ ${formatNumber(progress.max)}`} ({formatTime(elapsedTime)})
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(`calc(max(3px, ${(count / maxAllCount) * 100}%))`)
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(`calc(max(3px, ${(selectedCount / maxAllCount) * 100}%))`)
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
- <div className={css.hbox(5)}>
511
- <div className={all.size(16, 16)} />
512
- <span>All data ({formatNumber(countsArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.allSize)}B)</span>
513
- </div>
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
- <div
599
- className={infoDisplay(120)}
600
- title={this.state.fileMetadata.map(x => x?.files || []).flat().map(x =>
601
- `${x.path} (${formatNumber(x.size)})`
602
- ).join("\n")}
603
- >
604
- File count: {formatNumber(totalFileCount)}, Backblaze size: {formatNumber(totalBackblazeByteCount)}B (compressed), Disk size: {formatNumber(totalLocalByteCount)}B (uncompressed)
605
- </div>
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
- No data downloaded yet. Click Run to download data.
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 && <div
613
- className={infoDisplay(60).button}
614
- onClick={() => {
615
- Querysub.commit(() => {
616
- cacheBustParam.value = Date.now();
617
- });
618
- void this.handleDownload();
619
- }}
620
- >
621
- Snapshot from dates: {readLocalTimes.map(x => formatVeryNiceDateTime(x)).join(" | ")}. Clear here to reload data.
622
- </div>}
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. We do not track how many were ignored, there might be infinite missing logs.
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. We do not track how many were ignored, there might be infinite missing logs.
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
- Log Line Throttled
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
- <InputPicker
306
- label={<div className={css.hbox(10)}>
307
- <div className={css.flexShrink0}>
308
- Selected Fields
309
- </div>
310
- <Button onClick={() => {
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
- for (let key of Object.keys(newValues)) {
313
- newValues[key] = key in defaultSelectedFields;
314
- }
353
+ newValues[x] = true;
315
354
  selectedFieldsURL.value = newValues;
316
- }}>
317
- Reset
318
- </Button>
319
- </div>}
320
- picked={selectedFields}
321
- options={Array.from(fieldNames).map(x => ({ value: x, label: x }))}
322
- addPicked={x => {
323
- let newValues = { ...selectedFieldsURL.value };
324
- newValues[x] = true;
325
- selectedFieldsURL.value = newValues;
326
- }}
327
- removePicked={x => {
328
- let newValues = { ...selectedFieldsURL.value };
329
- newValues[x] = false;
330
- selectedFieldsURL.value = newValues;
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 = decodeNodeId(nodeId);
14
+ let nodeParts = getNodeIdLocation(nodeId);
14
15
  return {
15
- __machineId: nodeParts?.machineId,
16
+ __machineId: getOwnMachineId(),
16
17
  __mountId: SocketFunction.mountedNodeId,
17
- __threadId: nodeParts?.threadId,
18
+ __threadId: getOwnThreadId(),
18
19
  __port: nodeParts?.port,
19
20
  __nodeId: nodeId,
20
21
  __entry: process.argv[1],