querysub 0.508.0 → 0.509.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/bin/function-public.js +2 -0
- package/bin/server.js +0 -2
- package/package.json +3 -4
- package/spec.txt +5 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +1 -1
- package/src/-g-core-values/NodeCapabilities.ts +4 -0
- package/src/-g-core-values/scheduledShutdown.ts +48 -0
- package/src/0-path-value-core/AuthorityLookup.ts +50 -9
- package/src/0-path-value-core/PathRouter.ts +10 -31
- package/src/0-path-value-core/PathRouterRouteOverride.ts +14 -98
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +1 -4
- package/src/0-path-value-core/PathValueCommitter.ts +1 -6
- package/src/0-path-value-core/PathValueController.ts +5 -7
- package/src/0-path-value-core/PathWatcher.ts +8 -11
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +8 -22
- package/src/0-path-value-core/pathValueCore.ts +4 -5
- package/src/1-path-client/RemoteWatcher.ts +47 -34
- package/src/2-proxy/archiveMoveHarness.ts +2 -1
- package/src/3-path-functions/PathFunctionHelpers.ts +11 -10
- package/src/3-path-functions/PathFunctionRunner.ts +165 -24
- package/src/3-path-functions/functionReplay.ts +2 -0
- package/src/3-path-functions/syncSchema.ts +0 -2
- package/src/4-deploy/edgeBootstrap.ts +10 -0
- package/src/4-deploy/edgeClientWatcher.tsx +4 -6
- package/src/4-deploy/edgeNodes.ts +52 -12
- package/src/4-querysub/FunctionRunnerTracking.ts +290 -0
- package/src/4-querysub/Querysub.ts +11 -16
- package/src/4-querysub/QuerysubController.ts +13 -11
- package/src/4-querysub/permissions.ts +3 -0
- package/src/4-querysub/querysubPrediction.ts +2 -2
- package/src/config.ts +1 -3
- package/src/deployManager/components/ServiceDetailPage.tsx +127 -73
- package/src/deployManager/components/ServicesListPage.tsx +3 -3
- package/src/deployManager/machineApplyMainCode.ts +139 -153
- package/src/deployManager/machineController.ts +12 -61
- package/src/deployManager/machineSchema.ts +84 -3
- package/src/deployManager/setupMachineMain.ts +2 -1
- package/src/diagnostics/grossStats/GrossStatsController.ts +3 -2
- package/src/diagnostics/logs/errorTickets/autoFixer.ts +3 -2
- package/src/diagnostics/logs/logGitHashes.ts +1 -1
- package/src/diagnostics/managementPages.tsx +2 -0
- package/src/diagnostics/misc-pages/AuthoritySpecPage.tsx +36 -28
- package/src/diagnostics/misc-pages/FunctionCaptureAnalyzePage.tsx +1 -1
- package/src/library-components/NetworkSelector.tsx +77 -0
- package/src/misc.ts +19 -0
- package/src/user-implementation/userData.ts +1 -1
- package/src/zipThreaded.ts +3 -2
- package/bin/server-dev.js +0 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig } from "../machineSchema";
|
|
3
|
+
import { DEFAULT_OVERLAP_TIME, MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { t } from "../../2-proxy/schema2";
|
|
6
6
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
@@ -12,7 +12,7 @@ import { deepCloneJSON, nextId, sort } from "socket-function/src/misc";
|
|
|
12
12
|
import { InputLabel } from "../../library-components/InputLabel";
|
|
13
13
|
import { Button } from "../../library-components/Button";
|
|
14
14
|
import { isDefined } from "../../misc";
|
|
15
|
-
import { watchScreenOutput, stopWatchingScreenOutput
|
|
15
|
+
import { watchScreenOutput, stopWatchingScreenOutput } from "../machineController";
|
|
16
16
|
import { getPathStr2 } from "../../path";
|
|
17
17
|
import { ATag, Anchor } from "../../library-components/ATag";
|
|
18
18
|
import { ScrollOnMount } from "../../library-components/ScrollOnMount";
|
|
@@ -42,7 +42,10 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
42
42
|
data: t.type(""),
|
|
43
43
|
callbackId: t.type(""),
|
|
44
44
|
}),
|
|
45
|
-
|
|
45
|
+
// Milliseconds; 0 means no scheduled time picked yet (use Deploy Now instead)
|
|
46
|
+
switchTime: t.number(0),
|
|
47
|
+
// Milliseconds; 0 means use the service's configured overlapTime
|
|
48
|
+
overlapOverride: t.number(0),
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
|
|
@@ -109,37 +112,7 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
109
112
|
});
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
Querysub.commit(() => {
|
|
114
|
-
this.state.isKillingRolling = true;
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
Querysub.onCommitFinished(async () => {
|
|
118
|
-
try {
|
|
119
|
-
let uniqueMachineIds = Array.from(new Set(config.machineIds));
|
|
120
|
-
|
|
121
|
-
for (let machineId of uniqueMachineIds) {
|
|
122
|
-
let machineInfo = await MachineServiceController(SocketFunction.browserNodeId()).getMachineInfo.promise(machineId);
|
|
123
|
-
if (!machineInfo) continue;
|
|
124
|
-
|
|
125
|
-
let applyNodeId = machineInfo.applyNodeId;
|
|
126
|
-
await MachineController(SocketFunction.browserNodeId()).killRollingServicesFromBrowser.promise({
|
|
127
|
-
machineNodeId: applyNodeId,
|
|
128
|
-
serviceKey: config.parameters.key,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
} catch (e: any) {
|
|
132
|
-
Querysub.localCommit(() => {
|
|
133
|
-
this.state.saveError = `Error killing rolling services: ${e.message}`;
|
|
134
|
-
});
|
|
135
|
-
} finally {
|
|
136
|
-
Querysub.localCommit(() => {
|
|
137
|
-
this.state.isKillingRolling = false;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
115
|
+
// Saves to the PENDING config, which only takes effect once a switchover is deployed / scheduled.
|
|
143
116
|
private async updateConfig(updatedConfig: ServiceConfig): Promise<void> {
|
|
144
117
|
const selectedServiceId = selectedServiceIdParam.value;
|
|
145
118
|
if (!selectedServiceId) return;
|
|
@@ -153,7 +126,7 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
153
126
|
|
|
154
127
|
Querysub.onCommitFinished(async () => {
|
|
155
128
|
try {
|
|
156
|
-
await MachineServiceController(SocketFunction.browserNodeId()).
|
|
129
|
+
await MachineServiceController(SocketFunction.browserNodeId()).setPendingServiceConfigs.promise([updatedConfig]);
|
|
157
130
|
} catch (error) {
|
|
158
131
|
Querysub.localCommit(() => {
|
|
159
132
|
this.state.saveError = error instanceof Error ? error.message : String(error);
|
|
@@ -168,6 +141,18 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
168
141
|
});
|
|
169
142
|
}
|
|
170
143
|
|
|
144
|
+
private runControllerAction(action: () => Promise<void>) {
|
|
145
|
+
Querysub.onCommitFinished(async () => {
|
|
146
|
+
try {
|
|
147
|
+
await action();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
Querysub.localCommit(() => {
|
|
150
|
+
this.state.saveError = error instanceof Error ? error.message : String(error);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
171
156
|
render() {
|
|
172
157
|
const selectedServiceId = selectedServiceIdParam.value;
|
|
173
158
|
if (!selectedServiceId) return <div>No service selected</div>;
|
|
@@ -175,40 +160,21 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
175
160
|
const gitInfo = MachineServiceController(SocketFunction.browserNodeId()).getGitInfo();
|
|
176
161
|
|
|
177
162
|
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
178
|
-
|
|
163
|
+
const originalConfig = controller.getServiceConfig(selectedServiceId);
|
|
179
164
|
let serviceConfigType = controller.getServiceConfigType();
|
|
165
|
+
let pendingConfig = controller.getPendingServiceConfig(selectedServiceId);
|
|
166
|
+
let switchover = controller.getServiceSwitchover(selectedServiceId);
|
|
180
167
|
|
|
181
168
|
if (!originalConfig || !serviceConfigType) {
|
|
182
169
|
if (controller.isAnyLoading()) return <div>Loading service...</div>;
|
|
183
170
|
return <div>Service not found</div>;
|
|
184
171
|
}
|
|
185
172
|
|
|
186
|
-
//
|
|
187
|
-
const config = this.state.unsavedChanges
|
|
173
|
+
// The editor shows unsaved edits, else the saved-but-not-deployed pending config, else the live config
|
|
174
|
+
const config = this.state.unsavedChanges || pendingConfig || originalConfig;
|
|
188
175
|
const configT = config;
|
|
189
176
|
const hasUnsavedChanges = !!this.state.unsavedChanges;
|
|
190
|
-
|
|
191
|
-
let rollingServiceCount = 0;
|
|
192
|
-
if (config.parameters.rollingWindow) {
|
|
193
|
-
let uniqueMachineIds = Array.from(new Set(config.machineIds));
|
|
194
|
-
for (let machineId of uniqueMachineIds) {
|
|
195
|
-
try {
|
|
196
|
-
let machineInfo = controller.getMachineInfo(machineId);
|
|
197
|
-
if (!machineInfo) continue;
|
|
198
|
-
|
|
199
|
-
let applyNodeId = machineInfo.applyNodeId;
|
|
200
|
-
let rollingInfo = MachineController(SocketFunction.browserNodeId()).getRollingServiceInfoFromBrowser({
|
|
201
|
-
machineNodeId: applyNodeId,
|
|
202
|
-
serviceKey: config.parameters.key,
|
|
203
|
-
});
|
|
204
|
-
if (rollingInfo) {
|
|
205
|
-
rollingServiceCount += rollingInfo.length;
|
|
206
|
-
}
|
|
207
|
-
} catch (e: any) {
|
|
208
|
-
console.error(`Error getting rolling service info for machine ${machineId}: ${e.message}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
177
|
+
const hasPendingConfig = !!pendingConfig;
|
|
212
178
|
|
|
213
179
|
// Sort machines by status and heartbeat
|
|
214
180
|
let nextIndexes = new Map<string, number>();
|
|
@@ -358,6 +324,11 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
358
324
|
<div title={formatVeryNiceDateTime(machineInfo.heartbeat)}>
|
|
359
325
|
Heartbeat: {formatTime(now - machineInfo.heartbeat)}
|
|
360
326
|
</div>
|
|
327
|
+
{switchover && (
|
|
328
|
+
<div className={css.colorhsl(35, 80, 40).fontWeight("bold")} title={formatVeryNiceDateTime(switchover.switchTime)}>
|
|
329
|
+
⏻ Shuts down in {formatTime(switchover.switchTime - now)}
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
361
332
|
{serviceInfo && (
|
|
362
333
|
<>
|
|
363
334
|
{!!serviceInfo.lastLaunchedTime && <div title={formatVeryNiceDateTime(serviceInfo.lastLaunchedTime)}>
|
|
@@ -482,22 +453,14 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
482
453
|
|
|
483
454
|
<button className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 0, 80)}
|
|
484
455
|
onClick={() => {
|
|
485
|
-
|
|
486
|
-
|
|
456
|
+
// Poke bypasses the pending flow, as it exists to force an immediate restart of the LIVE config
|
|
457
|
+
let live = deepCloneJSON(originalConfig);
|
|
458
|
+
live.parameters.poke = (live.parameters.poke || 0) + 1;
|
|
459
|
+
this.runControllerAction(() => controller.setServiceConfigs.promise([live]));
|
|
487
460
|
}}>
|
|
488
461
|
Poke
|
|
489
462
|
</button>
|
|
490
463
|
|
|
491
|
-
{!!configT.parameters.rollingWindow && rollingServiceCount > 0 && <button
|
|
492
|
-
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 70, 90)}
|
|
493
|
-
onClick={() => {
|
|
494
|
-
void this.killAllRollingServices(config);
|
|
495
|
-
}}
|
|
496
|
-
disabled={this.state.isKillingRolling}
|
|
497
|
-
>
|
|
498
|
-
{this.state.isKillingRolling ? "Killing..." : `💀 Kill ${rollingServiceCount} Rolling Service${rollingServiceCount === 1 ? "" : "s"}`}
|
|
499
|
-
</button>}
|
|
500
|
-
|
|
501
464
|
{hasUnsavedChanges && <>
|
|
502
465
|
<Button
|
|
503
466
|
flavor="noui"
|
|
@@ -511,7 +474,7 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
511
474
|
this.updateConfigSynced(this.state.unsavedChanges);
|
|
512
475
|
}
|
|
513
476
|
}>
|
|
514
|
-
{this.state.isSaving ? "Saving..." : "Save"}
|
|
477
|
+
{this.state.isSaving ? "Saving..." : "Save Pending"}
|
|
515
478
|
</Button>
|
|
516
479
|
<button
|
|
517
480
|
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(50, 80, 50)}
|
|
@@ -604,6 +567,97 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
604
567
|
</button>
|
|
605
568
|
</div>
|
|
606
569
|
|
|
570
|
+
{(hasPendingConfig || switchover) && (() => {
|
|
571
|
+
let overlap = this.state.overlapOverride || config.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
|
|
572
|
+
let scheduledTime = this.state.switchTime;
|
|
573
|
+
let notEnoughTime = !!scheduledTime && scheduledTime - now < overlap;
|
|
574
|
+
return <div className={css.vbox(10).pad2(12).fillWidth.bord2(210, 60, 50).hsl(210, 40, 95)}>
|
|
575
|
+
<div className={css.boldStyle.fontSize(15)}>
|
|
576
|
+
{switchover && "🕐 Switchover scheduled" || "📝 Pending changes saved (not deployed)"}
|
|
577
|
+
</div>
|
|
578
|
+
{switchover && <div className={css.vbox(4)}>
|
|
579
|
+
<div>
|
|
580
|
+
Old instances shut down at <b>{formatVeryNiceDateTime(switchover.switchTime)}</b> (in {formatTime(switchover.switchTime - now)}), overlap {formatTime(switchover.overlapTime)}
|
|
581
|
+
</div>
|
|
582
|
+
<div>
|
|
583
|
+
New instances start at <b>{formatVeryNiceDateTime(switchover.switchTime - switchover.overlapTime)}</b> {switchover.switchTime - switchover.overlapTime <= now && "(already running)" || `(in ${formatTime(switchover.switchTime - switchover.overlapTime - now)})`}
|
|
584
|
+
</div>
|
|
585
|
+
<div className={css.hbox(8)}>
|
|
586
|
+
<button
|
|
587
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 70, 90)}
|
|
588
|
+
onClick={() => {
|
|
589
|
+
this.runControllerAction(() => controller.cancelSwitchover.promise(selectedServiceId));
|
|
590
|
+
}}>
|
|
591
|
+
Cancel Switchover
|
|
592
|
+
</button>
|
|
593
|
+
</div>
|
|
594
|
+
</div>}
|
|
595
|
+
{hasPendingConfig && !switchover && <div className={css.vbox(8)}>
|
|
596
|
+
<div className={css.hbox(12).wrap}>
|
|
597
|
+
<InputLabel
|
|
598
|
+
label={`Overlap (ms, default ${formatTime(config.parameters.overlapTime || DEFAULT_OVERLAP_TIME)})`}
|
|
599
|
+
number
|
|
600
|
+
value={this.state.overlapOverride || ""}
|
|
601
|
+
onChangeValue={value => {
|
|
602
|
+
this.state.overlapOverride = Number(value) || 0;
|
|
603
|
+
}}
|
|
604
|
+
/>
|
|
605
|
+
<span>Effective overlap: {formatTime(overlap)}</span>
|
|
606
|
+
</div>
|
|
607
|
+
<div className={css.hbox(12).wrap}>
|
|
608
|
+
<button
|
|
609
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(120, 70, 85).fontWeight("bold")}
|
|
610
|
+
onClick={() => {
|
|
611
|
+
let switchTime = Date.now() + overlap;
|
|
612
|
+
this.runControllerAction(() => controller.scheduleSwitchover.promise({
|
|
613
|
+
serviceId: selectedServiceId,
|
|
614
|
+
switchTime,
|
|
615
|
+
overlapTime: overlap,
|
|
616
|
+
}));
|
|
617
|
+
}}>
|
|
618
|
+
🚀 Deploy Now (switch in {formatTime(overlap)})
|
|
619
|
+
</button>
|
|
620
|
+
<InputLabel
|
|
621
|
+
label="Switch at"
|
|
622
|
+
isDatetime
|
|
623
|
+
value={this.state.switchTime}
|
|
624
|
+
onChangeValue={value => {
|
|
625
|
+
this.state.switchTime = Number(value) || 0;
|
|
626
|
+
}}
|
|
627
|
+
/>
|
|
628
|
+
{!!scheduledTime && <span>
|
|
629
|
+
{scheduledTime > now && `in ${formatTime(scheduledTime - now)}` || "in the past!"}
|
|
630
|
+
</span>}
|
|
631
|
+
{notEnoughTime && <span className={css.colorhsl(35, 85, 40).fontWeight("bold")}>
|
|
632
|
+
⚠️ Not enough time for the full overlap ({formatTime(overlap)}) — the new instances won't be fully started before the old ones shut down
|
|
633
|
+
</span>}
|
|
634
|
+
<button
|
|
635
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(120, 70, 90)}
|
|
636
|
+
disabled={!scheduledTime}
|
|
637
|
+
onClick={() => {
|
|
638
|
+
if (!scheduledTime) return;
|
|
639
|
+
this.runControllerAction(() => controller.scheduleSwitchover.promise({
|
|
640
|
+
serviceId: selectedServiceId,
|
|
641
|
+
switchTime: scheduledTime,
|
|
642
|
+
overlapTime: overlap,
|
|
643
|
+
}));
|
|
644
|
+
}}>
|
|
645
|
+
🕐 Schedule Switchover
|
|
646
|
+
</button>
|
|
647
|
+
</div>
|
|
648
|
+
<div className={css.hbox(8)}>
|
|
649
|
+
<button
|
|
650
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(50, 80, 60)}
|
|
651
|
+
onClick={() => {
|
|
652
|
+
this.runControllerAction(() => controller.discardPendingServiceConfig.promise(selectedServiceId));
|
|
653
|
+
}}>
|
|
654
|
+
Discard Pending Config
|
|
655
|
+
</button>
|
|
656
|
+
</div>
|
|
657
|
+
</div>}
|
|
658
|
+
</div>;
|
|
659
|
+
})()}
|
|
660
|
+
|
|
607
661
|
{this.state.saveError && (
|
|
608
662
|
<div className={css.pad2(12).bord2(0, 80, 50).hsl(0, 80, 95).colorhsl(0, 80, 30)}>
|
|
609
663
|
⚠️ Error: {this.state.saveError}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MachineServiceController, ServiceConfig } from "../machineSchema";
|
|
3
|
+
import { DEFAULT_OVERLAP_TIME, MachineServiceController, ServiceConfig } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { t } from "../../2-proxy/schema2";
|
|
6
6
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
@@ -55,7 +55,7 @@ export class ServicesListPage extends qreact.Component {
|
|
|
55
55
|
gitRef: gitInfo.latestRef,
|
|
56
56
|
command: "ping 1.1.1.1",
|
|
57
57
|
deploy: false,
|
|
58
|
-
|
|
58
|
+
overlapTime: DEFAULT_OVERLAP_TIME,
|
|
59
59
|
},
|
|
60
60
|
info: {
|
|
61
61
|
title: `New Service created at ${formatNiceDateTime(Date.now())}`,
|
|
@@ -138,7 +138,7 @@ export class ServicesListPage extends qreact.Component {
|
|
|
138
138
|
</div>
|
|
139
139
|
<div className={css.vbox(4)}>
|
|
140
140
|
<div>Updated {formatDateJSX(config.info.lastUpdatedTime)} AGO</div>
|
|
141
|
-
{config.parameters.
|
|
141
|
+
{config.parameters.overlapTime && <div className={css.colorhsl(210, 50, 50).fontSize(14).fontWeight("bold")}>Switchover overlap: {formatTime(config.parameters.overlapTime)}</div> || undefined}
|
|
142
142
|
</div>
|
|
143
143
|
</div>
|
|
144
144
|
</Anchor>
|