querysub 0.302.0 → 0.304.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/package.json +2 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +41 -0
- package/src/-g-core-values/NodeCapabilities.ts +35 -17
- package/src/0-path-value-core/NodePathAuthorities.ts +30 -32
- package/src/1-path-client/RemoteWatcher.ts +9 -4
- package/src/2-proxy/PathValueProxyWatcher.ts +14 -0
- package/src/3-path-functions/PathFunctionRunner.ts +4 -0
- package/src/3-path-functions/syncSchema.ts +4 -0
- package/src/4-deploy/deployFunctions.ts +0 -3
- package/src/4-deploy/deployGetFunctionsInner.ts +2 -0
- package/src/4-deploy/edgeBootstrap.ts +5 -0
- package/src/4-deploy/edgeClientWatcher.tsx +37 -4
- package/src/4-querysub/Querysub.ts +5 -1
- package/src/4-querysub/QuerysubController.ts +15 -26
- package/src/5-diagnostics/memoryValueAudit.ts +1 -1
- package/src/deployManager/machineApplyMainCode.ts +8 -0
- package/src/deployManager/machineSchema.ts +1 -1
- package/src/deployManager/spec.txt +8 -8
- package/src/diagnostics/errorLogs/ErrorLogController.ts +2 -1
- package/src/diagnostics/logs/ObjectDisplay.tsx +8 -3
- package/src/diagnostics/managementPages.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.304.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"js-sha512": "^0.9.0",
|
|
23
23
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
24
24
|
"pako": "^2.1.0",
|
|
25
|
-
"socket-function": "^0.
|
|
25
|
+
"socket-function": "^0.135.0",
|
|
26
26
|
"terser": "^5.31.0",
|
|
27
27
|
"typesafecss": "^0.22.0",
|
|
28
28
|
"yaml": "^2.5.0",
|
|
@@ -220,6 +220,45 @@ export function configRootDiscoveryLocation(config: {
|
|
|
220
220
|
}) {
|
|
221
221
|
rootDiscoveryNodeId = getNodeId(config.domain, config.port);
|
|
222
222
|
}
|
|
223
|
+
|
|
224
|
+
export function updateRootDiscoveryLocation(nodeId: string) {
|
|
225
|
+
let prevRootId = getBrowserUrlNode();
|
|
226
|
+
if (!isClient()) {
|
|
227
|
+
throw new Error(`updateRootDiscoveryLocation should only be called in the browser. If not a client, you should be able to discovery new nodes automatically`);
|
|
228
|
+
}
|
|
229
|
+
let nodeCache = Object.entries(require.cache);
|
|
230
|
+
for (let [path, module] of nodeCache) {
|
|
231
|
+
if (!module) continue;
|
|
232
|
+
if (!path.includes(prevRootId)) continue;
|
|
233
|
+
let newPath = path.replace(prevRootId, nodeId);
|
|
234
|
+
// Remove the old module
|
|
235
|
+
delete require.cache[path];
|
|
236
|
+
// Add the new module
|
|
237
|
+
require.cache[newPath] = module;
|
|
238
|
+
module.filename = module.filename.replace(prevRootId, nodeId);
|
|
239
|
+
module.id = module.id.replace(prevRootId, nodeId);
|
|
240
|
+
if (module.original) {
|
|
241
|
+
module.original.filename = module.original.filename.replace(prevRootId, nodeId);
|
|
242
|
+
module.original.originalId = module.original.originalId.replace(prevRootId, nodeId);
|
|
243
|
+
for (let [key, value] of Object.entries(module.original.requests)) {
|
|
244
|
+
if (key.includes(prevRootId)) {
|
|
245
|
+
let newKey = key.replace(prevRootId, nodeId);
|
|
246
|
+
delete module.original.requests[key];
|
|
247
|
+
module.original.requests[newKey] = value;
|
|
248
|
+
key = newKey;
|
|
249
|
+
}
|
|
250
|
+
if (value.includes(prevRootId)) {
|
|
251
|
+
value = value.replace(prevRootId, nodeId);
|
|
252
|
+
module.original.requests[key] = value;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
rootDiscoveryNodeId = nodeId;
|
|
258
|
+
addNodeId(nodeId);
|
|
259
|
+
}
|
|
260
|
+
(globalThis as any).updateRootDiscoveryLocation = updateRootDiscoveryLocation;
|
|
261
|
+
|
|
223
262
|
/** NOTE: Can also be called serverside, if configRootDiscoveryLocation is called (otherwise can always be called clientside). */
|
|
224
263
|
export function getBrowserUrlNode() {
|
|
225
264
|
if (!isClient()) throw new Error(`getBrowserUrlNode can only be called when isClient()`);
|
|
@@ -472,6 +511,7 @@ if (isServer()) {
|
|
|
472
511
|
}
|
|
473
512
|
}
|
|
474
513
|
|
|
514
|
+
|
|
475
515
|
export async function forceRemoveNode(nodeId: string) {
|
|
476
516
|
await archives().del(nodeId);
|
|
477
517
|
void tellEveryoneNodesChanges();
|
|
@@ -488,6 +528,7 @@ export async function nodeDiscoveryShutdown() {
|
|
|
488
528
|
void tellEveryoneNodesChanges();
|
|
489
529
|
}
|
|
490
530
|
const tellEveryoneNodesChanges = throttleFunction(1000, function tellEveryoneNodesChanges() {
|
|
531
|
+
if (isClient()) return;
|
|
491
532
|
for (let nodeId of allNodeIds2) {
|
|
492
533
|
if (isOwnNodeId(nodeId)) continue;
|
|
493
534
|
ignoreErrors(NodeDiscoveryController.nodes[nodeId].resyncNodes());
|
|
@@ -19,6 +19,8 @@ import { hackDevtoolsWebsocketForward } from "./oneTimeForward";
|
|
|
19
19
|
|
|
20
20
|
let loadTime = Date.now();
|
|
21
21
|
|
|
22
|
+
let controllerNodeIdCache = new Map<string, string | Promise<string | undefined>>();
|
|
23
|
+
|
|
22
24
|
// NOTE: If this becomes slow (because we are just trying all servers), we could start to store capabilities
|
|
23
25
|
// in PersistedStorage. However, I don't think it will be a problem for quite some time.
|
|
24
26
|
export async function getControllerNodeId(
|
|
@@ -27,26 +29,42 @@ export async function getControllerNodeId(
|
|
|
27
29
|
retryCount = 1,
|
|
28
30
|
initialTime = Date.now(),
|
|
29
31
|
): Promise<string | undefined> {
|
|
30
|
-
let
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for (let nodeId of nodeIdsToTest) {
|
|
34
|
-
if (await doesNodeExposeController(nodeId, controller)) {
|
|
35
|
-
if (!quiet) {
|
|
36
|
-
let duration = Date.now() - initialTime;
|
|
37
|
-
console.log(green(`Resolved capability ${controller._classGuid} with node ${nodeId}, in ${formatTime(duration)}`));
|
|
38
|
-
}
|
|
39
|
-
return nodeId;
|
|
40
|
-
}
|
|
32
|
+
let cached = controllerNodeIdCache.get(controller._classGuid);
|
|
33
|
+
if (cached && typeof cached !== "string") {
|
|
34
|
+
cached = await cached;
|
|
41
35
|
}
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
if (cached && !SocketFunction.isNodeConnected(cached)) {
|
|
37
|
+
controllerNodeIdCache.delete(controller._classGuid);
|
|
38
|
+
cached = undefined;
|
|
45
39
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
40
|
+
if (cached) return cached;
|
|
41
|
+
let promise = getInternal();
|
|
42
|
+
controllerNodeIdCache.set(controller._classGuid, promise);
|
|
43
|
+
return promise;
|
|
44
|
+
|
|
45
|
+
async function getInternal() {
|
|
46
|
+
|
|
47
|
+
let nodeIdsToTest = await getAllNodeIds();
|
|
48
|
+
// Shuffle, so we aren't always using the same node!
|
|
49
|
+
nodeIdsToTest = shuffle(nodeIdsToTest, Date.now());
|
|
50
|
+
for (let nodeId of nodeIdsToTest) {
|
|
51
|
+
if (await doesNodeExposeController(nodeId, controller)) {
|
|
52
|
+
if (!quiet) {
|
|
53
|
+
let duration = Date.now() - initialTime;
|
|
54
|
+
console.log(green(`Resolved capability ${controller._classGuid} with node ${nodeId}, in ${formatTime(duration)}`));
|
|
55
|
+
}
|
|
56
|
+
return nodeId;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (retryCount > 0) {
|
|
60
|
+
await delay(2000);
|
|
61
|
+
return getControllerNodeId(controller, quiet, retryCount - 1, initialTime);
|
|
62
|
+
}
|
|
63
|
+
if (!quiet) {
|
|
64
|
+
console.warn(yellow(`Could not find a node that exposes controller ${controller._classGuid}, tried: ${JSON.stringify(nodeIdsToTest)}`));
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
48
67
|
}
|
|
49
|
-
return undefined;
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
export async function getControllerNodeIdList(
|
|
@@ -20,7 +20,7 @@ import { formatNumber, formatTime } from "socket-function/src/formatting/format"
|
|
|
20
20
|
import { cache, cacheLimited } from "socket-function/src/caching";
|
|
21
21
|
import { IdentityController_getCurrentReconnectNodeIdAssert, IdentityController_getReconnectNodeIdAssert } from "../-c-identity/IdentityController";
|
|
22
22
|
import { getBufferFraction, getBufferInt, getShortNumber } from "../bits";
|
|
23
|
-
import { devDebugbreak, isDevDebugbreak } from "../config";
|
|
23
|
+
import { devDebugbreak, getDomain, isDevDebugbreak } from "../config";
|
|
24
24
|
import { diskLog } from "../diagnostics/logs/diskLogger";
|
|
25
25
|
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
26
26
|
|
|
@@ -106,17 +106,18 @@ class NodePathAuthorities {
|
|
|
106
106
|
// NOTE: WHILE calling this function at the module level (because we this is a singleton) is bad,
|
|
107
107
|
// it SHOULD be fine. "getOwnNodeId" is part of level "-f-node-discovery", and we are level "0-path-value-core".
|
|
108
108
|
// It should be ready before us, and it should never be allowed to depend on us.
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
let selfOnReady = new PromiseObj();
|
|
111
|
-
this.authorities.set(getOwnNodeId(), {
|
|
112
|
-
nodeId: getOwnNodeId(),
|
|
113
|
-
authorityPaths: [],
|
|
114
|
-
isReadReady: 0,
|
|
115
|
-
createTime: this.createTime,
|
|
116
|
-
self: true,
|
|
117
|
-
onReady: selfOnReady,
|
|
118
|
-
});
|
|
119
111
|
if (isServer()) {
|
|
112
|
+
let originalOwnId = getOwnNodeId();
|
|
113
|
+
this.authorities.set(getOwnNodeId(), {
|
|
114
|
+
nodeId: getOwnNodeId(),
|
|
115
|
+
authorityPaths: [],
|
|
116
|
+
isReadReady: 0,
|
|
117
|
+
createTime: this.createTime,
|
|
118
|
+
self: true,
|
|
119
|
+
onReady: selfOnReady,
|
|
120
|
+
});
|
|
120
121
|
void SocketFunction.mountPromise.finally(() => {
|
|
121
122
|
// Move over to actual nodeId (which only exists after we mount)
|
|
122
123
|
let newOwnId = getOwnNodeId();
|
|
@@ -234,16 +235,17 @@ class NodePathAuthorities {
|
|
|
234
235
|
nodeIds = nodeIds.filter(nodeId => !isOwnNodeId(nodeId));
|
|
235
236
|
let newNodes = nodeIds.filter(nodeId => !this.authorities.has(nodeId));
|
|
236
237
|
|
|
237
|
-
let
|
|
238
|
+
let doInitialCheck = 0;
|
|
239
|
+
if (first || this.authorities.size === 0) {
|
|
240
|
+
doInitialCheck = POLL_RATE;
|
|
241
|
+
}
|
|
238
242
|
let isCurrentFirst = first;
|
|
239
243
|
first = false;
|
|
240
244
|
|
|
241
245
|
let promise = Promise.allSettled(newNodes.map(async nodeId => {
|
|
242
246
|
if (this.authorities.has(nodeId)) return;
|
|
243
247
|
|
|
244
|
-
|
|
245
|
-
let ourLoc = getNodeIdLocation(getOwnNodeId());
|
|
246
|
-
if (loc && loc.address.startsWith("127-0-0-1") && loc.port === ourLoc?.port) {
|
|
248
|
+
if (isOwnNodeId(nodeId)) {
|
|
247
249
|
// Ignore it if it's just us, using the special localhost address
|
|
248
250
|
return;
|
|
249
251
|
}
|
|
@@ -266,7 +268,7 @@ class NodePathAuthorities {
|
|
|
266
268
|
console.log(blue(`Identifying ${nodeId} as a path authority. ping latency = ${formatTime(time)}`));
|
|
267
269
|
this.authorities.set(nodeId, {
|
|
268
270
|
nodeId,
|
|
269
|
-
self: nodeId
|
|
271
|
+
self: isOwnNodeId(nodeId),
|
|
270
272
|
authorityPaths: [],
|
|
271
273
|
isReadReady: 0,
|
|
272
274
|
createTime,
|
|
@@ -279,14 +281,14 @@ class NodePathAuthorities {
|
|
|
279
281
|
});
|
|
280
282
|
let finished = false;
|
|
281
283
|
try {
|
|
282
|
-
if (
|
|
284
|
+
if (doInitialCheck) {
|
|
283
285
|
let timeoutObj = new PromiseObj();
|
|
284
286
|
setTimeout(() => {
|
|
285
287
|
timeoutObj.resolve();
|
|
286
|
-
if (!finished &&
|
|
287
|
-
console.warn(yellow(`Timeout after ${formatTime(
|
|
288
|
+
if (!finished && doInitialCheck) {
|
|
289
|
+
console.warn(yellow(`Timeout after ${formatTime(doInitialCheck)} while identifying ${nodeId}`));
|
|
288
290
|
}
|
|
289
|
-
},
|
|
291
|
+
}, doInitialCheck);
|
|
290
292
|
await Promise.race([
|
|
291
293
|
timeoutObj.promise,
|
|
292
294
|
errorToUndefined(this.forceCheckForReadReady(nodeId)),
|
|
@@ -384,7 +386,15 @@ class NodePathAuthorities {
|
|
|
384
386
|
obj.isReadReady = isReadReady;
|
|
385
387
|
obj.onReady.resolve();
|
|
386
388
|
|
|
387
|
-
|
|
389
|
+
if (isClient()) {
|
|
390
|
+
// If we are in the browser, any node that we can talk to is an edge node, and so presumably owns all paths!
|
|
391
|
+
// - Also clients, as... we shouldn't add certs when isClient, even if we are isNode.
|
|
392
|
+
obj.authorityPaths = [{
|
|
393
|
+
pathPrefix: getPathStr1(getDomain()),
|
|
394
|
+
}];
|
|
395
|
+
} else {
|
|
396
|
+
obj.authorityPaths = await PathController.nodes[nodeId].getAuthorityPaths();
|
|
397
|
+
}
|
|
388
398
|
if (obj.authorityPaths.length > 0) {
|
|
389
399
|
console.log(blue(`Identified ${isReadReady ? "(ready)" : yellow("(loading)")} ${nodeId} as a path authority for:`));
|
|
390
400
|
for (let authorityPath of obj.authorityPaths) {
|
|
@@ -570,9 +580,6 @@ class NodePathAuthorities {
|
|
|
570
580
|
);
|
|
571
581
|
}
|
|
572
582
|
public getReadNodes(path: string): string[] {
|
|
573
|
-
if (isClient()) {
|
|
574
|
-
return [getBrowserUrlNode()];
|
|
575
|
-
}
|
|
576
583
|
if (!this.waited) throw new Error(`waitUntilRoutingIsReady must be called before getReadNodes`);
|
|
577
584
|
return this.getReadObjs(path).map(x => x.nodeId);
|
|
578
585
|
}
|
|
@@ -631,9 +638,6 @@ class NodePathAuthorities {
|
|
|
631
638
|
if (this.isSelfAuthority(path)) {
|
|
632
639
|
return getOwnNodeId();
|
|
633
640
|
}
|
|
634
|
-
if (isClient()) {
|
|
635
|
-
return getBrowserUrlNode();
|
|
636
|
-
}
|
|
637
641
|
if (!this.waited) throw new Error(`waitUntilRoutingIsReady must be called before getSingleReadNode`);
|
|
638
642
|
let objs = this.getReadObjs(path);
|
|
639
643
|
return this.pickAuthorityNode(objs)?.nodeId;
|
|
@@ -654,9 +658,6 @@ class NodePathAuthorities {
|
|
|
654
658
|
if (this.isSelfAuthority(path)) {
|
|
655
659
|
return { nodes: [{ nodeId: getOwnNodeId(), range: { start: 0, end: 1 } }] };
|
|
656
660
|
}
|
|
657
|
-
if (isClient()) {
|
|
658
|
-
return { nodes: [{ nodeId: getBrowserUrlNode(), range: { start: 0, end: 1 } }] };
|
|
659
|
-
}
|
|
660
661
|
|
|
661
662
|
let wildcardPath = appendToPathStr(path, "");
|
|
662
663
|
|
|
@@ -749,9 +750,6 @@ class NodePathAuthorities {
|
|
|
749
750
|
// IMPORTANT! Even if we are the authority, this doesn't mean other nodes aren't also the authority,
|
|
750
751
|
// so we have to let it propagate to all nodes.
|
|
751
752
|
//if (this.isSelfAuthority(path)) return [getOwnNodeId()];
|
|
752
|
-
if (isClient()) {
|
|
753
|
-
return [getBrowserUrlNode()];
|
|
754
|
-
}
|
|
755
753
|
await this.waitUntilRoutingIsReady();
|
|
756
754
|
|
|
757
755
|
let result = Array.from(this.authorities.values())
|
|
@@ -5,7 +5,7 @@ import { cache } from "socket-function/src/caching";
|
|
|
5
5
|
import { isNode, nextId } from "socket-function/src/misc";
|
|
6
6
|
import { measureFnc, measureBlock } from "socket-function/src/profiling/measure";
|
|
7
7
|
import { isOwnNodeId, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
8
|
-
import { nodePathAuthority, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
8
|
+
import { LOCAL_DOMAIN_PATH, nodePathAuthority, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
9
9
|
import { PathValueController, pathValueCommitter } from "../0-path-value-core/PathValueController";
|
|
10
10
|
import { WatchConfig, authorityStorage, decodeParentFilter, matchesParentRangeFilter, pathWatcher } from "../0-path-value-core/pathValueCore";
|
|
11
11
|
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
@@ -151,9 +151,9 @@ export class RemoteWatcher {
|
|
|
151
151
|
return watchObj?.nodesId;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
private watchUnwatchSerial = runInSerial((name: string, fnc: () => Promise<unknown>) =>
|
|
155
|
-
fnc()
|
|
156
|
-
);
|
|
154
|
+
private watchUnwatchSerial = runInSerial(async (name: string, fnc: () => Promise<unknown>) => {
|
|
155
|
+
return await fnc();
|
|
156
|
+
});
|
|
157
157
|
|
|
158
158
|
/** NOTE: We dedupe duplicate watches in watchLatest. */
|
|
159
159
|
private async watchLatestBase(config: WatchConfig & { debugName?: string }) {
|
|
@@ -183,6 +183,9 @@ export class RemoteWatcher {
|
|
|
183
183
|
let totalMissingPaths = 0;
|
|
184
184
|
measureBlock(() => {
|
|
185
185
|
for (let path of config.paths) {
|
|
186
|
+
// NOTE: We weren't ignoring local paths here for a while, but... I'm pretty sure we should, to save time.
|
|
187
|
+
if (path.startsWith(LOCAL_DOMAIN_PATH)) continue;
|
|
188
|
+
|
|
186
189
|
// IMPORTANT! We have to await, otherwise we might see the paths are watched, skip them
|
|
187
190
|
// find another path which has an authority, then go to watch that... which causes
|
|
188
191
|
// us to circumvent earlier watchers, causing us to watch out of order, causing reads
|
|
@@ -229,6 +232,8 @@ export class RemoteWatcher {
|
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
for (let path of config.parentPaths) {
|
|
235
|
+
// NOTE: We weren't ignoring local paths here for a while, but... I'm pretty sure we should, to save time.
|
|
236
|
+
if (path.startsWith(LOCAL_DOMAIN_PATH)) continue;
|
|
232
237
|
let watchObj = this.remoteWatchParents.get(path);
|
|
233
238
|
if (
|
|
234
239
|
watchObj
|
|
@@ -42,6 +42,8 @@ import { onNextPaint } from "../functional/onNextPaint";
|
|
|
42
42
|
// With a harness that joins the two parts in a loop (mostly powered by clientWatcher,
|
|
43
43
|
// which can run the trigger loop).
|
|
44
44
|
|
|
45
|
+
const DEFAULT_MAX_LOCKS = 1000;
|
|
46
|
+
|
|
45
47
|
let nextOrderSeqNum = 1;
|
|
46
48
|
|
|
47
49
|
export interface WatcherOptions<Result> {
|
|
@@ -208,6 +210,8 @@ export interface WatcherOptions<Result> {
|
|
|
208
210
|
* - Also to tell us how long and how many paths are loading on startup.
|
|
209
211
|
*/
|
|
210
212
|
logSyncTimings?: boolean;
|
|
213
|
+
|
|
214
|
+
maxLocksOverride?: number;
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
let harvestableReadyLoopCount = 0;
|
|
@@ -1469,6 +1473,7 @@ export class PathValueProxyWatcher {
|
|
|
1469
1473
|
);
|
|
1470
1474
|
let newReadCheck = !!value.isTransparent;
|
|
1471
1475
|
if (oldReadCheck !== newReadCheck) {
|
|
1476
|
+
console.error(`Old read check ${oldReadCheck} !== newReadCheck ${newReadCheck} for ${value.path}, canGCValue: ${value.canGCValue}, isTransparent: ${value.isTransparent}`);
|
|
1472
1477
|
// IMPORTANT! IF WE HIT THIS ASSERT, DEBUG IT RIGHT NOW!
|
|
1473
1478
|
// - I updated the read check to use isTransparent. BUT... I'm not sure
|
|
1474
1479
|
// this is correct. This WILL break if we add any new isTransparent values,
|
|
@@ -1538,6 +1543,11 @@ export class PathValueProxyWatcher {
|
|
|
1538
1543
|
}
|
|
1539
1544
|
}
|
|
1540
1545
|
|
|
1546
|
+
let maxLocks = watcher.options.maxLocksOverride || DEFAULT_MAX_LOCKS;
|
|
1547
|
+
if (locks.length > maxLocks) {
|
|
1548
|
+
throw new Error(`Too many locks for ${watcher.debugName} (${locks.length} > ${maxLocks}). You can override max locks with maxLocksOverride (in options / functionMetadata).`);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1541
1551
|
setValues = clientWatcher.setValues({
|
|
1542
1552
|
values: watcher.pendingWrites,
|
|
1543
1553
|
eventPaths: watcher.pendingEventWrites,
|
|
@@ -2228,6 +2238,10 @@ export function noAtomicSchema<T>(code: () => T) {
|
|
|
2228
2238
|
|
|
2229
2239
|
|
|
2230
2240
|
export const proxyWatcher = new PathValueProxyWatcher();
|
|
2241
|
+
if ((globalThis as any).proxyWatcher) {
|
|
2242
|
+
debugger;
|
|
2243
|
+
console.error(`Loaded PathValueProxyWatcher twice. This will break this. In ${module.filename}`);
|
|
2244
|
+
}
|
|
2231
2245
|
(globalThis as any).proxyWatcher = proxyWatcher;
|
|
2232
2246
|
(globalThis as any).ProxyWatcher = PathValueProxyWatcher;
|
|
2233
2247
|
// Probably the most useful debug function for debugging watch based functions.
|
|
@@ -76,6 +76,8 @@ export type FunctionSpec = {
|
|
|
76
76
|
gitURL: string;
|
|
77
77
|
/** Ex, the hash */
|
|
78
78
|
gitRef: string;
|
|
79
|
+
|
|
80
|
+
maxLocksOverride?: number;
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
export interface CallSpec {
|
|
@@ -513,6 +515,7 @@ export class PathFunctionRunner {
|
|
|
513
515
|
getPermissionsCheck: PermissionsChecker && (() => new PermissionsChecker(callPath)),
|
|
514
516
|
nestedCalls: "inline",
|
|
515
517
|
temporary: true,
|
|
518
|
+
maxLocksOverride: functionSpec.maxLocksOverride,
|
|
516
519
|
watchFunction: function runCallWatcher() {
|
|
517
520
|
runCount++;
|
|
518
521
|
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
@@ -560,6 +563,7 @@ export class PathFunctionRunner {
|
|
|
560
563
|
|| syncedSpec.FilePath !== functionSpec.FilePath
|
|
561
564
|
|| syncedSpec.gitRef !== functionSpec.gitRef
|
|
562
565
|
|| syncedSpec.gitURL !== functionSpec.gitURL
|
|
566
|
+
|| syncedSpec.maxLocksOverride !== functionSpec.maxLocksOverride
|
|
563
567
|
)
|
|
564
568
|
) {
|
|
565
569
|
isFunctionSpecOutdated = true;
|
|
@@ -20,6 +20,7 @@ import path from "path";
|
|
|
20
20
|
import { isEmpty } from "../misc";
|
|
21
21
|
import { getSpecFromModule } from "./pathFunctionLoader";
|
|
22
22
|
import { isNode } from "typesafecss";
|
|
23
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
23
24
|
|
|
24
25
|
// This is the the function id which should be used when creating the FunctionSpec (in order to load the module),
|
|
25
26
|
// to access the permissions in the schema.
|
|
@@ -45,6 +46,9 @@ export type FunctionMetadata<F = unknown> = {
|
|
|
45
46
|
* before writing to B.
|
|
46
47
|
*/
|
|
47
48
|
delayCommit?: boolean;
|
|
49
|
+
|
|
50
|
+
/** Too many locks can lag the server, and eventually cause crashes. Consider using Querysub.noLocks(() => ...) around code that is accessing too many values, assuming you don't want to lock them. However, if absolutely required, you can override max locks to allow as many locks to be created as you want until the server crashses... */
|
|
51
|
+
maxLocksOverride?: number;
|
|
48
52
|
};
|
|
49
53
|
|
|
50
54
|
export interface SchemaObject<Schema = any, Functions = any> {
|
|
@@ -25,9 +25,6 @@ export async function deployGetFunctions(): Promise<FunctionSpec[]> {
|
|
|
25
25
|
return functionSpecs;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
//todonext;
|
|
29
|
-
// Ugh... some kind of throttling? Nah...
|
|
30
|
-
// So... progress sections, just... start and stop?
|
|
31
28
|
export type DeployProgress = {
|
|
32
29
|
(config: { section: string; progress: number; }): void;
|
|
33
30
|
};
|
|
@@ -76,6 +76,7 @@ async function main() {
|
|
|
76
76
|
const functionIds = Object.keys(schema.rawFunctions);
|
|
77
77
|
functionIds.push(PERMISSIONS_FUNCTION_ID);
|
|
78
78
|
for (let functionId of functionIds) {
|
|
79
|
+
let metadata = schema.functionMetadata?.[functionId];
|
|
79
80
|
const exportPathStr = getExportPath(functionId);
|
|
80
81
|
let spec: FunctionSpec = {
|
|
81
82
|
DomainName: domainName,
|
|
@@ -85,6 +86,7 @@ async function main() {
|
|
|
85
86
|
exportPathStr,
|
|
86
87
|
gitURL,
|
|
87
88
|
gitRef,
|
|
89
|
+
maxLocksOverride: metadata?.maxLocksOverride,
|
|
88
90
|
};
|
|
89
91
|
console.log(`FUNCTION_SPEC:${JSON.stringify(spec)}`);
|
|
90
92
|
}
|
|
@@ -14,6 +14,9 @@ export const liveHashOverrideURL = new URLParam(liveHashOverrideParam, undefined
|
|
|
14
14
|
time: number;
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
declare global {
|
|
18
|
+
var getEdgeNodeConfig: undefined | (() => Promise<EdgeNodeConfig>);
|
|
19
|
+
}
|
|
17
20
|
|
|
18
21
|
let getCachedConfig = cache(async (url: string): Promise<EdgeNodesIndex | undefined> => {
|
|
19
22
|
setTimeout(() => {
|
|
@@ -218,6 +221,8 @@ async function edgeNodeFunction(config: {
|
|
|
218
221
|
};
|
|
219
222
|
}
|
|
220
223
|
|
|
224
|
+
globalThis.getEdgeNodeConfig = getEdgeNodeConfig;
|
|
225
|
+
|
|
221
226
|
let cachedConfig = config.cachedConfig;
|
|
222
227
|
|
|
223
228
|
let liveHashOverride = "";
|
|
@@ -2,10 +2,10 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
2
2
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
3
3
|
import { deploySchema } from "./deploySchema";
|
|
4
4
|
import { getDomain, noSyncing } from "../config";
|
|
5
|
-
import { throttleFunction, timeInMinute } from "socket-function/src/misc";
|
|
5
|
+
import { throttleFunction, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
6
6
|
import { isNode } from "typesafecss";
|
|
7
7
|
import { logErrors, timeoutToError } from "../errors";
|
|
8
|
-
import { blue, red } from "socket-function/src/formatting/logColors";
|
|
8
|
+
import { blue, red, yellow } from "socket-function/src/formatting/logColors";
|
|
9
9
|
import { showModal } from "../5-diagnostics/Modal";
|
|
10
10
|
import { qreact } from "../4-dom/qreact";
|
|
11
11
|
import { liveHashOverrideURL } from "./edgeBootstrap";
|
|
@@ -13,6 +13,11 @@ import { css } from "typesafecss";
|
|
|
13
13
|
import { formatNiceDateTime, formatTime } from "socket-function/src/formatting/format";
|
|
14
14
|
import { delay } from "socket-function/src/batching";
|
|
15
15
|
import { atomicObjectRead } from "../2-proxy/PathValueProxyWatcher";
|
|
16
|
+
import { lazy } from "socket-function/src/caching";
|
|
17
|
+
import { Button } from "../library-components/Button";
|
|
18
|
+
import { updateRootDiscoveryLocation } from "../-f-node-discovery/NodeDiscovery";
|
|
19
|
+
|
|
20
|
+
const SWITCH_SERVER_TIMEOUT = timeInSecond * 15;
|
|
16
21
|
|
|
17
22
|
let lastHashServer = "";
|
|
18
23
|
export function startEdgeNotifier() {
|
|
@@ -29,7 +34,7 @@ export function startEdgeNotifier() {
|
|
|
29
34
|
}
|
|
30
35
|
void notifyClients(liveHash, refreshThresholdTime);
|
|
31
36
|
});
|
|
32
|
-
(async () => {
|
|
37
|
+
void (async () => {
|
|
33
38
|
await delay(1);
|
|
34
39
|
let { watchOnRollingUpdate } = await import("../deployManager/machineController");
|
|
35
40
|
watchOnRollingUpdate({
|
|
@@ -119,7 +124,7 @@ function onLiveHashChange(liveHash: string, refreshThresholdTime: number) {
|
|
|
119
124
|
>
|
|
120
125
|
<div className={css.vbox(10).maxWidth(250)}>
|
|
121
126
|
<h3 className={css.margin(0)}>Server Update Available</h3>
|
|
122
|
-
<div>The server has been updated. Please refresh the page to ensure you don't experience
|
|
127
|
+
<div>The server has been updated. Please refresh the page to ensure you don't experience compatibility issues.</div>
|
|
123
128
|
{i !== notifyIntervals.length - 2 && <div>This notification will be shown again at {formatNiceDateTime(Date.now() + waitDuration)}</div>}
|
|
124
129
|
{i === notifyIntervals.length - 2 && <div>The page will automatically refresh at {formatNiceDateTime(Date.now() + waitDuration)}</div>}
|
|
125
130
|
</div>
|
|
@@ -168,6 +173,7 @@ function onLiveHashChange(liveHash: string, refreshThresholdTime: number) {
|
|
|
168
173
|
})();
|
|
169
174
|
}
|
|
170
175
|
|
|
176
|
+
|
|
171
177
|
const EdgeNotifierClientController = SocketFunction.register(
|
|
172
178
|
"EdgeNotifierClientController-ed122e41-6ad2-4161-9492-a3f8414bd4f0",
|
|
173
179
|
new class EdgeNotifierClient {
|
|
@@ -188,10 +194,37 @@ if (!isNode()) {
|
|
|
188
194
|
setImmediate(() => {
|
|
189
195
|
void Querysub.optionalStartupWait().finally(async () => {
|
|
190
196
|
curHash = await EdgeNotifierController.nodes[SocketFunction.browserNodeId()].watchUpdates();
|
|
197
|
+
void runRefreshLoop();
|
|
191
198
|
});
|
|
192
199
|
SocketFunction.expose(EdgeNotifierClientController);
|
|
193
200
|
});
|
|
194
201
|
}
|
|
202
|
+
const runRefreshLoop = lazy(async () => {
|
|
203
|
+
let getEdgeNodeConfig = globalThis.getEdgeNodeConfig;
|
|
204
|
+
if (!getEdgeNodeConfig) throw new Error(`Missing getEdgeNodeConfig. This means we can't reconnect if the server disconnects. Did we not inject the edge node detection code into the bootstrapper?`);
|
|
205
|
+
let lastAliveTime = Date.now();
|
|
206
|
+
let doNotRefresh = false;
|
|
207
|
+
while (true) {
|
|
208
|
+
await delay(3000);
|
|
209
|
+
if (doNotRefresh) break;
|
|
210
|
+
let timeSinceLastAlive = Date.now() - lastAliveTime;
|
|
211
|
+
if (SocketFunction.isNodeConnected(SocketFunction.browserNodeId())) {
|
|
212
|
+
lastAliveTime = Date.now();
|
|
213
|
+
} else if (timeSinceLastAlive > SWITCH_SERVER_TIMEOUT) {
|
|
214
|
+
lastAliveTime = Date.now();
|
|
215
|
+
console.log(yellow(`Server timed out, finding new server`));
|
|
216
|
+
try {
|
|
217
|
+
let config = await getEdgeNodeConfig();
|
|
218
|
+
updateRootDiscoveryLocation(config.host);
|
|
219
|
+
} catch (e: any) {
|
|
220
|
+
console.warn(`Failed to find new edge node, trying again in 15 seconds: ${e.message}`);
|
|
221
|
+
await delay(15000);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
console.log(yellow(`No edge node connected, trying again in 3 seconds`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
195
228
|
|
|
196
229
|
class EdgeNotifierControllerBase {
|
|
197
230
|
public async watchUpdates() {
|
|
@@ -17,7 +17,7 @@ import { getHostedIP, getSNICerts, publishMachineARecords } from "../-e-certs/Ed
|
|
|
17
17
|
import { LOCAL_DOMAIN, nodePathAuthority } from "../0-path-value-core/NodePathAuthorities";
|
|
18
18
|
import { debugCoreMode, registerGetCompressNetwork, encodeParentFilter, registerGetCompressDisk, authorityStorage } from "../0-path-value-core/pathValueCore";
|
|
19
19
|
import { clientWatcher, ClientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
20
|
-
import { SyncWatcher, proxyWatcher, specialObjectWriteValue, isSynced, PathValueProxyWatcher, atomic, doAtomicWrites, noAtomicSchema, undeleteFromLookup, registerSchemaPrefix, WatcherOptions } from "../2-proxy/PathValueProxyWatcher";
|
|
20
|
+
import { SyncWatcher, proxyWatcher, specialObjectWriteValue, isSynced, PathValueProxyWatcher, atomic, doAtomicWrites, noAtomicSchema, undeleteFromLookup, registerSchemaPrefix, WatcherOptions, doProxyOptions } from "../2-proxy/PathValueProxyWatcher";
|
|
21
21
|
import { isInProxyDatabase, rawSchema } from "../2-proxy/pathDatabaseProxyBase";
|
|
22
22
|
import { isValueProxy2, getProxyPath } from "../2-proxy/pathValueProxy";
|
|
23
23
|
import { getCurrentCallAllowUndefined, getCurrentCall, CallSpec, PathFunctionRunner } from "../3-path-functions/PathFunctionRunner";
|
|
@@ -438,6 +438,10 @@ export class Querysub {
|
|
|
438
438
|
onNestedSynced(callback);
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
+
public static noLocks = (callback: () => void) => {
|
|
442
|
+
doProxyOptions({ noLocks: true }, callback);
|
|
443
|
+
};
|
|
444
|
+
|
|
441
445
|
/** A more powerful version of omCommitFinished, which even waits for call predictions (or tries to).
|
|
442
446
|
* - Also see afterPredictionsSynced, which runs the callback in a write.
|
|
443
447
|
* NOTE: IDEALLY, you would just call a series of synced functions. They will be run in order,
|
|
@@ -34,7 +34,7 @@ import { PromiseObj } from "../promise";
|
|
|
34
34
|
import { LoggingClient } from "../0-path-value-core/LoggingClient";
|
|
35
35
|
import * as prediction from "./querysubPrediction";
|
|
36
36
|
import { getCallResultPath } from "./querysubPrediction";
|
|
37
|
-
import { pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
37
|
+
import { nodePathAuthority, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
38
38
|
import { diskLog } from "../diagnostics/logs/diskLogger";
|
|
39
39
|
import { assertIsManagementUser } from "../diagnostics/managementPages";
|
|
40
40
|
import { getBrowserUrlNode } from "../-f-node-discovery/NodeDiscovery";
|
|
@@ -43,7 +43,7 @@ setFlag(require, "preact", "allowclient", true);
|
|
|
43
43
|
import yargs from "yargs";
|
|
44
44
|
import { mergeFilterables, parseFilterable, serializeFilterable } from "../misc/filterable";
|
|
45
45
|
import { isManagementUser, onAllPredictionsFinished } from "../-0-hooks/hooks";
|
|
46
|
-
import { isBootstrapOnly, isLocal } from "../config";
|
|
46
|
+
import { getDomain, isBootstrapOnly, isLocal } from "../config";
|
|
47
47
|
|
|
48
48
|
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
49
49
|
.option("fncfilter", { type: "string", default: "", desc: `Sets the filterable state for function calls, causing them to target specific FunctionRunners. If no FunctionRunner matches, all functions will fail to run. For example: "devtestserver" will match a FunctionRunner that uses the "devtestserver" filter. Merges with the existing filterable state if a client sets it explicitly.` })
|
|
@@ -55,40 +55,29 @@ const getFncFilter = lazy(() => parseFilterable(yargObj.fncfilter));
|
|
|
55
55
|
export { Querysub, id };
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// very lightweight, hopefully...)
|
|
63
|
-
// - I guess we track our watches, but even that... should be fairly light, and... there
|
|
64
|
-
// is little benefit to sharding that?
|
|
65
|
-
export const querysubNodeId = lazy(() => {
|
|
66
|
-
if (isNode()) {
|
|
67
|
-
return getControllerNodeId(QuerysubController);
|
|
58
|
+
// TODO: Eventually this will be split by domain, which required nodeIds to be split by domain, so that getControllerNodeId can check nodeIds on a specific domain.
|
|
59
|
+
export async function querysubNodeId() {
|
|
60
|
+
if (isClient()) {
|
|
61
|
+
return nodePathAuthority.getSingleReadNodePromise(getPathStr1(getDomain()));
|
|
68
62
|
} else {
|
|
69
|
-
|
|
70
|
-
// developers), so... just use the browser url.
|
|
71
|
-
return getBrowserUrlNode();
|
|
63
|
+
return getControllerNodeId(QuerysubController);
|
|
72
64
|
}
|
|
73
|
-
}
|
|
65
|
+
}
|
|
66
|
+
|
|
74
67
|
|
|
75
68
|
setImmediate(() => {
|
|
76
|
-
async function querysubWatchLatest(config: WatchConfig) {
|
|
77
|
-
|
|
78
|
-
if (!nodeId) throw new Error("No querysub node found");
|
|
79
|
-
await QuerysubController.nodes[nodeId].watch(config);
|
|
69
|
+
async function querysubWatchLatest(config: WatchConfig, authorityId: string) {
|
|
70
|
+
await QuerysubController.nodes[authorityId].watch(config);
|
|
80
71
|
}
|
|
81
|
-
async function querysubUnwatchLatest(config: WatchConfig) {
|
|
82
|
-
|
|
83
|
-
if (!nodeId) throw new Error("No querysub node found");
|
|
84
|
-
await QuerysubController.nodes[nodeId].unwatch(config);
|
|
72
|
+
async function querysubUnwatchLatest(config: WatchConfig, authorityId: string) {
|
|
73
|
+
await QuerysubController.nodes[authorityId].unwatch(config);
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
let baseRemoteWatchFunction = RemoteWatcher.REMOTE_WATCH_FUNCTION;
|
|
88
77
|
RemoteWatcher.REMOTE_WATCH_FUNCTION = async (config, authorityId) => {
|
|
89
78
|
let weTrusted = isServer() && await errorToUndefined(isTrustedByNode(authorityId));
|
|
90
79
|
if (!weTrusted) {
|
|
91
|
-
await querysubWatchLatest(config);
|
|
80
|
+
await querysubWatchLatest(config, authorityId);
|
|
92
81
|
} else {
|
|
93
82
|
await baseRemoteWatchFunction(config, authorityId);
|
|
94
83
|
}
|
|
@@ -98,7 +87,7 @@ setImmediate(() => {
|
|
|
98
87
|
RemoteWatcher.REMOTE_UNWATCH_FUNCTION = async (config, authorityIdBase) => {
|
|
99
88
|
let weTrusted = isServer() && await isTrustedByNode(authorityIdBase);
|
|
100
89
|
if (!weTrusted) {
|
|
101
|
-
logErrors(querysubUnwatchLatest(config));
|
|
90
|
+
logErrors(querysubUnwatchLatest(config, authorityIdBase));
|
|
102
91
|
} else {
|
|
103
92
|
logErrors(baseRemoteUnwatchFunction(config, authorityIdBase));
|
|
104
93
|
}
|
|
@@ -313,7 +313,7 @@ async function auditSpecificPathsBase(config: {
|
|
|
313
313
|
}
|
|
314
314
|
sort(logObjs, x => x.log.time);
|
|
315
315
|
let now = Date.now();
|
|
316
|
-
for (let { source, log } of logObjs) {
|
|
316
|
+
for (let { source, log } of logObjs.slice(-100)) {
|
|
317
317
|
console.log(`${source.padEnd(8, " ")} ${log.type.padEnd(20, " ")}`, now - log.time, log.values, log.time);
|
|
318
318
|
}
|
|
319
319
|
debugbreak(2);
|
|
@@ -367,6 +367,14 @@ const runScreenCommand = measureWrap(async function runScreenCommand(config: {
|
|
|
367
367
|
} else {
|
|
368
368
|
console.log(red(`No nodeId file found for ${screenName}, not triggering rolling update notification`));
|
|
369
369
|
}
|
|
370
|
+
// Stop the pipe-pane, and delete the pipe file
|
|
371
|
+
try {
|
|
372
|
+
await runPromise(`${prefix}tmux pipe-pane -O -t ${screenName}`);
|
|
373
|
+
await fs.promises.unlink(os.homedir() + "/" + SERVICE_FOLDER + screenName + "/pipe.txt");
|
|
374
|
+
} catch (e: any) {
|
|
375
|
+
console.warn(`Error stopping pipe-pane for ${screenName}: ${e.stack}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
370
378
|
let rollingScreenName = screenName + "-rolling" + SCREEN_SUFFIX;
|
|
371
379
|
console.log(green(`Renaming screen ${screenName} to ${rollingScreenName} for rolling interval ${config.rollingWindow} at ${new Date().toLocaleString()}`));
|
|
372
380
|
await runPromise(`${prefix}tmux rename-session -t ${screenName} ${rollingScreenName}`);
|
|
@@ -106,7 +106,7 @@ async function registerNodeForMachineCleanup(nodeId: string) {
|
|
|
106
106
|
console.log(green(`Registering node for machine cleanup at ${nodeIdPath}`));
|
|
107
107
|
await fs.promises.writeFile(nodeIdPath, nodeId);
|
|
108
108
|
} else {
|
|
109
|
-
console.log(
|
|
109
|
+
console.log(`Not registering node for machine cleanup because we are not in the service folder: ${currentPath}`);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
1)
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// updateRootDiscoveryLocation("127-0-0-1.querysubtest.com:7008")
|
|
2
|
+
|
|
3
|
+
Get updateRootDiscoveryLocation called when we disconnect, and don't reconnect?
|
|
4
|
+
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
1) Redeploy everything to latest
|
|
8
|
+
3) Update cyoa rolling time back to 4 hours
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
9) In machine info, support an object which can show current, and max, so we can show it as a bar and the max amount for:
|
|
@@ -19,5 +20,4 @@
|
|
|
19
20
|
(Yes, it overlaps with node metrics, but that's okay, sometimes we want to look at machines, other times services, and the types of errors that show up in either changes).
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
11)
|
|
23
|
-
- Or... screw it, and move onto audio apis...
|
|
23
|
+
11) A spinning animation on the FPS display, so we can visually see when the browser locks up (because the animation will stop?)
|
|
@@ -344,7 +344,8 @@ export class ErrorLogControllerBase {
|
|
|
344
344
|
if (ErrorLogControllerBase.logIssueNotifyEquals(prev, notify)) {
|
|
345
345
|
return;
|
|
346
346
|
}
|
|
347
|
-
|
|
347
|
+
// Don't log when we receive it, otherwise errors show up on all servers which is very confusing and annoying!
|
|
348
|
+
//console.log(magenta(`Received log notify from ${caller.nodeId} for ${notify?.oldestUngrouped?.message}`));
|
|
348
349
|
ErrorLogControllerBase.liveLogsIssueNotify.set(caller.nodeId, notify);
|
|
349
350
|
logErrors(ErrorLogControllerBase.recalcAllNotify());
|
|
350
351
|
}
|
|
@@ -89,9 +89,14 @@ export class ObjectDisplay extends qreact.Component<{
|
|
|
89
89
|
</>;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
function log() {
|
|
93
|
+
(globalThis as any).debugValue = value;
|
|
94
|
+
console.log("debugValue=", value);
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
if (!canHaveChildren(value)) {
|
|
93
98
|
return <ShowMore maxHeight={maxHeight} className={css.maxWidth("50vw")}>
|
|
94
|
-
<span className={css.maxHeight("70vh").overflowAuto}>
|
|
99
|
+
<span className={css.maxHeight("70vh").overflowAuto} onClick={log}>
|
|
95
100
|
<div class={css.relative}>
|
|
96
101
|
{renderValue(value, [])}
|
|
97
102
|
<MeasureHeightCSS />
|
|
@@ -103,7 +108,7 @@ export class ObjectDisplay extends qreact.Component<{
|
|
|
103
108
|
css.hbox(6).alignItems("start")
|
|
104
109
|
+ (!expanded && css.maxHeight(maxHeight).overflowHidden)
|
|
105
110
|
+ (expanded && css.maxHeight("70vh").overflowAuto)
|
|
106
|
-
}>
|
|
111
|
+
} onClick={log}>
|
|
107
112
|
{canHaveChildren(value) && <Button
|
|
108
113
|
className={
|
|
109
114
|
css.hsl(270, 50, 50).background("hsl(270, 50%, 70%)", "hover")
|
|
@@ -135,7 +140,7 @@ export class PrimitiveDisplay extends qreact.Component<{
|
|
|
135
140
|
return <span className={css.boldStyle.hslcolor(265, 70, 60)}>{value}</span>;
|
|
136
141
|
}
|
|
137
142
|
if (typeof value === "boolean") {
|
|
138
|
-
return <span className={css.boldStyle.hslcolor(207, 70, 60)}>{value}</span>;
|
|
143
|
+
return <span className={css.boldStyle.hslcolor(207, 70, 60)}>{JSON.stringify(value)}</span>;
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
if (typeof value === "string") {
|
|
@@ -318,7 +318,7 @@ class ManagementRoot extends qreact.Component {
|
|
|
318
318
|
}
|
|
319
319
|
`}
|
|
320
320
|
</style>
|
|
321
|
-
<div class={css.fillWidth.hbox(30).wrap.hsl(245, 25, 60).pad2(10).pointerEvents("all")}>
|
|
321
|
+
<div class={css.fillWidth.hbox(30, 10).wrap.hsl(245, 25, 60).pad2(10).pointerEvents("all")}>
|
|
322
322
|
<LogNotify />
|
|
323
323
|
{pages.map(page =>
|
|
324
324
|
<ATag values={[{ param: managementPageURL, value: page.componentName }]}>{page.title}</ATag>
|