querysub 0.461.0 → 0.463.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 (95) hide show
  1. package/package.json +1 -1
  2. package/src/-d-trust/NetworkTrust2.ts +1 -1
  3. package/src/0-path-value-core/LockWatcher2.ts +2 -5
  4. package/src/0-path-value-core/PathRouter.ts +2 -3
  5. package/src/0-path-value-core/PathRouterConstants.ts +4 -0
  6. package/src/0-path-value-core/PathValueCommitter.ts +0 -1
  7. package/src/0-path-value-core/PathValueController.ts +1 -1
  8. package/src/0-path-value-core/PathWatcher.ts +14 -5
  9. package/src/0-path-value-core/ValidStateComputer.ts +234 -86
  10. package/src/0-path-value-core/pathValueCore.ts +57 -78
  11. package/src/1-path-client/RemoteWatcher.ts +4 -3
  12. package/src/1-path-client/pathValueClientWatcher.ts +28 -2
  13. package/src/2-proxy/PathValueProxyWatcher.ts +29 -24
  14. package/src/2-proxy/TransactionDelayer.ts +44 -22
  15. package/src/2-proxy/archiveMoveHarness.ts +2 -2
  16. package/src/3-path-functions/PathFunctionRunner.ts +30 -22
  17. package/src/3-path-functions/PathFunctionRunnerMain.ts +1 -3
  18. package/src/4-deploy/deployFunctions.ts +1 -1
  19. package/src/4-deploy/deployMain.ts +1 -1
  20. package/src/4-deploy/edgeClientWatcher.tsx +1 -1
  21. package/src/4-deploy/edgeNodes.ts +1 -1
  22. package/src/4-dom/qreactTest.tsx +1 -1
  23. package/src/4-querysub/Querysub.ts +8 -9
  24. package/src/4-querysub/QuerysubController.ts +19 -1
  25. package/src/4-querysub/permissions.ts +1 -1
  26. package/src/4-querysub/predictionQueue.tsx +1 -1
  27. package/src/4-querysub/querysubPrediction.ts +25 -12
  28. package/src/5-diagnostics/GenericFormat.tsx +1 -1
  29. package/src/5-diagnostics/qreactDebug.tsx +2 -2
  30. package/src/archiveapps/archiveGCEntry.tsx +1 -1
  31. package/src/archiveapps/archiveJoinEntry.ts +3 -3
  32. package/src/config.ts +5 -1
  33. package/src/config2.ts +9 -7
  34. package/src/deployManager/components/CommitModal.tsx +1 -1
  35. package/src/deployManager/components/DeployProgressView.tsx +1 -1
  36. package/src/deployManager/components/MachineDetailPage.tsx +1 -1
  37. package/src/deployManager/components/MachinesListPage.tsx +1 -1
  38. package/src/deployManager/components/ServiceDetailPage.tsx +1 -1
  39. package/src/deployManager/components/ServicesListPage.tsx +1 -1
  40. package/src/deployManager/components/Tools.tsx +1 -1
  41. package/src/deployManager/machineApplyMainCode.ts +3 -3
  42. package/src/deployManager/machineController.ts +1 -1
  43. package/src/deployManager/machineSchema.ts +1 -1
  44. package/src/deployManager/setupMachineMain.ts +1 -1
  45. package/src/diagnostics/FunctionCallInfoState.ts +6 -3
  46. package/src/diagnostics/MachineThreadInfo.tsx +1 -1
  47. package/src/diagnostics/NodeViewer.tsx +2 -1
  48. package/src/diagnostics/StatWarning.tsx +56 -0
  49. package/src/diagnostics/StatsHeader.tsx +248 -0
  50. package/src/diagnostics/StatsOverrides.ts +50 -0
  51. package/src/diagnostics/SyncTestPage.tsx +3 -3
  52. package/src/diagnostics/TimeDebug.tsx +1 -1
  53. package/src/diagnostics/debugger/mcp-server.ts +1 -1
  54. package/src/diagnostics/grossStats/GrossStatsPage.tsx +1 -1
  55. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +1 -1
  56. package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +1 -1
  57. package/src/diagnostics/logs/TimeRangeSelector.tsx +1 -1
  58. package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +1 -1
  59. package/src/diagnostics/logs/errorNotifications2/ErrorWarning.tsx +0 -1
  60. package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +1 -1
  61. package/src/diagnostics/logs/errorNotifications2/errorWatchEntry.ts +1 -1
  62. package/src/diagnostics/logs/errorNotifications2/errorWatcher.ts +1 -1
  63. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +1 -1
  64. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +1 -1
  65. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +1 -1
  66. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +1 -1
  67. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +1 -1
  68. package/src/diagnostics/managementPages.tsx +4 -9
  69. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +1 -1
  70. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +1 -1
  71. package/src/diagnostics/misc-pages/DNSPage.tsx +1 -1
  72. package/src/diagnostics/pathAuditer.ts +6 -6
  73. package/src/diagnostics/statsDefinitions.tsx +253 -0
  74. package/src/functional/throttleRender.ts +1 -1
  75. package/src/library-components/AspectSizedComponent.tsx +1 -1
  76. package/src/library-components/Button.tsx +1 -1
  77. package/src/library-components/ButtonSelector.tsx +1 -1
  78. package/src/library-components/DropdownCustom.tsx +1 -1
  79. package/src/library-components/DropdownSelector.tsx +1 -1
  80. package/src/library-components/Histogram.tsx +1 -1
  81. package/src/library-components/InlinePopup.tsx +1 -1
  82. package/src/library-components/LazyComponent.tsx +5 -1
  83. package/src/library-components/StickyBottomScroll.tsx +1 -1
  84. package/src/library-components/TypedConfigEditor.tsx +1 -1
  85. package/src/library-components/URLParam.ts +5 -9
  86. package/src/library-components/drag.ts +1 -1
  87. package/src/misc/formatJSX.tsx +1 -1
  88. package/src/user-implementation/userData.ts +1 -1
  89. package/test2.ts +1 -1
  90. package/testEntry2.ts +1 -1
  91. package/valid.md +205 -0
  92. package/src/deployManager/LaunchTrackingHeader.tsx +0 -65
  93. package/src/diagnostics/FunctionCallInfo.tsx +0 -141
  94. package/src/diagnostics/PathDistributionInfo.tsx +0 -110
  95. package/src/diagnostics/ValuePathWarning.tsx +0 -68
@@ -3,7 +3,7 @@ import "../inject";
3
3
  import { measureWrap } from "socket-function/src/profiling/measure";
4
4
  import { getOwnMachineId } from "../-a-auth/certs";
5
5
  import { forceRemoveNode, getOurNodeId, getOurNodeIdAssert } from "../-f-node-discovery/NodeDiscovery";
6
- import { Querysub } from "../4-querysub/QuerysubController";
6
+ import { Querysub } from "../4-querysub/Querysub";
7
7
  import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, serviceConfigs, SERVICE_FOLDER, machineInfos, SERVICE_NODE_FILE_NAME, getEffectiveServiceConfigs, recordLaunch } from "./machineSchema";
8
8
  import { runPromise } from "../functional/runCommand";
9
9
  import { getExternalIP } from "socket-function/src/networking";
@@ -30,8 +30,8 @@ import { fsExistsAsync } from "../fs";
30
30
  // to PIPE_FILE_LINE_LIMIT lines it is truncated down to PIPE_FILE_LINE_KEEP. The
31
31
  // file size is checked every PIPE_FILE_LINE_KEEP lines, so the file stays within
32
32
  // [PIPE_FILE_LINE_KEEP, PIPE_FILE_LINE_LIMIT] lines.
33
- const PIPE_FILE_LINE_LIMIT = 2_000;
34
- const PIPE_FILE_LINE_KEEP = 1_000;
33
+ const PIPE_FILE_LINE_LIMIT = 10_000;
34
+ const PIPE_FILE_LINE_KEEP = 5_000;
35
35
 
36
36
 
37
37
  const getMemoryInfo = measureWrap(async function getMemoryInfo(): Promise<{ value: number; max: number } | undefined> {
@@ -6,7 +6,7 @@ import { timeInMinute } from "socket-function/src/misc";
6
6
  import { assertIsManagementUser } from "../diagnostics/managementPages";
7
7
  import { isNode } from "typesafecss";
8
8
  import { streamScreenOutput, getRollingServiceInfo, killRollingServicesForService } from "./machineApplyMainCode";
9
- import { Querysub } from "../4-querysub/QuerysubController";
9
+ import { Querysub } from "../4-querysub/Querysub";
10
10
  import { getPathStr2 } from "../path";
11
11
  import { getGitURLLive, setGitRef } from "../4-deploy/git";
12
12
  import os from "os";
@@ -21,7 +21,7 @@ import { runPromise } from "../functional/runCommand";
21
21
  import { isNode } from "typesafecss";
22
22
  import { DeployProgress, deployFunctions, deployGetFunctions } from "../4-deploy/deployFunctions";
23
23
  import { FunctionSpec, functionSchema } from "../3-path-functions/PathFunctionRunner";
24
- import { Querysub } from "../4-querysub/QuerysubController";
24
+ import { Querysub } from "../4-querysub/Querysub";
25
25
  import { green, red } from "socket-function/src/formatting/logColors";
26
26
  import { fsExistsAsync } from "../fs";
27
27
 
@@ -1,6 +1,6 @@
1
1
  import { getBackblazePath } from "../-a-archives/archivesBackBlaze";
2
2
  import { getGitURLLive, getGitRefLive } from "../4-deploy/git";
3
- import { Querysub } from "../4-querysub/QuerysubController";
3
+ import { Querysub } from "../4-querysub/Querysub";
4
4
  import { runPromise } from "../functional/runCommand";
5
5
  import fs from "fs";
6
6
  import os from "os";
@@ -1,4 +1,4 @@
1
- import { Querysub } from "../4-querysub/QuerysubController";
1
+ import { Querysub } from "../4-querysub/Querysub";
2
2
  import { onPredictionFinished } from "../4-querysub/querysubPrediction";
3
3
  import { lazy } from "socket-function/src/caching";
4
4
  import { t } from "../2-proxy/schema2";
@@ -23,6 +23,7 @@ interface CallStatsData {
23
23
  remotePathCount: number;
24
24
  totalValueCount: number;
25
25
  proxyWatcherCount: number;
26
+ callsByServer: { [creatorId: string]: number };
26
27
  perFunctionStats: {
27
28
  [functionId: string]: {
28
29
  totalCalls: number;
@@ -56,6 +57,7 @@ let statsData: CallStatsData = {
56
57
  remotePathCount: 0,
57
58
  totalValueCount: 0,
58
59
  proxyWatcherCount: 0,
60
+ callsByServer: {},
59
61
  perFunctionStats: {},
60
62
  };
61
63
 
@@ -75,8 +77,9 @@ export function callState(): CallStatsData {
75
77
  export let ensureSubscribed = lazy(() => {
76
78
  onPredictionFinished((data) => {
77
79
  statsData.totalCalls++;
78
- statsData.totalInternalReruns += data.result.totalInternalLoopCount;
79
- statsData.totalFullReruns += data.result.outerLoopCount;
80
+ statsData.callsByServer[data.creatorId] = (statsData.callsByServer[data.creatorId] || 0) + 1;
81
+ statsData.totalInternalReruns += data.result.totalInternalLoopCount - 1;
82
+ statsData.totalFullReruns += data.result.outerLoopCount - 1;
80
83
  addToStatsValue(statsData.evalTimeStats, data.result.evalTime);
81
84
  addToStatsValue(statsData.timeTakenStats, data.result.timeTaken);
82
85
  addToStatsValue(statsData.totalTimeStats, data.result.totalTime);
@@ -15,7 +15,7 @@ import dns from "dns";
15
15
  import { isDefined } from "../misc";
16
16
  import { getDebuggerUrl } from "./listenOnDebugger";
17
17
  import { Button } from "../library-components/Button";
18
- import { Querysub } from "../4-querysub/QuerysubController";
18
+ import { Querysub } from "../4-querysub/Querysub";
19
19
  import { NodeViewerController } from "./NodeViewer";
20
20
  import { showFullscreenModal } from "../5-diagnostics/FullscreenModal";
21
21
  import { css } from "../4-dom/css";
@@ -3,7 +3,8 @@ import { SocketFunction } from "socket-function/SocketFunction";
3
3
  import { css } from "typesafecss";
4
4
  import { getAllNodeIds, getBrowserUrlNode, syncNodesNow } from "../../src/-f-node-discovery/NodeDiscovery";
5
5
  import { errorToUndefined, errorToUndefinedSilent, logErrors, timeoutToError } from "../../src/errors";
6
- import { Querysub, QuerysubController } from "../../src/4-querysub/QuerysubController";
6
+ import { QuerysubController } from "../../src/4-querysub/QuerysubController";
7
+ import { Querysub } from "../../src/4-querysub/Querysub";
7
8
  import { NodeCapabilitiesController, getControllerNodeIdList } from "../../src/-g-core-values/NodeCapabilities";
8
9
  import { lazy } from "socket-function/src/caching";
9
10
  import { atomicObjectWrite } from "../../src/2-proxy/PathValueProxyWatcher";
@@ -0,0 +1,56 @@
1
+ import { qreact } from "../4-dom/qreact";
2
+ import { css } from "../4-dom/css";
3
+
4
+ module.hotreload = true;
5
+
6
+ const FLASH_CLASS_NAME = "StatWarning-flash";
7
+
8
+ export type StatWarningAction = { label: string; onClick: () => void };
9
+
10
+ export class StatWarning extends qreact.Component<{
11
+ emoji: string;
12
+ name: string;
13
+ threshold: number;
14
+ flashing?: boolean;
15
+ actions?: StatWarningAction[];
16
+ children?: qreact.ComponentChildren;
17
+ }> {
18
+ render() {
19
+ let isFlashing = this.props.flashing !== false;
20
+ let actions = this.props.actions || [];
21
+ let alertClass = css.hbox(6).pad2(6, 2) + " " + css.hsl(0, 80, 60)
22
+ + (isFlashing && (" " + FLASH_CLASS_NAME) || "");
23
+ let restClass = css.hbox(8).pad2(6, 2).wrap + " " + css.hsl(0, 0, 25);
24
+ return <div className={css.hbox(0).wrap}>
25
+ <div className={alertClass}>
26
+ <span>{this.props.emoji}</span>
27
+ <span>Threshold exceeded: {this.props.threshold}</span>
28
+ </div>
29
+ <div className={restClass}>
30
+ <span>{this.props.children}</span>
31
+ <span> -</span>
32
+ <span>{this.props.name}</span>
33
+ {actions.map(action =>
34
+ <div
35
+ className={css.button.pad2(6, 2).background("hsla(0, 0%, 100%, 0.15)")}
36
+ onClick={event => {
37
+ event.stopPropagation();
38
+ action.onClick();
39
+ }}
40
+ >
41
+ {action.label}
42
+ </div>
43
+ )}
44
+ </div>
45
+ {isFlashing && <style>{`
46
+ @keyframes ${FLASH_CLASS_NAME}-anim {
47
+ 0%, 100% { background-color: hsl(0, 80%, 60%); }
48
+ 50% { background-color: hsl(0, 80%, 30%); }
49
+ }
50
+ .${FLASH_CLASS_NAME} {
51
+ animation: ${FLASH_CLASS_NAME}-anim 0.6s infinite;
52
+ }
53
+ `}</style>}
54
+ </div>;
55
+ }
56
+ }
@@ -0,0 +1,248 @@
1
+ import { qreact } from "../4-dom/qreact";
2
+ import { css } from "../4-dom/css";
3
+ import { t } from "../2-proxy/schema2";
4
+ import { timeInHour } from "socket-function/src/misc";
5
+ import { isCurrentUserSuperUser } from "../user-implementation/userData";
6
+ import { Querysub } from "../4-querysub/Querysub";
7
+ import { StatWarning, StatWarningAction } from "./StatWarning";
8
+ import { getStatOverridesSync, setStatOverride, statOffenderKey } from "./StatsOverrides";
9
+
10
+ module.hotreload = true;
11
+
12
+ const IGNORE_SHORT_MS = 12 * timeInHour;
13
+ const IGNORE_LONG_MS = 3 * 24 * timeInHour;
14
+ const PEAK_MULTIPLIER = 2;
15
+ const REEVALUATE_INTERVAL_MS = 60_000;
16
+
17
+ export interface StatDefinition {
18
+ category: string;
19
+ title: string;
20
+ emoji: string;
21
+ // Warns when >= threshold
22
+ threshold: number;
23
+ // Highlights with a blue background when >= dietThreshold (and below threshold). No dismiss action.
24
+ dietThreshold?: number;
25
+ formatter?: (value: number) => qreact.ComponentChildren;
26
+ getValue: () => number;
27
+ // Per-instance offenders responsible for the warning; used to render extra "raise/ignore for this one" buttons.
28
+ getOffenders?: () => { id: string; value: number }[];
29
+ }
30
+
31
+ let peakValues = new Map<string, number>();
32
+ let offenderPeaks = new Map<string, number>();
33
+
34
+ type StatInfo = {
35
+ stat: StatDefinition;
36
+ value: number;
37
+ threshold: number;
38
+ isOverThreshold: boolean;
39
+ isIgnored: boolean;
40
+ isOverDiet: boolean;
41
+ };
42
+
43
+ export class StatsHeader extends qreact.Component<{ getStats: () => StatDefinition[] }> {
44
+ state = t.state({
45
+ expanded: t.lookup({ open: t.booleanOptional }),
46
+ nowTick: t.number,
47
+ });
48
+
49
+ componentDidMount() {
50
+ setInterval(() => {
51
+ Querysub.localCommit(() => {
52
+ this.state.nowTick = Date.now();
53
+ });
54
+ }, REEVALUATE_INTERVAL_MS);
55
+ }
56
+
57
+ toggle(category: string) {
58
+ if (category in this.state.expanded) {
59
+ delete this.state.expanded[category];
60
+ return;
61
+ }
62
+ this.state.expanded[category] = { open: true };
63
+ }
64
+
65
+ buildInfos(): { categories: string[]; byCategory: Map<string, StatInfo[]>; infos: StatInfo[] } {
66
+ let now = Date.now();
67
+ let overrides = getStatOverridesSync();
68
+ let categories: string[] = [];
69
+ let byCategory = new Map<string, StatInfo[]>();
70
+ let infos: StatInfo[] = [];
71
+
72
+ for (let stat of this.props.getStats()) {
73
+ let value = stat.getValue();
74
+ let priorPeak = peakValues.get(stat.title) || 0;
75
+ if (value > priorPeak) {
76
+ peakValues.set(stat.title, value);
77
+ }
78
+ if (stat.getOffenders) {
79
+ for (let offender of stat.getOffenders()) {
80
+ let key = statOffenderKey(stat.title, offender.id);
81
+ let priorOffenderPeak = offenderPeaks.get(key) || 0;
82
+ if (offender.value > priorOffenderPeak) {
83
+ offenderPeaks.set(key, offender.value);
84
+ }
85
+ }
86
+ }
87
+ let override = overrides[stat.title] || {};
88
+ let threshold = override.thresholdOverride || stat.threshold;
89
+ let ignoredUntil = override.ignoreUntilMs || 0;
90
+ let isOverThreshold = value >= threshold;
91
+ let isIgnored = isOverThreshold && ignoredUntil > now;
92
+ let isOverDiet = !isOverThreshold && !!stat.dietThreshold && value >= stat.dietThreshold;
93
+ let info: StatInfo = { stat, value, threshold, isOverThreshold, isIgnored, isOverDiet };
94
+ infos.push(info);
95
+
96
+ let list = byCategory.get(stat.category);
97
+ if (!list) {
98
+ list = [];
99
+ byCategory.set(stat.category, list);
100
+ categories.push(stat.category);
101
+ }
102
+ list.push(info);
103
+ }
104
+
105
+ return { categories, byCategory, infos };
106
+ }
107
+
108
+ renderWarning(info: StatInfo): qreact.ComponentChildren {
109
+ let title = info.stat.title;
110
+ let actions: StatWarningAction[] = [
111
+ {
112
+ label: `Raise to ${PEAK_MULTIPLIER}× peak`,
113
+ onClick: () => {
114
+ let peak = peakValues.get(title) || info.value;
115
+ setStatOverride(title, { thresholdOverride: peak * PEAK_MULTIPLIER });
116
+ },
117
+ },
118
+ {
119
+ label: "Ignore 12h",
120
+ onClick: () => {
121
+ setStatOverride(title, { ignoreUntilMs: Date.now() + IGNORE_SHORT_MS });
122
+ },
123
+ },
124
+ {
125
+ label: "Ignore 3d",
126
+ onClick: () => {
127
+ setStatOverride(title, { ignoreUntilMs: Date.now() + IGNORE_LONG_MS });
128
+ },
129
+ },
130
+ ];
131
+ if (info.stat.getOffenders) {
132
+ for (let offender of info.stat.getOffenders()) {
133
+ let offenderKey = statOffenderKey(title, offender.id);
134
+ actions.push({
135
+ label: `${offender.id}: ${PEAK_MULTIPLIER}× peak`,
136
+ onClick: () => {
137
+ let peak = offenderPeaks.get(offenderKey) || offender.value;
138
+ setStatOverride(offenderKey, { thresholdOverride: peak * PEAK_MULTIPLIER });
139
+ },
140
+ });
141
+ actions.push({
142
+ label: `${offender.id}: ignore 12h`,
143
+ onClick: () => {
144
+ setStatOverride(offenderKey, { ignoreUntilMs: Date.now() + IGNORE_SHORT_MS });
145
+ },
146
+ });
147
+ actions.push({
148
+ label: `${offender.id}: ignore 3d`,
149
+ onClick: () => {
150
+ setStatOverride(offenderKey, { ignoreUntilMs: Date.now() + IGNORE_LONG_MS });
151
+ },
152
+ });
153
+ }
154
+ }
155
+ let formatted = info.stat.formatter && info.stat.formatter(info.value) || info.value;
156
+ return <StatWarning
157
+ emoji={info.stat.emoji}
158
+ name={title}
159
+ threshold={info.threshold}
160
+ actions={actions}
161
+ >
162
+ {formatted}
163
+ </StatWarning>;
164
+ }
165
+
166
+ renderStatInline(info: StatInfo): qreact.ComponentChildren {
167
+ let stat = info.stat;
168
+ let formatted = stat.formatter && stat.formatter(info.value) || info.value;
169
+ let baseClass = css.hbox(4).pad2(6, 2);
170
+ let className = info.isOverDiet
171
+ && baseClass + " " + css.background("hsl(210, 70%, 35%)")
172
+ || baseClass;
173
+ let dietSuffix = info.isOverDiet && ` (diet threshold ${stat.dietThreshold})` || "";
174
+ return <span
175
+ title={`${stat.title}${dietSuffix}`}
176
+ className={className}
177
+ >
178
+ {stat.emoji} {formatted}
179
+ </span>;
180
+ }
181
+
182
+ render() {
183
+ if (!isCurrentUserSuperUser()) return undefined;
184
+
185
+ let { categories, byCategory, infos } = this.buildInfos();
186
+ let activeWarnings = infos.filter(info =>
187
+ info.isOverThreshold && !info.isIgnored && !(info.stat.category in this.state.expanded)
188
+ );
189
+ let activeDietWarnings = infos.filter(info =>
190
+ info.isOverDiet && !(info.stat.category in this.state.expanded)
191
+ );
192
+
193
+ return <div className={css.vbox(4)}>
194
+ <div className={css.hbox(2).wrap}>
195
+ {categories.map(category => {
196
+ let isOpen = category in this.state.expanded;
197
+ let baseClass = css.button.hbox(4).pad2(10, 4)
198
+ .bord(1, "hsla(0, 0%, 100%, 0.25)")
199
+ .background(isOpen && "hsla(0, 0%, 100%, 0.18)" || "hsla(0, 0%, 100%, 0.06)");
200
+ return <div
201
+ className={baseClass + ""}
202
+ onClick={() => this.toggle(category)}
203
+ >
204
+ <span>{category}</span>
205
+ </div>;
206
+ })}
207
+ </div>
208
+ {categories.filter(category => category in this.state.expanded).map(category => {
209
+ let list = byCategory.get(category) || [];
210
+ let inline = list.filter(info => !info.isOverThreshold);
211
+ let warnings = list.filter(info => info.isOverThreshold);
212
+ return <div className={css.vbox(2).pad2(6, 2).background("hsla(0, 0%, 100%, 0.04)")}>
213
+ <div className={css.colorhsl(0, 0, 50).fontSize(11)}>{category}</div>
214
+ <div className={css.hbox(12).wrap}>
215
+ {inline.map(info => this.renderStatInline(info))}
216
+ </div>
217
+ {warnings.length > 0 && <div className={css.vbox(4)}>
218
+ {warnings.map(info => this.renderWarning(info))}
219
+ </div>}
220
+ </div>;
221
+ })}
222
+ {activeDietWarnings.length > 0 && <div className={css.vbox(4)}>
223
+ {(() => {
224
+ let dietCategories: string[] = [];
225
+ let dietByCategory = new Map<string, StatInfo[]>();
226
+ for (let info of activeDietWarnings) {
227
+ let list = dietByCategory.get(info.stat.category);
228
+ if (!list) {
229
+ list = [];
230
+ dietByCategory.set(info.stat.category, list);
231
+ dietCategories.push(info.stat.category);
232
+ }
233
+ list.push(info);
234
+ }
235
+ return dietCategories.map(category =>
236
+ <div className={css.hbox(8).wrap}>
237
+ <span className={css.colorhsl(0, 0, 50).fontSize(11)}>{category}</span>
238
+ {(dietByCategory.get(category) || []).map(info => this.renderStatInline(info))}
239
+ </div>
240
+ );
241
+ })()}
242
+ </div>}
243
+ {activeWarnings.length > 0 && <div className={css.vbox(4)}>
244
+ {activeWarnings.map(info => this.renderWarning(info))}
245
+ </div>}
246
+ </div>;
247
+ }
248
+ }
@@ -0,0 +1,50 @@
1
+ import { Querysub } from "../4-querysub/Querysub";
2
+ import { t } from "../2-proxy/schema2";
3
+
4
+ const LOCAL_STORAGE_KEY = "StatsHeader.overrides.v2";
5
+
6
+ export type StatOverride = { thresholdOverride?: number; ignoreUntilMs?: number };
7
+ type OverridesMap = { [key: string]: StatOverride };
8
+
9
+ let cachedOverrides: OverridesMap | undefined;
10
+
11
+ let overridesSchema = Querysub.createLocalSchema("statsOverrides", {
12
+ sequenceNumber: t.number,
13
+ });
14
+
15
+ function loadFromStorage(): OverridesMap {
16
+ if (cachedOverrides) {
17
+ return cachedOverrides;
18
+ }
19
+ try {
20
+ let raw = localStorage.getItem(LOCAL_STORAGE_KEY);
21
+ cachedOverrides = raw && JSON.parse(raw) as OverridesMap || {};
22
+ } catch (e) {
23
+ console.error(`StatsOverrides: failed to load:`, (e as Error)?.stack || e);
24
+ cachedOverrides = {};
25
+ }
26
+ return cachedOverrides;
27
+ }
28
+
29
+ export function getStatOverridesSync(): OverridesMap {
30
+ overridesSchema().sequenceNumber;
31
+ return loadFromStorage();
32
+ }
33
+
34
+ export function getStatOverrideSync(key: string): StatOverride {
35
+ return getStatOverridesSync()[key] || {};
36
+ }
37
+
38
+ export function setStatOverride(key: string, patch: StatOverride) {
39
+ let current = loadFromStorage();
40
+ let next: OverridesMap = { ...current };
41
+ next[key] = { ...next[key], ...patch };
42
+ cachedOverrides = next;
43
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(next));
44
+ let schema = overridesSchema();
45
+ schema.sequenceNumber = schema.sequenceNumber + 1;
46
+ }
47
+
48
+ export function statOffenderKey(statTitle: string, offenderId: string): string {
49
+ return `${statTitle}::${offenderId}`;
50
+ }
@@ -2,7 +2,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { qreact } from "../4-dom/qreact";
3
3
  import { assertIsManagementUser } from "./managementPages";
4
4
  import { getSyncedController } from "../library-components/SyncedController";
5
- import { Querysub } from "../4-querysub/QuerysubController";
5
+ import { Querysub } from "../4-querysub/Querysub";
6
6
  import { isRegisteredUserPERMISSIONS, isSuperUserPERMISSIONS } from "../user-implementation/userData";
7
7
  import { css } from "typesafecss";
8
8
  import { Button } from "../library-components/Button";
@@ -58,7 +58,7 @@ let { data, functions } = Querysub.createSchema({
58
58
  }
59
59
  for (let path of watcher.pendingWatches.paths) {
60
60
  if (!path.includes("syncText")) continue;
61
- let pathValue = authorityStorage.getValueAtTime(path, undefined, false, "noAudit");
61
+ let pathValue = authorityStorage.getValueAtOrBeforeTime(path, undefined, false, "noAudit");
62
62
  let source = remoteWatcher.getExistingWatchRemoteNodeId(path);
63
63
  console.log("Path value", pathValue?.time.time, pathValueSerializer.getPathValue(pathValue), path, source);
64
64
  }
@@ -315,7 +315,7 @@ class SyncTestControllerBase {
315
315
  }
316
316
 
317
317
  public async getValueAndTime(path: string) {
318
- let pathValue = authorityStorage.getValueAtTime(path, undefined, false, "noAudit");
318
+ let pathValue = authorityStorage.getValueAtOrBeforeTime(path, undefined, false, "noAudit");
319
319
  let value = pathValueSerializer.getPathValue(pathValue);
320
320
  let time = pathValue?.time.time;
321
321
  return { value, time };
@@ -1,5 +1,5 @@
1
1
  import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
2
- import { Querysub } from "../../src/4-querysub/QuerysubController";
2
+ import { Querysub } from "../../src/4-querysub/Querysub";
3
3
  import { formatTime } from "socket-function/src/formatting/format";
4
4
  import { getTrueTimeOffset } from "socket-function/time/trueTimeShim";
5
5
  import { css } from "typesafecss";
@@ -41,7 +41,7 @@ import * as dns from "dns";
41
41
  import "../../inject";
42
42
 
43
43
  import { DebuggerRemote } from "./debugger-remote";
44
- import { Querysub } from "../../4-querysub/QuerysubController";
44
+ import { Querysub } from "../../4-querysub/Querysub";
45
45
  import { getAllNodeIds } from "../../-f-node-discovery/NodeDiscovery";
46
46
  import { NodeCapabilitiesController } from "../../-g-core-values/NodeCapabilities";
47
47
  import { timeoutToUndefinedSilent } from "../../errors";
@@ -1,7 +1,7 @@
1
1
  import preact from "preact";
2
2
  import { qreact } from "../../4-dom/qreact";
3
3
  import { css } from "../../4-dom/css";
4
- import { Querysub } from "../../4-querysub/QuerysubController";
4
+ import { Querysub } from "../../4-querysub/Querysub";
5
5
  import { SocketFunction } from "socket-function/SocketFunction";
6
6
  import { formatDateTime, formatNumber } from "socket-function/src/formatting/format";
7
7
  import { timeInHour, timeInMinute } from "socket-function/src/misc";
@@ -8,7 +8,7 @@ import { getLoggers2Async, LogDatum } from "../diskLogger";
8
8
  import { list, timeInDay, timeInHour, keyByArray, sort, throttleFunction } from "socket-function/src/misc";
9
9
  import { formatDateTime, formatDateTimeDetailed, formatNumber, formatTime, formatPercent } from "socket-function/src/formatting/format";
10
10
  import { IndexedLogs, TimeFilePathWithSize } from "./IndexedLogs";
11
- import { Querysub } from "../../../4-querysub/QuerysubController";
11
+ import { Querysub } from "../../../4-querysub/Querysub";
12
12
  import { URLParam } from "../../../library-components/URLParam";
13
13
  import { getOwnMachineId, getOwnThreadId } from "../../../-f-node-discovery/NodeDiscovery";
14
14
  import { FilePathSelector, FilePathsByMachine } from "./FilePathSelector";
@@ -15,7 +15,7 @@ import "../../../inject";
15
15
 
16
16
  import * as http from "http";
17
17
  import { logErrors, timeoutToUndefinedSilent } from "../../../errors";
18
- import { Querysub } from "../../../4-querysub/QuerysubController";
18
+ import { Querysub } from "../../../4-querysub/Querysub";
19
19
  import { MCPIndexedLogs } from "./MCPIndexedLogs";
20
20
  import { getAllNodeIds } from "../../../-f-node-discovery/NodeDiscovery";
21
21
  import { NodeCapabilitiesController } from "../../../-g-core-values/NodeCapabilities";
@@ -6,7 +6,7 @@ import { css } from "../../4-dom/css";
6
6
  import { formatTime } from "socket-function/src/formatting/format";
7
7
  import { InputLabel } from "../../library-components/InputLabel";
8
8
  import { Button } from "../../library-components/Button";
9
- import { Querysub } from "../../4-querysub/QuerysubController";
9
+ import { Querysub } from "../../4-querysub/Querysub";
10
10
  import { timeInDay, timeInHour, timeInMinute } from "socket-function/src/misc";
11
11
 
12
12
  // URL parameters for time range
@@ -4,7 +4,7 @@ import { css } from "typesafecss";
4
4
  import { LogDatum } from "../diskLogger";
5
5
  import { sort, timeInDay } from "socket-function/src/misc";
6
6
  import { formatDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
7
- import { Querysub } from "../../../4-querysub/QuerysubController";
7
+ import { Querysub } from "../../../4-querysub/Querysub";
8
8
  import { InputLabel } from "../../../library-components/InputLabel";
9
9
  import { isPublic } from "../../../config";
10
10
  import { MachineThreadInfo } from "../../MachineThreadInfo";
@@ -1,7 +1,6 @@
1
1
  import { qreact } from "../../../4-dom/qreact";
2
2
  import { t } from "../../../2-proxy/schema2";
3
3
  import { css } from "typesafecss";
4
- import { Querysub } from "../../../4-querysub/QuerysubController";
5
4
  import { ATag } from "../../../library-components/ATag";
6
5
  import { managementPageURL, showingManagementURL } from "../../managementPages";
7
6
  import { LogDatumRenderer } from "./ErrorNotificationPage";
@@ -2,7 +2,7 @@ import { cacheLimited, lazy } from "socket-function/src/caching";
2
2
  import { nestArchives } from "../../../-a-archives/archives";
3
3
  import { getArchivesBackblaze } from "../../../-a-archives/archivesBackBlaze";
4
4
  import { archiveJSONT } from "../../../-a-archives/archivesJSONT";
5
- import { Querysub } from "../../../4-querysub/QuerysubController";
5
+ import { Querysub } from "../../../4-querysub/Querysub";
6
6
  import { getDomain, isPublic } from "../../../config";
7
7
  import { MachineInfo } from "../../../deployManager/machineSchema";
8
8
  import { createMatchesPattern } from "../IndexedLogs/bufferSearchFindMatcher";
@@ -2,7 +2,7 @@ import "../../../forceProduction";
2
2
  import "../../../inject";
3
3
 
4
4
  import { logErrors } from "../../../errors";
5
- import { Querysub } from "../../../4-querysub/QuerysubController";
5
+ import { Querysub } from "../../../4-querysub/Querysub";
6
6
  import { exposeErrorWatchService } from "./errorNotifications";
7
7
 
8
8
  logErrors(main());
@@ -2,7 +2,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { lazy } from "socket-function/src/caching";
3
3
  import { nextId } from "socket-function/src/misc";
4
4
  import { t } from "../../../2-proxy/schema2";
5
- import { Querysub } from "../../../4-querysub/QuerysubController";
5
+ import { Querysub } from "../../../4-querysub/Querysub";
6
6
  import { LogDatum } from "../diskLogger";
7
7
  import { SuppressionMatch, watchUnmatchedErrors, ErrorNotificationsController } from "./errorNotifications";
8
8
 
@@ -3,7 +3,7 @@ import { deepCloneJSON } from "socket-function/src/misc";
3
3
  import { css } from "typesafecss";
4
4
  import { t } from "../../../2-proxy/schema2";
5
5
  import { qreact } from "../../../4-dom/qreact";
6
- import { Querysub } from "../../../4-querysub/QuerysubController";
6
+ import { Querysub } from "../../../4-querysub/Querysub";
7
7
  import { Button } from "../../../library-components/Button";
8
8
  import { InputLabel } from "../../../library-components/InputLabel";
9
9
  import { LogDatum } from "../diskLogger";
@@ -3,7 +3,7 @@ import { deepCloneJSON } from "socket-function/src/misc";
3
3
  import { css } from "typesafecss";
4
4
  import { t } from "../../../2-proxy/schema2";
5
5
  import { qreact } from "../../../4-dom/qreact";
6
- import { Querysub } from "../../../4-querysub/QuerysubController";
6
+ import { Querysub } from "../../../4-querysub/Querysub";
7
7
  import { Button } from "../../../library-components/Button";
8
8
  import { niceStringify } from "../../../niceStringify";
9
9
  import { LogDatum } from "../diskLogger";
@@ -2,7 +2,7 @@ import { qreact } from "../../../4-dom/qreact";
2
2
  import { t } from "../../../2-proxy/schema2";
3
3
  import { css } from "typesafecss";
4
4
  import { deepCloneJSON, nextId, sort } from "socket-function/src/misc";
5
- import { Querysub } from "../../../4-querysub/QuerysubController";
5
+ import { Querysub } from "../../../4-querysub/Querysub";
6
6
  import { InputLabel } from "../../../library-components/InputLabel";
7
7
  import { Button } from "../../../library-components/Button";
8
8
  import { LifeCycle, LifeCycleEntry, LifeCyclesController, getVariables } from "./lifeCycles";
@@ -4,7 +4,7 @@ import { deepCloneJSON, nextId } from "socket-function/src/misc";
4
4
  import { css } from "typesafecss";
5
5
  import { t } from "../../../2-proxy/schema2";
6
6
  import { qreact } from "../../../4-dom/qreact";
7
- import { Querysub } from "../../../4-querysub/QuerysubController";
7
+ import { Querysub } from "../../../4-querysub/Querysub";
8
8
  import { formatValue } from "../../../5-diagnostics/GenericFormat";
9
9
  import { Button } from "../../../library-components/Button";
10
10
  import { InputLabel } from "../../../library-components/InputLabel";
@@ -1,4 +1,4 @@
1
- import { Querysub } from "../../../4-querysub/QuerysubController";
1
+ import { Querysub } from "../../../4-querysub/Querysub";
2
2
  import { getTimeRange } from "../TimeRangeSelector";
3
3
  import { throttleFunction, sort } from "socket-function/src/misc";
4
4
  import { getLoggers2Async, LogDatum } from "../diskLogger";