querysub 0.250.0 → 0.252.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.250.0",
3
+ "version": "0.252.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",
@@ -64,4 +64,4 @@
64
64
  "resolutions": {
65
65
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"
66
66
  }
67
- }
67
+ }
@@ -366,6 +366,7 @@ class NodePathAuthorities {
366
366
  // as being out of date.
367
367
  let now = Date.now();
368
368
  let isReadReady = await errorToUndefinedSilent(PathController.nodes[nodeId].isReadReady());
369
+
369
370
  // If the node is gone, delete it from authorities
370
371
  if (isReadReady === undefined) {
371
372
  console.error(yellow(`Node errored out, removing from authorities ${nodeId}`));
@@ -290,6 +290,16 @@ async function edgeNodeFunction(config: {
290
290
  }
291
291
  async function getEdgeNodeConfig(): Promise<EdgeNodeConfig> {
292
292
  let edgeIndex = cachedConfig || await (await fetch(config.edgeNodeConfigURL)).json() as EdgeNodesIndex;
293
+
294
+ edgeIndex.edgeNodes.sort((a, b) => -(a.bootTime - b.bootTime));
295
+ // Filter out duplicate hostnames (taking the max bootTime). We can't have multiple services using the same port, so duplicates are DEFINITELY dead. This fixes some issues with accessing old services, and generally makes reading the logs easier.
296
+ let hostNames = new Set<string>();
297
+ edgeIndex.edgeNodes = edgeIndex.edgeNodes.filter(x => {
298
+ if (hostNames.has(x.host)) return false;
299
+ hostNames.add(x.host);
300
+ return true;
301
+ });
302
+
293
303
  cachedConfig = undefined;
294
304
  let liveHashForced = false;
295
305
  if (Date.now() < liveHashOverrideExpiryTime) {
@@ -7,7 +7,7 @@ import { getArchivesBackblazePublic } from "../-a-archives/archivesBackBlaze";
7
7
  import { nestArchives } from "../-a-archives/archives";
8
8
  import { SocketFunction } from "socket-function/SocketFunction";
9
9
  import { runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
10
- import { compare, compareArray, isNodeTrue, timeInMinute } from "socket-function/src/misc";
10
+ import { compare, compareArray, isNodeTrue, sort, timeInMinute } from "socket-function/src/misc";
11
11
  import { cacheLimited, lazy } from "socket-function/src/caching";
12
12
  import { canHaveChildren } from "socket-function/src/types";
13
13
  import { shutdown } from "../diagnostics/periodic";
@@ -48,7 +48,7 @@ export class MachinesListPage extends qreact.Component {
48
48
  this.state.selectedForDeletion[machineId] = true;
49
49
  }
50
50
  }}>
51
- 🗑️ Delete Machines
51
+ 🗑️ Delete Dead Machines
52
52
  </button>
53
53
  )}
54
54
 
@@ -7,7 +7,7 @@ import { Querysub } from "../../4-querysub/QuerysubController";
7
7
  import { currentViewParam, selectedServiceIdParam, selectedMachineIdParam } from "../urlParams";
8
8
  import { formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
9
9
  import { InputPicker } from "../../library-components/InputPicker";
10
- import { nextId, sort } from "socket-function/src/misc";
10
+ import { deepCloneJSON, nextId, sort } from "socket-function/src/misc";
11
11
  import { InputLabel } from "../../library-components/InputLabel";
12
12
  import { Button } from "../../library-components/Button";
13
13
  import { isDefined } from "../../misc";
@@ -89,6 +89,8 @@ type ServiceConfig = ${serviceConfigType}
89
89
  }
90
90
 
91
91
  private updateUnsavedChanges(updatedConfig: ServiceConfig) {
92
+ // Do not let them update the serviceId, as that would break things
93
+ updatedConfig.serviceId = selectedServiceIdParam.value;
92
94
  const newConfigStr = JSON.stringify(updatedConfig, null, 4);
93
95
  this.state.unsavedChanges = updatedConfig;
94
96
 
@@ -159,6 +161,8 @@ type ServiceConfig = ${serviceConfigType}
159
161
  if (!selectedServiceId) return;
160
162
 
161
163
  Querysub.commit(() => {
164
+ // Do not let them update the serviceId, as that would break things
165
+ updatedConfig.serviceId = selectedServiceIdParam.value;
162
166
  this.state.isSaving = true;
163
167
  this.state.saveError = "";
164
168
  });
@@ -333,8 +337,10 @@ type ServiceConfig = ${serviceConfigType}
333
337
  <div
334
338
  className={css.hbox(12)}
335
339
  >
336
- <div className={css.flexGrow(1)}>
337
- {machineId} ({machineInfo.info["getExternalIP"]})
340
+ <div className={css.vbox(5)}>
341
+ <div>
342
+ {machineId} ({machineInfo.info["getExternalIP"]})
343
+ </div>
338
344
  </div>
339
345
  {isMachineDead && (
340
346
  <div className={css.colorhsl(0, 80, 50)}>
@@ -513,6 +519,33 @@ type ServiceConfig = ${serviceConfigType}
513
519
 
514
520
  <div className={css.marginAuto}></div>
515
521
 
522
+ <button className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(110, 70, 90)}
523
+ onClick={() => {
524
+ if (!selectedServiceId) return;
525
+
526
+ const newServiceId = `service-${Math.round(Date.now())}`;
527
+ const clonedConfig = deepCloneJSON(config);
528
+ clonedConfig.serviceId = newServiceId;
529
+ clonedConfig.parameters.deploy = false;
530
+ clonedConfig.info.title += " (Copy)";
531
+
532
+ Querysub.onCommitFinished(async () => {
533
+ try {
534
+ await controller.addServiceConfig.promise(newServiceId, clonedConfig);
535
+ Querysub.commit(() => {
536
+ selectedServiceIdParam.value = newServiceId;
537
+ this.state.unsavedChanges = undefined;
538
+ });
539
+ } catch (error) {
540
+ Querysub.localCommit(() => {
541
+ this.state.saveError = error instanceof Error ? error.message : String(error);
542
+ });
543
+ }
544
+ });
545
+ }}>
546
+ ✚ Duplicate Service
547
+ </button>
548
+
516
549
  <button className={css.pad2(12, 8).button.bord2(0, 80, 50).hsl(0, 80, 90)}
517
550
  onClick={() => {
518
551
  if (!selectedServiceId) return;
@@ -18,7 +18,7 @@ export class ServicesListPage extends qreact.Component {
18
18
  if (!serviceList) return <div>Loading services...</div>;
19
19
 
20
20
  let services = serviceList.map(serviceId => [serviceId, controller.getServiceConfig(serviceId)] as const);
21
- sort(services, x => -(x[1]?.info.lastUpdatedTime || Date.now()));
21
+ sort(services, x => -(x[1]?.info.lastUpdatedTime || Date.now()) - (x[1]?.parameters.deploy && (Number.MAX_SAFE_INTEGER / 2) || 0));
22
22
 
23
23
  let getMachineInfo = cache((machineId: string) => controller.getMachineInfo(machineId));
24
24
 
@@ -59,7 +59,7 @@ export class ServicesListPage extends qreact.Component {
59
59
  </div>
60
60
  <div className={css.vbox(8)}>
61
61
  {services.map(([serviceId, config]) => {
62
- if (!config) return <div key={serviceId}>Loading {serviceId}...</div>;
62
+ if (!config) return <div key={serviceId}>Config is broken?</div>;
63
63
  let failingMachines = config.machineIds.filter(machineId => {
64
64
  let machineInfo = getMachineInfo(machineId);
65
65
  return machineInfo?.services[serviceId]?.errorFromLastRun;
@@ -25,7 +25,7 @@ import { onServiceConfigChange } from "./machineController";
25
25
  import { PromiseObj } from "../promise";
26
26
  import path from "path";
27
27
 
28
- const PIPE_FILE_LINE_LIMIT = 10;
28
+ const PIPE_FILE_LINE_LIMIT = 100_000;
29
29
 
30
30
  const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
31
31
  let machineInfo: MachineInfo = {
@@ -36,8 +36,6 @@ export type ServiceConfig = {
36
36
  command: string;
37
37
  /** Allows forcing an update */
38
38
  poke?: number;
39
- /** If once we don't restart it on exit, only running it once. However we will run it again on boot, so it's not once globally, it's just running it once for now. */
40
- once?: boolean;
41
39
 
42
40
  /** Not set by default, so we can setup the configuration before deploying it (or so we can undeploy easily without deleting it) */
43
41
  deploy?: boolean;
@@ -144,6 +142,7 @@ export class DeployControllerBase {
144
142
  }
145
143
 
146
144
  public async addServiceConfig(serviceId: string, config: ServiceConfig) {
145
+ config.info.lastUpdatedTime = Date.now();
147
146
  let existingConfig = await serviceConfigs.get(serviceId);
148
147
  if (existingConfig) {
149
148
  throw new Error(`Service ${serviceId} already exists`);
@@ -153,6 +152,7 @@ export class DeployControllerBase {
153
152
  }
154
153
 
155
154
  public async setServiceConfig(serviceId: string, config: ServiceConfig) {
155
+ config.info.lastUpdatedTime = Date.now();
156
156
  let serviceConfig = await serviceConfigs.get(serviceId);
157
157
 
158
158
  if (!serviceConfig) {
@@ -1,36 +1,46 @@
1
- 2) Verify when the file is truncated it still works
1
+ Ugh...
2
+ 1) Our local http server can't talk to the remote PathValueServer server?
3
+ 2) The remote http server isn't loading the modules?
4
+ - Which... come from permissions, and so the PathValueServer?
5
+ - Ah, and, it can't find the PathValueServer either?
2
6
 
3
- 2) Apply ansi coloring / whatever type we use
7
+ OH! It is... that we keep picking the wrong server, thinking it is working, but it has an old config?
8
+
9
+ bd6cf9eb4accffba8
10
+
11
+ 4) Add 2X redundancy to PathValueServer, and... FunctionRunner?
4
12
 
5
- 4) Destroy our testing digital ocean server
6
13
 
7
14
  5) Setup on our regular digital ocean server
8
- - Remove previous startup.sh, and crontab and kill existing tmux services
9
15
  - Setup all the services in the new UI
10
16
  - Copy from the previous startup.sh, running the same services
11
17
  - Changing the UI if anything is extremely annoying, but... I don't see how it would be...
12
18
  tmux send-keys -t server1 "cd ~/cyoa && yarn server-public" Enter
13
19
  tmux send-keys -t server2 "cd ~/cyoa && yarn server-public" Enter
14
20
  tmux send-keys -t fnc "cd ~/cyoa && yarn function-public --verbosecalls" Enter
21
+
15
22
  tmux send-keys -t http "cd ~/cyoa && yarn cyoa-public --verbosecalls" Enter
16
23
  tmux send-keys -t watch "cd ~/cyoa && yarn gc-watch-public" Enter
17
24
  tmux send-keys -t join "cd ~/cyoa && yarn join-public" Enter
18
25
  5) Verify the editor works
19
26
 
20
- 6) Verify PathValueServer gracefully shutdowns, not losing any values (because it delays and flushes writes before shutting down, detecting the ctrl+c).
21
-
22
-
23
- 7) Quick node removal on process crash OR removal
24
- - In the service, check our parent folder to see if we are in a screen (/machine-services/git/), and then write our nodeId to /machine-services/nodeId
25
- - If we see a nodeId when we are removing a screen, or killing the service, then delete that nodeId from the nodeId directory (and call tellEveryoneNodesChanges)
26
- 7.1) Verify this by killing a lot of services (the function runner?), by just poking it over and over, verifying the nodes are quickly deleted
27
27
 
28
28
 
29
29
 
30
30
  8) REIMPLEMENT yarn do-update functionality, with UI on the configuration page
31
- OH, maybe... buttons for querysub, button for the main site, and then buttons on each service to update them!
32
- YES, and then, a button to do all of that on each service, and a button to do that and on each service.
33
- - So a lot of buttons. But only 2 components, one for all, and one for a specific service.
31
+ - Components
32
+ <UpdateButtons />
33
+ - Commit all (add, commit, push)
34
+ - Only shows up if there are unsaved changes
35
+ - Commit querysub (publish, add, commit, push querysub, update package.json of app, add, commit, push)
36
+ - Only shows up if there are unsaved changes
37
+ - Deploy all (set hash to latest and save)
38
+ - Only shows up if (any) deployed hashes differ from latest for app
39
+ <UpdateServiceButtons service={serviceConfig} />
40
+ - Commit (add, commit, push)
41
+ - Only shows up if there are unsaved changes
42
+ - Deploy (set hash to latest and save)
43
+ - Only shows up if deployed hash differ from latest for app
34
44
  - Endpoints
35
45
  - anyQuerysubUnsaved
36
46
  - anyAppUnsaved
@@ -48,6 +58,27 @@
48
58
  - Commit, push, update hash
49
59
  - Also with an option to also update querysub (it'll tell us if querysub has changes), updating package.json to
50
60
 
61
+
62
+ 7) Quick node removal on process crash OR removal
63
+ - In the service, check our parent folder to see if we are in a screen (/machine-services/git/), and then write our nodeId to /machine-services/nodeId
64
+ - If we see a nodeId when we are removing a screen, or killing the service, then delete that nodeId from the nodeId directory (and call tellEveryoneNodesChanges)
65
+ 7.1) Verify this by killing a lot of services (the function runner?), by just poking it over and over, verifying the nodes are quickly deleted
66
+
67
+
68
+
69
+ In general, hit our servers hard, to make sure we can launch them, they can talk to each other, etc. Even with entirely new folders (to somewhat emulate another machine), and they can all startup smoothly.
70
+ 6) BUG! On startup, new nodes might take a poll interval to be fully ready? Hmm... we should restart our servers a lot again to make sure they come back up QUICKLY
71
+ - OH! It might be that the identity takes some time to propagate (the DNS), which causes us to require a second poll to be alive? Maybe... our local machine deleted it, because it couldn't see the DNS? No... none of these are reasonable...
72
+ - Try changing the key, so it syncs again, so we start fresh with the server
73
+ - Reduce it to just 1 instance as well?
74
+ 6.1) HTTP required a reboot when it was running without PathValueServer, as it didn't clone the right hashes, and then never did, even with PathValueServer was up?
75
+ - But maybe it was a node polling issue? But we are supposed to send notifications when nodes are launched? Hmm...
76
+ FORTUNATELY, looking at logs is now very simple, as well as updating nodes, so... this shouldn't be too hard to debug
77
+
78
+
79
+ 6) Verify PathValueServer gracefully shutdowns, not losing any values (because it delays and flushes writes before shutting down, detecting the ctrl+c).
80
+
81
+
51
82
  8) Fix deploy user notification issue, where the refresh button doesn't work?
52
83
 
53
84
  9) Rolling service updates
@@ -3,6 +3,6 @@ import { URLParam } from "../library-components/URLParam";
3
3
  export type ViewType = "services" | "machines" | "service-detail" | "machine-detail";
4
4
 
5
5
  // URL Parameters for navigation state
6
- export const currentViewParam = new URLParam<ViewType>("view", "machines");
6
+ export const currentViewParam = new URLParam<ViewType>("machineview", "machines");
7
7
  export const selectedServiceIdParam = new URLParam("serviceId", "");
8
8
  export const selectedMachineIdParam = new URLParam("machineId", "");
@@ -70,6 +70,12 @@ export async function registerManagementPages2(config: {
70
70
  registeredModule = config.module;
71
71
  let inputPages: typeof config.pages = [];
72
72
 
73
+
74
+ inputPages.push({
75
+ title: "Machines",
76
+ componentName: "MachinesPage",
77
+ getModule: () => import("../deployManager/MachinesPage"),
78
+ });
73
79
  inputPages.push({
74
80
  title: "Nodes",
75
81
  componentName: "NodeViewer",
@@ -127,11 +133,6 @@ export async function registerManagementPages2(config: {
127
133
  componentName: "RequireAuditPage",
128
134
  getModule: () => import("./misc-pages/RequireAuditPage"),
129
135
  });
130
- inputPages.push({
131
- title: "Machines",
132
- componentName: "MachinesPage",
133
- getModule: () => import("../deployManager/MachinesPage"),
134
- });
135
136
  inputPages.push(...config.pages);
136
137
 
137
138
  // NOTE: We don't store the UI in the database (here, or anywhere else, at least