querysub 0.508.0 → 0.510.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 (48) hide show
  1. package/bin/function-public.js +2 -0
  2. package/bin/server.js +0 -2
  3. package/package.json +3 -4
  4. package/spec.txt +5 -0
  5. package/src/-f-node-discovery/NodeDiscovery.ts +1 -1
  6. package/src/-g-core-values/NodeCapabilities.ts +4 -0
  7. package/src/-g-core-values/scheduledShutdown.ts +48 -0
  8. package/src/0-path-value-core/AuthorityLookup.ts +50 -9
  9. package/src/0-path-value-core/PathRouter.ts +10 -31
  10. package/src/0-path-value-core/PathRouterRouteOverride.ts +14 -98
  11. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +1 -4
  12. package/src/0-path-value-core/PathValueCommitter.ts +1 -6
  13. package/src/0-path-value-core/PathValueController.ts +5 -7
  14. package/src/0-path-value-core/PathWatcher.ts +8 -11
  15. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +8 -22
  16. package/src/0-path-value-core/pathValueCore.ts +4 -5
  17. package/src/1-path-client/RemoteWatcher.ts +47 -34
  18. package/src/2-proxy/archiveMoveHarness.ts +2 -1
  19. package/src/3-path-functions/PathFunctionHelpers.ts +11 -10
  20. package/src/3-path-functions/PathFunctionRunner.ts +165 -24
  21. package/src/3-path-functions/functionReplay.ts +2 -0
  22. package/src/3-path-functions/syncSchema.ts +0 -2
  23. package/src/4-deploy/edgeBootstrap.ts +10 -0
  24. package/src/4-deploy/edgeClientWatcher.tsx +4 -6
  25. package/src/4-deploy/edgeNodes.ts +52 -12
  26. package/src/4-querysub/FunctionRunnerTracking.ts +290 -0
  27. package/src/4-querysub/Querysub.ts +11 -16
  28. package/src/4-querysub/QuerysubController.ts +13 -11
  29. package/src/4-querysub/permissions.ts +3 -0
  30. package/src/4-querysub/querysubPrediction.ts +2 -2
  31. package/src/config.ts +1 -3
  32. package/src/deployManager/components/ServiceDetailPage.tsx +206 -77
  33. package/src/deployManager/components/ServicesListPage.tsx +3 -3
  34. package/src/deployManager/machineApplyMainCode.ts +139 -153
  35. package/src/deployManager/machineController.ts +12 -61
  36. package/src/deployManager/machineSchema.ts +84 -3
  37. package/src/deployManager/setupMachineMain.ts +2 -1
  38. package/src/diagnostics/grossStats/GrossStatsController.ts +3 -2
  39. package/src/diagnostics/logs/errorTickets/autoFixer.ts +3 -2
  40. package/src/diagnostics/logs/logGitHashes.ts +1 -1
  41. package/src/diagnostics/managementPages.tsx +2 -0
  42. package/src/diagnostics/misc-pages/AuthoritySpecPage.tsx +36 -28
  43. package/src/diagnostics/misc-pages/FunctionCaptureAnalyzePage.tsx +1 -1
  44. package/src/library-components/NetworkSelector.tsx +77 -0
  45. package/src/misc.ts +19 -0
  46. package/src/user-implementation/userData.ts +1 -1
  47. package/src/zipThreaded.ts +3 -2
  48. 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";
@@ -8,11 +8,11 @@ import { currentViewParam, selectedServiceIdParam, selectedMachineIdParam } from
8
8
  import { formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
9
9
  import { InputPicker } from "../../library-components/InputPicker";
10
10
  import { MachinePicker } from "./MachinePicker";
11
- import { deepCloneJSON, nextId, sort } from "socket-function/src/misc";
11
+ import { deepCloneJSON, nextId, sort, timeInSecond } 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, MachineController } from "../machineController";
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";
@@ -26,6 +26,7 @@ import { getLogViewerParams } from "../../diagnostics/logs/IndexedLogs/LogViewer
26
26
  import { getScreenName } from "../machineApplyMainCode";
27
27
  import { getOwnThreadId } from "../../-f-node-discovery/NodeDiscovery";
28
28
  import { decodeNodeId } from "sliftutils/misc/https/certs";
29
+ import { showModal } from "../../5-diagnostics/Modal";
29
30
 
30
31
 
31
32
 
@@ -42,7 +43,10 @@ export class ServiceDetailPage extends qreact.Component {
42
43
  data: t.type(""),
43
44
  callbackId: t.type(""),
44
45
  }),
45
- isKillingRolling: t.type(false),
46
+ // Milliseconds; 0 means no scheduled time picked yet (use Deploy Now instead)
47
+ switchTime: t.number(0),
48
+ // Seconds; 0 means use the service's configured overlapTime
49
+ overlapOverride: t.number(0),
46
50
  });
47
51
 
48
52
 
@@ -109,37 +113,7 @@ export class ServiceDetailPage extends qreact.Component {
109
113
  });
110
114
  }
111
115
 
112
- private async killAllRollingServices(config: ServiceConfig) {
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
-
116
+ // Saves to the PENDING config, which only takes effect once a switchover is deployed / scheduled.
143
117
  private async updateConfig(updatedConfig: ServiceConfig): Promise<void> {
144
118
  const selectedServiceId = selectedServiceIdParam.value;
145
119
  if (!selectedServiceId) return;
@@ -153,7 +127,7 @@ export class ServiceDetailPage extends qreact.Component {
153
127
 
154
128
  Querysub.onCommitFinished(async () => {
155
129
  try {
156
- await MachineServiceController(SocketFunction.browserNodeId()).setServiceConfigs.promise([updatedConfig]);
130
+ await MachineServiceController(SocketFunction.browserNodeId()).setPendingServiceConfigs.promise([updatedConfig]);
157
131
  } catch (error) {
158
132
  Querysub.localCommit(() => {
159
133
  this.state.saveError = error instanceof Error ? error.message : String(error);
@@ -168,6 +142,41 @@ export class ServiceDetailPage extends qreact.Component {
168
142
  });
169
143
  }
170
144
 
145
+ private runControllerAction(action: () => Promise<void>) {
146
+ Querysub.onCommitFinished(async () => {
147
+ try {
148
+ await action();
149
+ } catch (error) {
150
+ Querysub.localCommit(() => {
151
+ this.state.saveError = error instanceof Error ? error.message : String(error);
152
+ });
153
+ }
154
+ });
155
+ }
156
+
157
+ private confirmForceDeployNow(serviceId: string, overlapTime: number) {
158
+ let modal: { close: () => void } | undefined;
159
+ modal = showModal({
160
+ content: <div className={css.fixed.pos(0, 0).size("100vw", "100vh").hsla(0, 0, 0, 0.5).display("flex").alignItems("center").justifyContent("center")}>
161
+ <div className={css.vbox(12).pad2(20).hsl(0, 0, 98).bord2(0, 0, 40) + " keepModalsOpen"}>
162
+ <div className={css.boldStyle.fontSize(16)}>⚡ Force deploy now?</div>
163
+ <div>The pending config becomes live immediately. The old instances are shut down right away, with no overlap.</div>
164
+ <div className={css.hbox(10)}>
165
+ <Button hue={0} onClick={() => {
166
+ modal && modal.close();
167
+ this.runControllerAction(() => MachineServiceController(SocketFunction.browserNodeId()).scheduleSwitchover.promise({
168
+ serviceId,
169
+ switchTime: Date.now(),
170
+ overlapTime,
171
+ }));
172
+ }}>⚡ Force Deploy Now</Button>
173
+ <Button onClick={() => modal && modal.close()}>Cancel</Button>
174
+ </div>
175
+ </div>
176
+ </div>,
177
+ });
178
+ }
179
+
171
180
  render() {
172
181
  const selectedServiceId = selectedServiceIdParam.value;
173
182
  if (!selectedServiceId) return <div>No service selected</div>;
@@ -175,40 +184,21 @@ export class ServiceDetailPage extends qreact.Component {
175
184
  const gitInfo = MachineServiceController(SocketFunction.browserNodeId()).getGitInfo();
176
185
 
177
186
  let controller = MachineServiceController(SocketFunction.browserNodeId());
178
- let originalConfig = controller.getServiceConfig(selectedServiceId);
187
+ const originalConfig = controller.getServiceConfig(selectedServiceId);
179
188
  let serviceConfigType = controller.getServiceConfigType();
189
+ let pendingConfig = controller.getPendingServiceConfig(selectedServiceId);
190
+ let switchover = controller.getServiceSwitchover(selectedServiceId);
180
191
 
181
192
  if (!originalConfig || !serviceConfigType) {
182
193
  if (controller.isAnyLoading()) return <div>Loading service...</div>;
183
194
  return <div>Service not found</div>;
184
195
  }
185
196
 
186
- // Use unsaved changes if available, otherwise use original config
187
- const config = this.state.unsavedChanges ? this.state.unsavedChanges : originalConfig;
197
+ // The editor shows unsaved edits, else the saved-but-not-deployed pending config, else the live config
198
+ const config = this.state.unsavedChanges || pendingConfig || originalConfig;
188
199
  const configT = config;
189
200
  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
- }
201
+ const hasPendingConfig = !!pendingConfig;
212
202
 
213
203
  // Sort machines by status and heartbeat
214
204
  let nextIndexes = new Map<string, number>();
@@ -234,7 +224,7 @@ export class ServiceDetailPage extends qreact.Component {
234
224
  };
235
225
  });
236
226
  sort(machineStatuses, x => (x.isDisabled ? 3 : x.hasError ? 0 : x.isMachineDead ? 1 : 2) * 1000000 + x.heartbeat);
237
- let now = Date.now();
227
+ let now = Querysub.nowDelayed(timeInSecond);
238
228
 
239
229
  let machines = (controller.getMachineList() || []).map(x => controller.getMachineInfo(x)).filter(isDefined);
240
230
  sort(machines, x => -x.heartbeat);
@@ -358,6 +348,11 @@ export class ServiceDetailPage extends qreact.Component {
358
348
  <div title={formatVeryNiceDateTime(machineInfo.heartbeat)}>
359
349
  Heartbeat: {formatTime(now - machineInfo.heartbeat)}
360
350
  </div>
351
+ {switchover && (
352
+ <div className={css.colorhsl(35, 80, 40).fontWeight("bold")} title={formatVeryNiceDateTime(switchover.switchTime)}>
353
+ ⏻ Shuts down in {formatTime(switchover.switchTime - now)}
354
+ </div>
355
+ )}
361
356
  {serviceInfo && (
362
357
  <>
363
358
  {!!serviceInfo.lastLaunchedTime && <div title={formatVeryNiceDateTime(serviceInfo.lastLaunchedTime)}>
@@ -482,22 +477,14 @@ export class ServiceDetailPage extends qreact.Component {
482
477
 
483
478
  <button className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 0, 80)}
484
479
  onClick={() => {
485
- configT.parameters.poke = (configT.parameters.poke || 0) + 1;
486
- void this.updateConfigSynced(configT);
480
+ // Poke bypasses the pending flow, as it exists to force an immediate restart of the LIVE config
481
+ let live = deepCloneJSON(originalConfig);
482
+ live.parameters.poke = (live.parameters.poke || 0) + 1;
483
+ this.runControllerAction(() => controller.setServiceConfigs.promise([live]));
487
484
  }}>
488
485
  Poke
489
486
  </button>
490
487
 
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
488
  {hasUnsavedChanges && <>
502
489
  <Button
503
490
  flavor="noui"
@@ -511,7 +498,7 @@ export class ServiceDetailPage extends qreact.Component {
511
498
  this.updateConfigSynced(this.state.unsavedChanges);
512
499
  }
513
500
  }>
514
- {this.state.isSaving ? "Saving..." : "Save"}
501
+ {this.state.isSaving ? "Saving..." : "Save Pending"}
515
502
  </Button>
516
503
  <button
517
504
  className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(50, 80, 50)}
@@ -543,7 +530,7 @@ export class ServiceDetailPage extends qreact.Component {
543
530
  }
544
531
 
545
532
  {this.state.isSaving && <div className={css.pad2(12, 8).bord2(0, 0, 20) + " rainbow-flash"}>
546
- Deploying changes
533
+ Saving pending changes
547
534
  </div>}
548
535
 
549
536
  <div className={css.marginAuto}></div>
@@ -604,6 +591,105 @@ export class ServiceDetailPage extends qreact.Component {
604
591
  </button>
605
592
  </div>
606
593
 
594
+ {(hasPendingConfig || switchover) && (() => {
595
+ let overlapSeconds = this.state.overlapOverride || Math.round((config.parameters.overlapTime || DEFAULT_OVERLAP_TIME) / timeInSecond);
596
+ let overlap = overlapSeconds * timeInSecond;
597
+ let scheduledTime = this.state.switchTime;
598
+ let notEnoughTime = !!scheduledTime && scheduledTime > now && scheduledTime - now < overlap;
599
+ return <div className={css.vbox(10).pad2(12).fillWidth.bord2(210, 60, 50).hsl(210, 40, 95)}>
600
+ <div className={css.boldStyle.fontSize(15)}>
601
+ {switchover && "🕐 Switchover scheduled" || "📝 Pending changes saved (not deployed)"}
602
+ </div>
603
+ {hasPendingConfig && <ConfigDiff base={originalConfig} next={config} />}
604
+ {switchover && <div className={css.vbox(4)}>
605
+ <div>
606
+ Old instances shut down at <b>{formatVeryNiceDateTime(switchover.switchTime)}</b> (in {formatTime(switchover.switchTime - now)}), overlap {formatTime(switchover.overlapTime)}
607
+ </div>
608
+ <div>
609
+ 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)})`}
610
+ </div>
611
+ <div className={css.hbox(8)}>
612
+ <button
613
+ className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 70, 90)}
614
+ onClick={() => {
615
+ this.runControllerAction(() => controller.cancelSwitchover.promise(selectedServiceId));
616
+ }}>
617
+ Cancel Switchover
618
+ </button>
619
+ </div>
620
+ </div>}
621
+ {hasPendingConfig && !switchover && (() => {
622
+ // The switch time is inferred from the overlap, unless one is explicitly set (in which case the overlap only controls when the new instances start)
623
+ let effectiveSwitchTime = scheduledTime || now + overlap;
624
+ return <div className={css.vbox(8)}>
625
+ <div className={css.hbox(12).wrap}>
626
+ <button
627
+ className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(120, 70, 85).fontWeight("bold")}
628
+ onClick={() => {
629
+ this.runControllerAction(() => controller.scheduleSwitchover.promise({
630
+ serviceId: selectedServiceId,
631
+ switchTime: scheduledTime || Date.now() + overlap,
632
+ overlapTime: overlap,
633
+ }));
634
+ }}>
635
+ 🚀 Deploy
636
+ </button>
637
+ <div className={css.opacity(scheduledTime && 1 || 0.5)}>
638
+ <InputLabel
639
+ label="Switch at"
640
+ isDatetime
641
+ value={effectiveSwitchTime}
642
+ onChangeValue={value => {
643
+ this.state.switchTime = Number(value) || 0;
644
+ }}
645
+ />
646
+ </div>
647
+ <span>(in {formatTime(effectiveSwitchTime - now)})</span>
648
+ {!!scheduledTime && <button
649
+ className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 0, 90)}
650
+ onClick={() => {
651
+ this.state.switchTime = 0;
652
+ }}>
653
+ ↺ Reset to overlap-based time
654
+ </button>}
655
+ <div className={css.opacity(scheduledTime && 0.5 || 1)}>
656
+ <InputLabel
657
+ label="Overlap (seconds)"
658
+ integer
659
+ value={overlapSeconds}
660
+ onChangeValue={value => {
661
+ this.state.overlapOverride = Number(value) || 0;
662
+ }}
663
+ />
664
+ </div>
665
+ </div>
666
+ {!!scheduledTime && scheduledTime <= now && <span className={css.colorhsl(0, 85, 45).fontWeight("bold")}>
667
+ ⚠️ The switch time is in the past — deploying switches immediately
668
+ </span>}
669
+ {notEnoughTime && <span className={css.colorhsl(35, 85, 40).fontWeight("bold")}>
670
+ ⚠️ The switch time is sooner than the overlap ({formatTime(overlap)}) — the new instances won't be fully started before the old ones shut down
671
+ </span>}
672
+ <div className={css.hbox(8)}>
673
+ <button
674
+ className={css.pad2(12, 8).button.bord2(0, 70, 40).hsl(0, 70, 90)}
675
+ onClick={() => {
676
+ this.confirmForceDeployNow(selectedServiceId, overlap);
677
+ }}>
678
+ ⚡ Force Deploy Now
679
+ </button>
680
+ <button
681
+ className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(50, 80, 60)}
682
+ onClick={() => {
683
+ this.runControllerAction(() => controller.discardPendingServiceConfig.promise(selectedServiceId));
684
+ }}>
685
+ Discard Pending Config
686
+ </button>
687
+ </div>
688
+ </div>;
689
+ })()}
690
+ </div>;
691
+ })()}
692
+
607
693
  {this.state.saveError && (
608
694
  <div className={css.pad2(12).bord2(0, 80, 50).hsl(0, 80, 95).colorhsl(0, 80, 30)}>
609
695
  ⚠️ Error: {this.state.saveError}
@@ -621,4 +707,47 @@ export class ServiceDetailPage extends qreact.Component {
621
707
  />
622
708
  </div>;
623
709
  }
624
- }
710
+ }
711
+
712
+ // An inline diff of the live config vs the config being deployed, so it's clear exactly what a switchover will change.
713
+ class ConfigDiff extends qreact.Component<{ base: ServiceConfig; next: ServiceConfig }> {
714
+ render() {
715
+ let baseValues = new Map<string, unknown>();
716
+ let nextValues = new Map<string, unknown>();
717
+ flattenConfigValues(this.props.base, "", baseValues);
718
+ flattenConfigValues(this.props.next, "", nextValues);
719
+ let keys = Array.from(new Set([...baseValues.keys(), ...nextValues.keys()]));
720
+ sort(keys, x => x);
721
+ let changed = keys.filter(key =>
722
+ // The save time always changes on save, so it would just be noise
723
+ key !== "info.lastUpdatedTime"
724
+ && JSON.stringify(baseValues.get(key)) !== JSON.stringify(nextValues.get(key))
725
+ );
726
+ if (changed.length === 0) {
727
+ return <div className={css.colorhsl(0, 0, 50)}>(no changes from the live config)</div>;
728
+ }
729
+ return <div className={css.vbox(2).fontFamily("monospace").fontSize(13)}>
730
+ {changed.map(key => <div className={css.hbox(6).wrap}>
731
+ <span className={css.boldStyle}>{key}:</span>
732
+ {baseValues.has(key) && <span className={css.colorhsl(0, 70, 40).textDecoration("line-through")}>{formatDiffValue(baseValues.get(key))}</span>}
733
+ {nextValues.has(key) && <span className={css.colorhsl(120, 60, 30)}>{formatDiffValue(nextValues.get(key))}</span> || <span className={css.colorhsl(0, 70, 40)}>(removed)</span>}
734
+ </div>)}
735
+ </div>;
736
+ }
737
+ }
738
+ function flattenConfigValues(obj: unknown, prefix: string, out: Map<string, unknown>) {
739
+ if (obj && typeof obj === "object" && !Array.isArray(obj)) {
740
+ for (let [key, value] of Object.entries(obj as { [key: string]: unknown })) {
741
+ flattenConfigValues(value, prefix && prefix + "." + key || key, out);
742
+ }
743
+ return;
744
+ }
745
+ out.set(prefix, obj);
746
+ }
747
+ function formatDiffValue(value: unknown): string {
748
+ let result = JSON.stringify(value);
749
+ if (result === undefined) {
750
+ return "undefined";
751
+ }
752
+ return result;
753
+ }
@@ -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
- rollingWindow: 0,
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.rollingWindow && <div className={css.colorhsl(0, 50, 50).fontSize(14).fontWeight("bold")}>Rolling window: {formatTime(config.parameters.rollingWindow)}</div> || undefined}
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>