querysub 0.326.0 → 0.328.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 (42) hide show
  1. package/package.json +3 -4
  2. package/src/-a-archives/archivesBackBlaze.ts +20 -0
  3. package/src/-a-archives/archivesDisk.ts +5 -5
  4. package/src/-a-archives/archivesLimitedCache.ts +118 -7
  5. package/src/-a-archives/archivesPrivateFileSystem.ts +3 -0
  6. package/src/-g-core-values/NodeCapabilities.ts +26 -11
  7. package/src/0-path-value-core/auditLogs.ts +4 -2
  8. package/src/2-proxy/PathValueProxyWatcher.ts +3 -0
  9. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  10. package/src/4-querysub/Querysub.ts +1 -1
  11. package/src/5-diagnostics/GenericFormat.tsx +2 -2
  12. package/src/deployManager/machineApplyMainCode.ts +10 -8
  13. package/src/deployManager/machineSchema.ts +4 -3
  14. package/src/deployManager/setupMachineMain.ts +3 -2
  15. package/src/diagnostics/logs/FastArchiveAppendable.ts +85 -59
  16. package/src/diagnostics/logs/FastArchiveController.ts +5 -2
  17. package/src/diagnostics/logs/FastArchiveViewer.tsx +222 -51
  18. package/src/diagnostics/logs/LogViewer2.tsx +83 -35
  19. package/src/diagnostics/logs/TimeRangeSelector.tsx +8 -0
  20. package/src/diagnostics/logs/diskLogGlobalContext.ts +3 -3
  21. package/src/diagnostics/logs/diskLogger.ts +70 -23
  22. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +111 -82
  23. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +37 -3
  24. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +52 -22
  25. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +8 -0
  26. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +198 -52
  27. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +3 -2
  28. package/src/diagnostics/managementPages.tsx +5 -0
  29. package/src/email_ims_notifications/discord.tsx +203 -0
  30. package/src/fs.ts +9 -0
  31. package/src/functional/SocketChannel.ts +9 -0
  32. package/src/functional/throttleRender.ts +134 -0
  33. package/src/library-components/ATag.tsx +2 -2
  34. package/src/library-components/SyncedController.ts +5 -3
  35. package/src/misc.ts +13 -0
  36. package/src/misc2.ts +54 -0
  37. package/src/user-implementation/SecurityPage.tsx +11 -5
  38. package/src/user-implementation/userData.ts +31 -16
  39. package/testEntry2.ts +14 -5
  40. package/src/user-implementation/setEmailKey.ts +0 -25
  41. /package/src/{email → email_ims_notifications}/postmark.tsx +0 -0
  42. /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";
@@ -15,22 +15,24 @@ import { ButtonSelector } from "../../library-components/ButtonSelector";
15
15
  import { Button } from "../../library-components/Button";
16
16
  import { lazy } from "socket-function/src/caching";
17
17
  import { LOG_LIMIT_FLAG } from "./diskLogger";
18
- import { canHaveChildren } from "socket-function/src/types";
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>[];
33
- onStart: () => void;
34
+ runOnLoad?: boolean;
35
+ onStart: () => MaybePromise<void>;
34
36
  getWantData?: (file: FileMetadata) => Promise<((posStart: number, posEnd: number, data: Buffer) => boolean) | undefined>;
35
37
  onDatums: (source: FastArchiveAppendable<T>, datums: T[], metadata: FileMetadata) => void;
36
38
  // Called after onData
@@ -38,15 +40,24 @@ export class FastArchiveViewer<T> extends qreact.Component<{
38
40
  onFinish?: () => void;
39
41
  }> {
40
42
  state = t.state({
43
+ runCount: t.atomic<number>(0),
41
44
  // rootPath =>
42
45
  fileMetadata: t.atomic<({
43
46
  files: FileMetadata[];
44
47
  createTime?: number;
45
48
  } | undefined)[]>([]),
46
- finished: t.atomic(false),
49
+ finished: t.atomic(true),
47
50
  error: t.atomic<string | undefined>(undefined),
48
51
  pendingSyncInitializations: t.atomic<number>(0),
49
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
+
50
61
  renderSeqNum: t.atomic<number>(0),
51
62
  });
52
63
 
@@ -108,6 +119,11 @@ export class FastArchiveViewer<T> extends qreact.Component<{
108
119
  this.latestSequenceNumber = this.currentSequenceNumber;
109
120
  const mySequenceNumber = this.currentSequenceNumber;
110
121
 
122
+ // Increment run count for each new run
123
+ Querysub.commit(() => {
124
+ this.state.runCount++;
125
+ });
126
+
111
127
  // Helper function to check if this sequence number is still the latest
112
128
  const isLatestSync = () => mySequenceNumber === this.latestSequenceNumber;
113
129
 
@@ -131,6 +147,15 @@ export class FastArchiveViewer<T> extends qreact.Component<{
131
147
 
132
148
  let scannedValueCount = 0;
133
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
+ });
134
159
 
135
160
  ifLatest(() => {
136
161
  this.state.error = undefined;
@@ -150,7 +175,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
150
175
 
151
176
  this.histogramStartTime = timeRange.startTime - this.histogramBucketTime * 2;
152
177
  this.histogramEndTime = timeRange.endTime + this.histogramBucketTime * 2;
153
- const bucketCount = Math.ceil((this.histogramEndTime - this.histogramStartTime) / this.histogramBucketTime);
178
+ const bucketCount = clamp(Math.ceil((this.histogramEndTime - this.histogramStartTime) / this.histogramBucketTime), 1, 10000);
154
179
 
155
180
  this.histogramAllDataCounts = new Float64Array(bucketCount);
156
181
  this.histogramSelectedDataCounts = new Float64Array(bucketCount);
@@ -181,9 +206,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
181
206
  }
182
207
  };
183
208
  try {
184
- ifLatest(() => {
185
- onStart();
186
- });
209
+ await onStart();
187
210
 
188
211
  const caseInsensitive = Querysub.fastRead(() => caseInsensitiveParam.value);
189
212
  let caseInsensitiveMapping = new Uint8Array(256);
@@ -408,6 +431,39 @@ export class FastArchiveViewer<T> extends qreact.Component<{
408
431
  }
409
432
  sort(progressEntries, x => x[1].initialTime);
410
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
+
411
467
  return (
412
468
  <div className={css.hbox(10).wrap.alignItems("start").fillWidth}>
413
469
  {!this.state.finished && (
@@ -420,33 +476,41 @@ export class FastArchiveViewer<T> extends qreact.Component<{
420
476
  Cancel
421
477
  </div>
422
478
  )}
423
- {progressEntries.map(([key, progress]) => {
424
- 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);
425
481
  let progressTime = 0;
426
- if (progress.value >= progress.max) {
427
- progressTime = progress.lastSetTime;
482
+ if (group.totalValue >= group.totalMax) {
483
+ progressTime = group.latestTime;
428
484
  } else {
429
485
  progressTime = getNowTime();
430
486
  }
431
- 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");
432
495
 
433
496
  return (
434
- <div key={key} className={
497
+ <div key={group.displayName} className={
435
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.
436
499
  css.vbox(4)
437
- }>
500
+ }
501
+ title={tooltipContent}
502
+ >
438
503
  <div className={css.vbox(2)}>
439
504
  <div className={css.fontSize(14).colorhsl(0, 0, 20)}>
440
- {progress.section}
505
+ {group.displayName}
441
506
  </div>
442
507
  <div className={css.fontSize(12).colorhsl(0, 0, 40).width(150)}>
443
- {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)})
444
509
  </div>
445
510
  </div>
446
511
  <div className={css.fillWidth.height(8).bord2(200, 20, 80).hsl(200, 10, 95)}>
447
512
  <div
448
- className={css.height(8).hsl(200, 50, 70).transition("width 200ms")}
449
- style={{ width: `${fraction * 100}%` }}
513
+ className={css.height(8).hsl(200, 50, 70).width(`${fraction * 100}%`).transition("width 200ms")}
450
514
  />
451
515
  </div>
452
516
  </div>
@@ -466,6 +530,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
466
530
  let countsArray = Array.from(this.histogramAllDataCounts);
467
531
  let selectedArray = Array.from(this.histogramSelectedDataCounts);
468
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;
469
535
  const selected = css.hsla(120, 40, 50, 1);
470
536
  const all = css.hsla(200, 40, 70, 1);
471
537
 
@@ -484,17 +550,19 @@ export class FastArchiveViewer<T> extends qreact.Component<{
484
550
  className={css.relative.fillHeight.fillWidth.filter("brightness(1.1)", "hover")}
485
551
  title={`${formatNumber(selectedCount)} filtered (${formatNumber(selectedSize)}B)\n${formatNumber(count)} all (${formatNumber(allSize)}B)\n${formatVeryNiceDateTime(this.histogramStartTime + index * this.histogramBucketTime)}`}
486
552
  >
487
- {count > 0 && <div
553
+ {count > 0 && !hideAllDataParam.value && <div
488
554
  className={
489
555
  all.absolute.bottom(0).left(0).right(1)
490
- .height(`calc(max(3px, ${(count / maxAllCount) * 100}%))`)
556
+ .transition("height 200ms")
557
+ .height(`calc(max(3px, ${(count / maxCountForScale) * 100}%))`)
491
558
  }
492
559
  />}
493
560
 
494
561
  {selectedCount > 0 && <div
495
562
  className={
496
563
  selected.absolute.bottom(0).left(0).right(1)
497
- .height(`calc(max(3px, ${(selectedCount / maxAllCount) * 100}%))`)
564
+ .transition("height 200ms")
565
+ .height(`calc(max(3px, ${(selectedCount / maxCountForScale) * 100}%))`)
498
566
  }
499
567
  />}
500
568
  </div>
@@ -502,19 +570,71 @@ export class FastArchiveViewer<T> extends qreact.Component<{
502
570
  })}
503
571
  </div>
504
572
  <div className={css.hbox(20).fontSize(12)}>
505
- <div className={css.hbox(5)}>
506
- <div className={all.size(16, 16)} />
507
- <span>All data ({formatNumber(countsArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.allSize)}B)</span>
508
- </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
+ )}
509
579
  <div className={css.hbox(5)}>
510
580
  <div className={selected.size(16, 16)} />
511
- <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>
512
582
  </div>
583
+ <InputLabelURL
584
+ label="Only show filtered data"
585
+ checkbox
586
+ url={hideAllDataParam}
587
+ flavor="small"
588
+ />
513
589
  </div>
514
590
  </div>
515
591
  );
516
592
  }
517
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
+
518
638
  public handleDownload = throttleFunction(500, () => {
519
639
  console.log("handleDownload");
520
640
  Querysub.onCommitFinished(() => {
@@ -522,6 +642,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
522
642
  });
523
643
  });
524
644
  render() {
645
+ if (throttleRender({ key: "FastArchiveViewer", frameDelay: 30 })) return undefined;
646
+
525
647
  let totalFileCount = 0;
526
648
  let totalBackblazeByteCount = 0;
527
649
  let totalLocalByteCount = 0;
@@ -545,6 +667,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
545
667
 
546
668
  const infoDisplay = (hue: number) => css.pad2(12).bord2(hue, 50, 50).hsl(hue, 50, 95).colorhsl(hue, 50, 40);
547
669
 
670
+
671
+
548
672
  return (
549
673
  <div className={css.vbox(20).pad2(20).fillBoth}>
550
674
  <div className={css.hbox(20).fillWidth}>
@@ -571,6 +695,11 @@ export class FastArchiveViewer<T> extends qreact.Component<{
571
695
  flavor="large"
572
696
  fillWidth
573
697
  onKeyUp={this.handleDownload}
698
+ ref2={() => {
699
+ if (this.props.runOnLoad) {
700
+ void this.handleDownload();
701
+ }
702
+ }}
574
703
  noEnterKeyBlur
575
704
  placeholder="Filter terms, ex x | y & z"
576
705
  />
@@ -584,28 +713,71 @@ export class FastArchiveViewer<T> extends qreact.Component<{
584
713
  flavor="small"
585
714
  />
586
715
  <div className={css.vbox(10)}>
587
- {this.state.fileMetadata
588
- && (
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
+ })()}
747
+ {this.state.runCount === 0 && (
748
+ <div className={infoDisplay(200).button} onClick={() => {
749
+ void this.handleDownload();
750
+ }}>
751
+ No data downloaded yet. Click here to download data.
752
+ </div>
753
+ )}
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 (
589
763
  <div
590
- className={infoDisplay(120)}
591
- title={this.state.fileMetadata.map(x => x?.files || []).flat().map(x =>
592
- `${x.path} (${formatNumber(x.size)})`
593
- ).join("\n")}
764
+ className={infoDisplay(0).button}
765
+ onClick={() => {
766
+ Querysub.commit(() => {
767
+ cacheBustParam.value = Date.now();
768
+ });
769
+ void this.handleDownload();
770
+ }}
594
771
  >
595
- File count: {formatNumber(totalFileCount)}, Backblaze size: {formatNumber(totalBackblazeByteCount)}B (compressed), Disk size: {formatNumber(totalLocalByteCount)}B (uncompressed)
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>
596
778
  </div>
597
- )}
598
- {this.state.finished && <div
599
- className={infoDisplay(60).button}
600
- onClick={() => {
601
- Querysub.commit(() => {
602
- cacheBustParam.value = Date.now();
603
- });
604
- void this.handleDownload();
605
- }}
606
- >
607
- Snapshot from dates: {readLocalTimes.map(x => formatVeryNiceDateTime(x)).join(" | ")}. Clear here to reload data.
608
- </div>}
779
+ );
780
+ })()}
609
781
  {!this.state.finished && <LoaderAurora />}
610
782
  {this.limitedScanCount > 0 && (
611
783
  <div className={infoDisplay(60).boldStyle.button}
@@ -614,7 +786,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
614
786
  void this.handleDownload();
615
787
  }}
616
788
  >
617
- 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.
618
790
  </div>
619
791
  )}
620
792
  {this.limitedMatchCount > 0 && filterParam.value && (
@@ -624,7 +796,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
624
796
  void this.handleDownload();
625
797
  }}
626
798
  >
627
- 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.
628
800
  </div>
629
801
  )}
630
802
  {this.state.pendingSyncInitializations > 0 && (
@@ -651,7 +823,6 @@ export class FastArchiveViewer<T> extends qreact.Component<{
651
823
  }
652
824
  }
653
825
 
654
-
655
826
  class LoaderAurora extends qreact.Component {
656
827
  render() {
657
828
  return (