querysub 0.406.0 → 0.408.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 (55) hide show
  1. package/bin/audit-disk-values.js +7 -0
  2. package/bin/deploy-prefixes.js +7 -0
  3. package/package.json +5 -3
  4. package/src/-a-archives/archiveCache.ts +12 -9
  5. package/src/-a-auth/certs.ts +1 -1
  6. package/src/-c-identity/IdentityController.ts +9 -1
  7. package/src/-f-node-discovery/NodeDiscovery.ts +63 -10
  8. package/src/0-path-value-core/AuthorityLookup.ts +14 -4
  9. package/src/0-path-value-core/PathRouter.ts +247 -117
  10. package/src/0-path-value-core/PathRouterRouteOverride.ts +1 -1
  11. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +4 -2
  12. package/src/0-path-value-core/PathValueCommitter.ts +68 -31
  13. package/src/0-path-value-core/PathValueController.ts +77 -8
  14. package/src/0-path-value-core/PathWatcher.ts +46 -4
  15. package/src/0-path-value-core/ShardPrefixes.ts +6 -0
  16. package/src/0-path-value-core/ValidStateComputer.ts +20 -8
  17. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +18 -55
  18. package/src/0-path-value-core/pathValueArchives.ts +19 -8
  19. package/src/0-path-value-core/pathValueCore.ts +75 -27
  20. package/src/0-path-value-core/startupAuthority.ts +9 -9
  21. package/src/1-path-client/RemoteWatcher.ts +217 -178
  22. package/src/1-path-client/pathValueClientWatcher.ts +6 -11
  23. package/src/2-proxy/pathValueProxy.ts +2 -3
  24. package/src/3-path-functions/PathFunctionRunner.ts +3 -1
  25. package/src/3-path-functions/syncSchema.ts +6 -2
  26. package/src/4-deploy/deployGetFunctionsInner.ts +1 -1
  27. package/src/4-deploy/deployPrefixes.ts +14 -0
  28. package/src/4-deploy/edgeNodes.ts +1 -1
  29. package/src/4-querysub/Querysub.ts +17 -5
  30. package/src/4-querysub/QuerysubController.ts +21 -10
  31. package/src/4-querysub/predictionQueue.tsx +3 -0
  32. package/src/4-querysub/querysubPrediction.ts +27 -20
  33. package/src/5-diagnostics/nodeMetadata.ts +17 -0
  34. package/src/diagnostics/NodeConnectionsPage.tsx +167 -0
  35. package/src/diagnostics/NodeViewer.tsx +11 -15
  36. package/src/diagnostics/PathDistributionInfo.tsx +102 -0
  37. package/src/diagnostics/SyncTestPage.tsx +19 -8
  38. package/src/diagnostics/auditDiskValues.ts +221 -0
  39. package/src/diagnostics/auditDiskValuesEntry.ts +43 -0
  40. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +5 -1
  41. package/src/diagnostics/logs/TimeRangeSelector.tsx +3 -3
  42. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +2 -0
  43. package/src/diagnostics/managementPages.tsx +10 -1
  44. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +3 -2
  45. package/src/diagnostics/pathAuditer.ts +21 -0
  46. package/src/path.ts +9 -2
  47. package/src/rangeMath.ts +41 -0
  48. package/tempnotes.txt +5 -58
  49. package/test.ts +13 -295
  50. package/src/diagnostics/benchmark.ts +0 -139
  51. package/src/diagnostics/runSaturationTest.ts +0 -416
  52. package/src/diagnostics/satSchema.ts +0 -64
  53. package/src/test/mongoSatTest.tsx +0 -55
  54. package/src/test/satTest.ts +0 -193
  55. package/src/test/test.tsx +0 -552
@@ -21,6 +21,7 @@ import { getSpecFromModule } from "./pathFunctionLoader";
21
21
  import { isNode } from "typesafecss";
22
22
  import { LOCAL_DOMAIN } from "../0-path-value-core/PathRouter";
23
23
  import { createRoutingOverrideKey } from "../0-path-value-core/PathRouterRouteOverride";
24
+ import { delay } from "socket-function/src/batching";
24
25
 
25
26
  // This is the the function id which should be used when creating the FunctionSpec (in order to load the module),
26
27
  // to access the permissions in the schema.
@@ -286,7 +287,9 @@ type SyncSchemaResult<Schema> = {
286
287
 
287
288
  let prefixes: string[] = [];
288
289
  // ONLY used by the deploy process. Path value server is pretty much the only one who needs this, and they aren't including client-side code, and they definitely aren't updating client-side code, so it doesn't make sense to use this directly.
289
- export function getPrefixesForDeploy(): string[] {
290
+ export async function getPrefixesForDeploy(): Promise<string[]> {
291
+ await new Promise(r => setImmediate(r));
292
+ await new Promise(r => setImmediate(r));
290
293
  return prefixes;
291
294
  }
292
295
  // Used for some old non syncSchema schema cases
@@ -302,7 +305,8 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
302
305
  const domainName = config.domainName ?? getDomain();
303
306
 
304
307
  const data = () => functionSchema()[domainName].PathFunctionRunner[moduleId].Data;
305
- let dataCached = lazy(() => data()) as () => Schema;
308
+ // NOTE: We used to cache this, but then they would cache the current database that's inside of the schema, which is dynamically set. So I don't think caching this really makes sense. We might be able to do some kind of intelligent cache where we check what the current database is and only reuse the cache if that's the same.
309
+ let dataCached = (() => data()) as () => Schema;
306
310
 
307
311
  let fncs = { ...functions };
308
312
 
@@ -94,7 +94,7 @@ async function main() {
94
94
  }
95
95
  }
96
96
 
97
- for (let prefix of getPrefixesForDeploy()) {
97
+ for (let prefix of await getPrefixesForDeploy()) {
98
98
  console.log(`PREFIX:${prefix}`);
99
99
  }
100
100
  }
@@ -0,0 +1,14 @@
1
+ import { setShardPrefixes } from "../0-path-value-core/ShardPrefixes";
2
+ import { getPrefixesForDeploy } from "../3-path-functions/syncSchema";
3
+ import path from "path";
4
+
5
+ async function main() {
6
+ let deployPath = path.resolve("./deploy.ts");
7
+ await import(deployPath);
8
+
9
+ let prefixes = await getPrefixesForDeploy();
10
+ await setShardPrefixes(prefixes);
11
+ }
12
+
13
+ main().catch(console.error).finally(() => process.exit());
14
+
@@ -273,7 +273,7 @@ async function updateEdgeNodesFile() {
273
273
  let liveHash = "";
274
274
  if (!noSyncing()) {
275
275
  liveHash = await Querysub.commitSynced(() => {
276
- return deploySchema()[getDomain()].deploy.live.hash;
276
+ return String(deploySchema()[getDomain()].deploy.live.hash);
277
277
  });
278
278
  }
279
279
 
@@ -285,10 +285,10 @@ export class Querysub {
285
285
  */
286
286
  @measureFnc
287
287
  public static async optionalStartupWait() {
288
+ await authorityLookup.startSyncing();
288
289
  await measureBlock(async () => {
289
290
  await waitForFirstTimeSync();
290
291
  }, "waitForFirstTimeSync");
291
- await authorityLookup.startSyncing();
292
292
  }
293
293
 
294
294
  public static createWatcher(watcher: (obj: SyncWatcher) => void, options?: Partial<WatcherOptions<unknown>>): {
@@ -714,6 +714,10 @@ export class Querysub {
714
714
  })();
715
715
  }
716
716
 
717
+ private static socketFunctionInit() {
718
+ SocketFunction.addGlobalHook(() => authorityLookup.startSyncing());
719
+ }
720
+
717
721
  private static hostCalled = false;
718
722
  private static afterBootImports: string[] = [];
719
723
  /** NOTE: Imports occur one at a time, after the "clientsideEntryPoint", with each import waiting for the previous to finish.
@@ -744,6 +748,7 @@ export class Querysub {
744
748
  sourceCheck?: MachineSourceCheck<any>;
745
749
  }) {
746
750
  console.log(`Hosting server with config: ${JSON.stringify(config)}`);
751
+ this.socketFunctionInit();
747
752
 
748
753
  if (isClient()) {
749
754
  throw new Error(`--client processes cannot host a service. Either stop passing --client and keep the process on the network and trusted, or stop calling hostServer and call Querysub.configRootDiscoveryLocation instead.`);
@@ -1021,6 +1026,8 @@ export class Querysub {
1021
1026
  RequireController.addMapGetModules(async (result, args) => {
1022
1027
  let configObj = args[2] as { signedIdentity: SignedIdentity | undefined } | undefined;
1023
1028
  if (!await isAllowedToSeeSource(configObj?.signedIdentity)) {
1029
+ require("debugbreak")(2);
1030
+ debugger;
1024
1031
  await isAllowedToSeeSource(configObj?.signedIdentity);
1025
1032
  //console.log(red(`Not allowed to see source`));
1026
1033
  for (let [key, value] of Object.entries(result.modules)) {
@@ -1038,6 +1045,7 @@ export class Querysub {
1038
1045
  throw new Error(`--client processes cannot host a service. Either stop passing --client and keep the process on the network and trusted, or stop calling hostServer and call Querysub.configRootDiscoveryLocation instead.`);
1039
1046
  }
1040
1047
 
1048
+ this.socketFunctionInit();
1041
1049
  await SocketFunction.mount({
1042
1050
  public: isPublic(),
1043
1051
  port,
@@ -1062,10 +1070,14 @@ export class Querysub {
1062
1070
  */
1063
1071
  endFraction?: number;
1064
1072
  }): (keyof T)[] {
1065
- if (!isNode()) {
1066
- console.warn(`keys() is unlikely to work clientside. If we need to emulate how the server does the filtering, we should add an additional function to help with that. But there are issues with how query sub proxies watches, which make proxying partial key watches too difficult. ALSO, this is mostly for watching very large data streams with dynamic sharding (in FunctionRunner), and so shouldn't be needed clientside (the application can handle filtering at a schema level if it really wants something similar to this).`);
1067
- }
1073
+
1068
1074
  let { startFraction, endFraction } = config || {};
1075
+ if ((startFraction === undefined) !== (endFraction === undefined)) {
1076
+ throw new Error(`startFraction and endFraction must both be provided, or both be undefined. If you want to get all keys, don't provide startFraction and endFraction.`);
1077
+ }
1078
+ if (!isNode() && startFraction !== undefined && endFraction !== undefined) {
1079
+ console.warn(`keys() with a range restriction is not supported clientside. It's too complicated for the proxy to handle it because the hashing depends on the authority server. You can synchronize all the keys client side, but you can't synchronize a restriction of the keys.`);
1080
+ }
1069
1081
  if (startFraction === undefined || endFraction === undefined) {
1070
1082
  return Object.keys(obj);
1071
1083
  }
@@ -1297,6 +1309,7 @@ setImmediate(async () => {
1297
1309
  // Import, so it registers addStatPeriodic
1298
1310
  await import("../5-diagnostics/nodeMetadata");
1299
1311
  await import("../diagnostics/pathAuditer");
1312
+ await import("../diagnostics/NodeConnectionsPage");
1300
1313
  });
1301
1314
 
1302
1315
  setImmediate(async () => {
@@ -1312,7 +1325,6 @@ registerGetCompressNetwork(() => Querysub.COMPRESS_NETWORK);
1312
1325
 
1313
1326
  import "../diagnostics/watchdog";
1314
1327
  import "../diagnostics/trackResources";
1315
- import "../diagnostics/benchmark";
1316
1328
  import { formatNumber, formatTime } from "socket-function/src/formatting/format";
1317
1329
  import { getCountPerPaint } from "../functional/onNextPaint";
1318
1330
  import { addEpsilons } from "../bits";
@@ -2,7 +2,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { cache, lazy } from "socket-function/src/caching";
3
3
  import { appendToPathStr, getPathDepth, getPathStr1, getPathStr3 } from "../path";
4
4
  import { FunctionMetadata } from "../3-path-functions/syncSchema";
5
- import { RemoteWatcher } from "../1-path-client/RemoteWatcher";
5
+ import { RemoteWatcher, remoteWatcher } from "../1-path-client/RemoteWatcher";
6
6
  import { getProxyPath } from "../2-proxy/pathValueProxy";
7
7
  import { atomic, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
8
8
  import { CallSpec, DEPTH_TO_DATA, commitCall, debugCallSpec, functionSchema, getDevFunctionSpecFromCall } from "../3-path-functions/PathFunctionRunner";
@@ -200,6 +200,7 @@ writeCall.value = async (callSpec: CallSpec, metadata: FunctionMetadata) => {
200
200
  });
201
201
 
202
202
  if (result === "run") {
203
+ obj.onApplied();
203
204
  // NOTE: I think awaiting here is fine, as the the prediction blockers don't wait based on the right call finishing, and I don't think anything really waits for this to finish.
204
205
  await baseAddCall(callSpec);
205
206
  } else {
@@ -533,6 +534,7 @@ export class QuerysubControllerBase {
533
534
  pathWatcher.unwatchPath({ callback: callerId, ...config });
534
535
  }
535
536
 
537
+ // NOTE: Calls are going to be temporary and random. Any user can use any call ID, so technically you could clobber other users' call IDs, or your our. There wouldn't really be any benefit. Nothing would really happen if you do that, so I don't believe these need to be kept secret. I think if you know someone else's call ID you might be able to read that data, but also it's securely random, so you're not going to be able to guess the call ID.
536
538
  public async addCall(call: CallSpec) {
537
539
  if (isBootstrapOnly()) throw new Error(`Cannot add calls to bootstrap only server`);
538
540
  if (Querysub.DEBUG_CALLS) {
@@ -661,19 +663,28 @@ export class QuerysubControllerBase {
661
663
  }
662
664
 
663
665
 
666
+ public async debugGetPathNodeIds(paths: string[]): Promise<Map<string, string | "missing">> {
667
+ await remoteWatcher.flushWatchers();
668
+ let result = new Map<string, string | "missing">();
669
+ for (let path of paths) {
670
+ let nodeId = remoteWatcher.getExistingWatchRemoteNodeId(path) ?? PathRouter.getReadyAuthority(path)?.nodeId ?? "missing";
671
+ result.set(path, nodeId);
672
+ }
673
+ return result;
674
+ }
675
+
664
676
  // NOTE: Maybe remove this? It's very useful for debugging though...
665
677
  public async debugGetSingleReadNode(path: string) {
666
678
  return PathRouter.getReadyAuthority(path)?.nodeId;
667
679
  }
668
680
 
669
- public async debugGetPathAuthorities(nodeId: string) {
670
- let authorities = await authorityLookup.getTopology();
671
- return authorities.find(x => x.nodeId === nodeId)?.authoritySpec;
672
- }
673
-
674
- public async debugGetTopologyEntry(nodeId: string) {
681
+ public async debugGetNodeSpecs(): Promise<Map<string, { routeStart: number; routeEnd: number }>> {
675
682
  let topology = await authorityLookup.getTopology();
676
- return topology.find(x => x.nodeId === nodeId);
683
+ let result = new Map<string, { routeStart: number; routeEnd: number }>();
684
+ for (let entry of topology) {
685
+ result.set(entry.nodeId, { routeStart: entry.authoritySpec.routeStart, routeEnd: entry.authoritySpec.routeEnd });
686
+ }
687
+ return result;
677
688
  }
678
689
  }
679
690
 
@@ -685,9 +696,9 @@ export const QuerysubController = SocketFunction.register(
685
696
  watch: { compress: true, },
686
697
  unwatch: { compress: true, },
687
698
  addCall: { compress: true, },
699
+ debugGetPathNodeIds: {},
700
+ debugGetNodeSpecs: {},
688
701
  debugGetSingleReadNode: {},
689
- debugGetPathAuthorities: {},
690
- debugGetTopologyEntry: {},
691
702
  getModulePath: {},
692
703
  getDevFunctionSpecFromCall: {},
693
704
  }),
@@ -156,6 +156,9 @@ export async function runInPredictionQueue(config: {
156
156
 
157
157
  // Resolve all the values before us as well, before even our own promise, and then wait on that promise. That way, everything finishes in the same order it starts.
158
158
  if (index >= 0) {
159
+ if (index > 1) {
160
+ console.log(`Flushing ${index} other calls due to ${callSpec.FunctionId}`);
161
+ }
159
162
  for (let i = 0; i <= index; i++) {
160
163
  commitQueue[i].resolveOrderedPromise.resolve();
161
164
  }
@@ -8,7 +8,6 @@ import { getModuleFromConfig, setGitURLMapping } from "../3-path-functions/pathF
8
8
  import { logErrors } from "../errors";
9
9
  import { getPathFromStr, getPathStr1 } from "../path";
10
10
  import { Querysub, QuerysubController, QuerysubControllerBase, registerPredictionBlocker, querysubNodeId } from "./QuerysubController";
11
- import { Benchmark } from "../diagnostics/benchmark";
12
11
  import { parseArgs } from "../3-path-functions/PathFunctionHelpers";
13
12
  import { magenta, red } from "socket-function/src/formatting/logColors";
14
13
  import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
@@ -21,6 +20,7 @@ import { isPublic } from "../config";
21
20
  import { clientWatcher } from "../1-path-client/pathValueClientWatcher";
22
21
  import { cacheAsyncLimitedJSON } from "../functional/promiseCache";
23
22
  import { addEpsilons } from "../bits";
23
+ import { delay } from "socket-function/src/batching";
24
24
  setFlag(require, "cbor-x", "allowclient", true);
25
25
  const cborEncoder = lazy(() => new cbor.Encoder({ structuredClone: true }));
26
26
 
@@ -90,13 +90,15 @@ const predictRunCommitLoop = (run: () => Promise<PredictResult | undefined>) =>
90
90
 
91
91
  // IMPORTANT! This has to be synchronous, and we need to synchronously get into the proxy watcher call. That way other calls after this know to wait for our proxy watcher to finish. Otherwise, it's very, very, very easy to write code, where you call a function, you expect it to write to a value, and then you run some other code, maybe which just uses a commit async, and tries to read from that value. But the value won't exist, because we won't even have started the prediction yet, and the proxy watcher can't possibly know to wait, because we haven't started the prediction. So... this has to be synchronous!
92
92
  export function predictCall(call: CallSpec, metadata: FunctionMetadata): {
93
+ onApplied: () => void;
93
94
  cancel: () => void;
94
95
  predictPromise: Promise<PredictResult | undefined>;
95
96
  } {
96
97
  let predictObj = predictCallBase({ call, metadata });
97
98
  let cancel = predictObj.cancel;
99
+ let onApplied = predictObj.onApplied;
98
100
  registerPredictionBlocker(call.CallId, predictObj.predictPromise);
99
- return { cancel, predictPromise: predictObj.predictPromise };
101
+ return { cancel, onApplied, predictPromise: predictObj.predictPromise };
100
102
  }
101
103
 
102
104
  function getPredictTime(time: Time) {
@@ -115,12 +117,33 @@ function predictCallBase(config: {
115
117
  metadata: FunctionMetadata;
116
118
  overrides?: PathValue[];
117
119
  }): {
120
+ onApplied: () => void;
118
121
  cancel: () => void;
119
122
  predictPromise: Promise<PredictResult | undefined>;
120
123
  } {
121
124
  let call = config.call;
122
125
  let pathResultWrite = getCallResultPath(call);
123
- Benchmark.onStartPredictCall(pathResultWrite);
126
+
127
+ let actualValueFinished = new PromiseObj();
128
+ function onActualFinished() {
129
+ if (actualValueFinished.resolveCalled) return;
130
+ let resultObj = authorityStorage.getValueAtTime(pathResultWrite, undefined);
131
+ let result = pathValueSerializer.getPathValue(resultObj) as FunctionResult | undefined;
132
+ if (!result) return;
133
+ if (result.lastInternalLoopCount === -1) return;
134
+ actualValueFinished.resolve();
135
+ for (let callback of onPredictionFinishedCallbacks) {
136
+ callback({ callId: call.CallId, result, functionId: call.FunctionId });
137
+ }
138
+ }
139
+ function onApplied() {
140
+ clientWatcher.setWatches({
141
+ callback: onActualFinished,
142
+ paths: new Set([pathResultWrite]),
143
+ parentPaths: new Set(),
144
+ });
145
+ }
146
+
124
147
  let functionResult: FunctionResult = {
125
148
  // Special prediction values (we don't even really need to put anything here, but it
126
149
  // might as well have the right format, in case a bug causes it to be used somewhere)
@@ -183,23 +206,6 @@ function predictCallBase(config: {
183
206
  readParentPaths: Set<string>;
184
207
  };
185
208
 
186
- let actualValueFinished = new PromiseObj();
187
- function onActualFinished() {
188
- let resultObj = authorityStorage.getValueAtTime(pathResultWrite, undefined);
189
- let result = pathValueSerializer.getPathValue(resultObj) as FunctionResult | undefined;
190
- if (!result) return;
191
- if (result.lastInternalLoopCount === -1) return;
192
- actualValueFinished.resolve();
193
- for (let callback of onPredictionFinishedCallbacks) {
194
- callback({ callId: call.CallId, result, functionId: call.FunctionId });
195
- }
196
- }
197
- clientWatcher.setWatches({
198
- callback: onActualFinished,
199
- paths: new Set([pathResultWrite]),
200
- parentPaths: new Set(),
201
- });
202
-
203
209
  setTimeout(() => {
204
210
  clientWatcher.unwatch(onActualFinished);
205
211
  }, 30 * 1000);
@@ -360,6 +366,7 @@ function predictCallBase(config: {
360
366
  return {
361
367
  cancel: rejectPrediction,
362
368
  predictPromise,
369
+ onApplied,
363
370
  };
364
371
  }
365
372
 
@@ -13,6 +13,7 @@ import { isNode } from "typesafecss";
13
13
  import { devDebugbreak } from "../config";
14
14
  import { addStatPeriodic, addStatSumPeriodic, addTimeProfileDistribution, onTimeProfile, logNodeStats, logNodeStateStats, registerNodeMetadata } from "../-0-hooks/hooks";
15
15
  import { magenta } from "socket-function/src/formatting/logColors";
16
+ import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
16
17
 
17
18
 
18
19
  let POLL_INTERVAL = timeInMinute * 5;
@@ -295,6 +296,16 @@ class NodeMetadataBase {
295
296
  };
296
297
  }));
297
298
  }
299
+
300
+ public async debugGetPathAuthorities(nodeId: string) {
301
+ let authorities = await authorityLookup.getTopology();
302
+ return authorities.find(x => x.nodeId === nodeId)?.authoritySpec;
303
+ }
304
+
305
+ public async debugGetTopologyEntry(nodeId: string) {
306
+ let topology = await authorityLookup.getTopology();
307
+ return topology.find(x => x.nodeId === nodeId);
308
+ }
298
309
  }
299
310
  export const NodeMetadataController = SocketFunction.register(
300
311
  "NodeMetadataController-8be51a16-27c2-449e-b044-dcef6c3efcaf",
@@ -303,5 +314,11 @@ export const NodeMetadataController = SocketFunction.register(
303
314
  getExtraMetadatas: {
304
315
  hooks: [requiresNetworkTrustHook],
305
316
  },
317
+ debugGetPathAuthorities: {
318
+ hooks: [requiresNetworkTrustHook],
319
+ },
320
+ debugGetTopologyEntry: {
321
+ hooks: [requiresNetworkTrustHook],
322
+ },
306
323
  }),
307
324
  );
@@ -0,0 +1,167 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { qreact } from "../4-dom/qreact";
3
+ import { assertIsManagementUser } from "./managementPages";
4
+ import { css } from "typesafecss";
5
+ import { getSyncedController } from "../library-components/SyncedController";
6
+ import { getAllNodeIds, getBrowserUrlNode } from "../-f-node-discovery/NodeDiscovery";
7
+ import { debugGetAllCallFactories } from "socket-function/src/nodeCache";
8
+ import { debugNodeId } from "../-c-identity/IdentityController";
9
+ import { fastHash } from "../misc/hash";
10
+ import { NodeCapabilitiesController } from "../-g-core-values/NodeCapabilities";
11
+ import { sort } from "socket-function/src/misc";
12
+
13
+
14
+
15
+ function getColorForNodeId(friendlyNodeId: string): string {
16
+ let hash = fastHash(friendlyNodeId);
17
+ let hue = hash % 360;
18
+ return `hsl(${hue}, 70%, 60%)`;
19
+ }
20
+
21
+ export class NodeConnectionsPage extends qreact.Component {
22
+ render() {
23
+ let browserNode = getBrowserUrlNode();
24
+ let controller = nodeConnectionsController(browserNode);
25
+
26
+ let nodeIds = controller.getAllNodeIds();
27
+ if (!nodeIds) {
28
+ return <div class={css.pad2(10)}>Loading...</div>;
29
+ }
30
+
31
+ let connectableNodes: Array<{ nodeId: string; friendlyNodeId: string; entryPoint: string | undefined; connections: Array<{ nodeId: string; friendlyNodeId: string }> }> = [];
32
+ let nonConnectableNodes: string[] = [];
33
+
34
+ for (let nodeId of nodeIds) {
35
+ try {
36
+ let result = controller.getNodeConnections_forBrowser(nodeId);
37
+ if (result) {
38
+ let friendlyNodeId = debugNodeId(nodeId);
39
+ let entryPoint: string | undefined;
40
+ try {
41
+ entryPoint = controller.getEntryPoint_forBrowser(nodeId);
42
+ } catch (e) {
43
+ entryPoint = undefined;
44
+ }
45
+ connectableNodes.push({
46
+ nodeId,
47
+ friendlyNodeId,
48
+ entryPoint,
49
+ connections: result.connections,
50
+ });
51
+ }
52
+ } catch (e) {
53
+ nonConnectableNodes.push(nodeId);
54
+ }
55
+ }
56
+
57
+ return (
58
+ <div class={css.pad2(10).vbox(20).fillWidth}>
59
+ <div class={css.vbox(10).fillWidth}>
60
+ <h1>Connectable Nodes</h1>
61
+ {connectableNodes.map(node => {
62
+ let color = getColorForNodeId(node.friendlyNodeId);
63
+ return (
64
+ <div class={css.vbox(5).pad2(10).maxHeight(600).overflowAuto.bord(1, { h: 0, s: 0, l: 80 })}>
65
+ <div class={css.vbox(3)}>
66
+ {node.entryPoint && (
67
+ <div class={css.fontSize(14).fontWeight("bold")}>
68
+ {node.entryPoint}
69
+ </div>
70
+ )}
71
+ <div class={css.hbox(5)}>
72
+ <span class={css.background(color).pad2(4, 2).fontWeight("bold")}>
73
+ {node.friendlyNodeId}
74
+ </span>
75
+ <span class={css.color("gray").fontSize(12)}>
76
+ ({node.nodeId})
77
+ </span>
78
+ </div>
79
+ </div>
80
+ <div class={css.vbox(3).paddingLeft(20)}>
81
+ {node.connections.length === 0 && (
82
+ <div class={css.color("gray")}>No connections</div>
83
+ )}
84
+ {node.connections.map(conn => {
85
+ let connColor = getColorForNodeId(conn.friendlyNodeId);
86
+ return (
87
+ <div class={css.hbox(5)}>
88
+ <span>→ Connected to:</span>
89
+ <span class={css.background(connColor).pad2(4, 2).fontWeight("bold")}>
90
+ {conn.friendlyNodeId}
91
+ </span>
92
+ <span class={css.color("gray").fontSize(12)}>
93
+ ({conn.nodeId})
94
+ </span>
95
+ </div>
96
+ );
97
+ })}
98
+ </div>
99
+ </div>
100
+ );
101
+ })}
102
+ </div>
103
+
104
+ <div class={css.vbox(10).fillWidth}>
105
+ <h1>Non-Connectable Nodes</h1>
106
+ <div class={css.vbox(5).pad2(10).maxHeight(600).overflowAuto.bord(1, { h: 0, s: 0, l: 80 })}>
107
+ {nonConnectableNodes.map(nodeId => {
108
+ let friendlyNodeId = debugNodeId(nodeId);
109
+ return (
110
+ <div class={css.hbox(5)}>
111
+ <span class={css.background("lightgray").pad2(4, 2).fontWeight("bold")}>
112
+ {friendlyNodeId}
113
+ </span>
114
+ <span class={css.color("gray").fontSize(12)}>
115
+ ({nodeId})
116
+ </span>
117
+ <span class={css.color("gray")}>- Can't connect</span>
118
+ </div>
119
+ );
120
+ })}
121
+ </div>
122
+ </div>
123
+ </div>
124
+ );
125
+ }
126
+ }
127
+
128
+ class NodeConnectionsControllerBase {
129
+ public async getNodeConnections() {
130
+ let factories = debugGetAllCallFactories();
131
+ let connections = factories.filter(x => x.isConnected).map(factory => ({
132
+ nodeId: factory.nodeId,
133
+ friendlyNodeId: debugNodeId(factory.nodeId),
134
+ }));
135
+ return { connections };
136
+ }
137
+
138
+ public async getNodeConnections_forBrowser(nodeId: string) {
139
+ let result = await NodeConnectionsController.nodes[nodeId].getNodeConnections();
140
+ sort(result.connections, x => x.friendlyNodeId);
141
+ return result;
142
+ }
143
+
144
+ public async getEntryPoint_forBrowser(nodeId: string) {
145
+ return await NodeCapabilitiesController.nodes[nodeId].getEntryPoint();
146
+ }
147
+
148
+ public async getAllNodeIds() {
149
+ return await getAllNodeIds();
150
+ }
151
+ }
152
+
153
+ export const NodeConnectionsController = SocketFunction.register(
154
+ "NodeConnectionsController-8f3e2a1b-4c5d-4f2e-9a3b-7c8d1e2f3a4b",
155
+ new NodeConnectionsControllerBase(),
156
+ () => ({
157
+ getNodeConnections: {},
158
+ getNodeConnections_forBrowser: {},
159
+ getEntryPoint_forBrowser: {},
160
+ getAllNodeIds: {},
161
+ }),
162
+ () => ({
163
+ hooks: [assertIsManagementUser],
164
+ })
165
+ );
166
+
167
+ export const nodeConnectionsController = getSyncedController(NodeConnectionsController);
@@ -2,7 +2,7 @@ import { qreact } from "../../src/4-dom/qreact";
2
2
  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
- import { errorToUndefined, logErrors, timeoutToError } from "../../src/errors";
5
+ import { errorToUndefined, errorToUndefinedSilent, logErrors, timeoutToError } from "../../src/errors";
6
6
  import { Querysub, QuerysubController } from "../../src/4-querysub/QuerysubController";
7
7
  import { NodeCapabilitiesController, getControllerNodeIdList } from "../../src/-g-core-values/NodeCapabilities";
8
8
  import { lazy } from "socket-function/src/caching";
@@ -37,7 +37,7 @@ type NodeData = {
37
37
  ip?: string;
38
38
  devToolsURL?: string;
39
39
 
40
- live_isReadReady?: boolean;
40
+ live_isAlive?: boolean;
41
41
  live_authorityPaths?: AuthoritySpec;
42
42
  live_exposedControllers?: string[];
43
43
  live_entryPoint?: string;
@@ -79,7 +79,7 @@ export class NodeViewer extends qreact.Component {
79
79
  let data: NodeData = { nodeId };
80
80
  try {
81
81
  data.table = await controller.getMiscInfo(nodeId);
82
- data.live_isReadReady = await controller.live_isReadReady(nodeId);
82
+ data.live_isAlive = await controller.live_isAlive(nodeId);
83
83
  data.live_exposedControllers = await controller.getExposedControllers(nodeId);
84
84
  data.live_entryPoint = await controller.live_getEntryPoint(nodeId);
85
85
  data.live_trueTimeOffset = await controller.live_getTrueTimeOffset(nodeId);
@@ -147,7 +147,7 @@ export class NodeViewer extends qreact.Component {
147
147
  let nodeDatas = Object.values(this.state.nodes);
148
148
  let deadCount = 0;
149
149
  if (!showNotReadyNodes.value) {
150
- let ready = nodeDatas.filter(x => x.live_isReadReady);
150
+ let ready = nodeDatas.filter(x => x.live_isAlive);
151
151
  deadCount = nodeDatas.length - ready.length;
152
152
  nodeDatas = ready;
153
153
  }
@@ -175,7 +175,7 @@ export class NodeViewer extends qreact.Component {
175
175
  tables["PathValueServer"] = [];
176
176
  tables["Querysub"] = [];
177
177
  for (let datum of nodeDatas) {
178
- if (datum.live_authorityPaths) {
178
+ if (datum.live_authorityPaths?.prefixes.length) {
179
179
  tables["PathValueServer"].push(datum);
180
180
  } else if (datum.live_exposedControllers?.includes("QuerysubController-6db5ef05-7563-4473-a440-2f64f03fe6ef")) {
181
181
  tables["Querysub"].push(datum);
@@ -492,12 +492,11 @@ class NodeViewerControllerBase {
492
492
  return await getControllerNodeIdList(controller);
493
493
  }
494
494
 
495
- public async live_isReadReady(nodeId: string) {
496
- let topo = await QuerysubController.nodes[nodeId].debugGetTopologyEntry(nodeId);
497
- return !!topo?.isReady;
495
+ public async live_isAlive(nodeId: string) {
496
+ return !!await errorToUndefinedSilent(NodeCapabilitiesController.nodes[nodeId].getEntryPoint());
498
497
  }
499
498
  public async live_getAuthorityPaths(nodeId: string) {
500
- let topo = await QuerysubController.nodes[nodeId].debugGetTopologyEntry(nodeId);
499
+ let topo = await NodeMetadataController.nodes[nodeId].debugGetTopologyEntry(nodeId);
501
500
  return topo?.authoritySpec;
502
501
  }
503
502
  public async live_getEntryPoint(nodeId: string) {
@@ -524,7 +523,7 @@ class NodeViewerControllerBase {
524
523
  }
525
524
  let promises = [
526
525
  wrapAddTableValue("paths|paths", {}, async () => {
527
- let live_authorityPaths = (await QuerysubController.nodes[nodeId].debugGetPathAuthorities(nodeId));
526
+ let live_authorityPaths = (await NodeMetadataController.nodes[nodeId].debugGetPathAuthorities(nodeId));
528
527
  return JSON.stringify(live_authorityPaths);
529
528
  }),
530
529
  wrapAddTableValue("paths|functions", {}, async () => {
@@ -549,15 +548,12 @@ class NodeViewerControllerBase {
549
548
  return nodeId.split(".").at(-3);
550
549
  }),
551
550
  wrapAddTableValue("miscAudits", { formatter: "varray:error" }, async () => {
552
- let liveTopo = await QuerysubController.nodes[nodeId].debugGetTopologyEntry(nodeId);
551
+ let liveTopo = await NodeMetadataController.nodes[nodeId].debugGetTopologyEntry(nodeId);
553
552
  let live_authorityPaths = liveTopo?.authoritySpec;
554
553
  let obj = await authorityLookup.getTopology();
555
554
  let cached_authority = await obj.find(x => x.nodeId === nodeId);
556
- let live_isReadReady = liveTopo?.isReady;
557
555
  return [
558
- live_isReadReady && !cached_authority && `Authority is missing in cache`,
559
556
  JSON.stringify(live_authorityPaths) !== JSON.stringify(cached_authority) && `Paths are out of sync. Cache has ${JSON.stringify(cached_authority)} live has ${JSON.stringify(live_authorityPaths)}`,
560
- live_isReadReady !== cached_authority?.isReady && `isReadReady is out of sync. Cache has ${cached_authority?.isReady} live has ${live_isReadReady}`,
561
557
  ].filter(x => x);
562
558
  }),
563
559
  wrapAddTableValue("capabilities|capabilities", {}, async () => {
@@ -596,7 +592,7 @@ export const NodeViewerController = SocketFunction.register(
596
592
  getAllNodeIds: {},
597
593
  getExposedControllers: {},
598
594
  getControllerNodeIdList: {},
599
- live_isReadReady: {},
595
+ live_isAlive: {},
600
596
  live_getAuthorityPaths: {},
601
597
  live_getEntryPoint: {},
602
598
  live_getTrueTimeOffset: {},