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 +2 -2
- package/src/0-path-value-core/NodePathAuthorities.ts +1 -0
- package/src/4-deploy/edgeBootstrap.ts +10 -0
- package/src/4-deploy/edgeNodes.ts +1 -1
- package/src/deployManager/components/MachinesListPage.tsx +1 -1
- package/src/deployManager/components/ServiceDetailPage.tsx +36 -3
- package/src/deployManager/components/ServicesListPage.tsx +2 -2
- package/src/deployManager/machineApplyMainCode.ts +1 -1
- package/src/deployManager/machineSchema.ts +2 -2
- package/src/deployManager/spec.txt +45 -14
- package/src/deployManager/urlParams.ts +1 -1
- package/src/diagnostics/managementPages.tsx +6 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "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";
|
|
@@ -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.
|
|
337
|
-
|
|
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}>
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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>("
|
|
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
|