querysub 0.359.0 → 0.361.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.359.0",
3
+ "version": "0.361.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",
package/src/config.ts CHANGED
@@ -22,7 +22,12 @@ let yargObj = parseArgsFactory()
22
22
  // TODO: The bootstrapper is a single file. Maybe we shouldn't run the entire service just for that. Although... maybe it's fine, as services are light?
23
23
  .option("bootstraponly", { type: "boolean", desc: "Don't register as an edge node, so we serve the bootstrap files, but we don't need up to date code because we are not used for endpoints or the UI." })
24
24
  .option("notifyemails", { type: "array", desc: "The emails to notify when errors occur." })
25
- .option("diskaudit", { type: "boolean", desc: "Track all audit logs to disk. This might end up writing A LOT of data." })
25
+ .option("diskaudit", {
26
+ type: "boolean",
27
+ // NOTE: I wanna see how long I can keep this on for. Eventually it's gonna become a problem and we're gonna have to turn it off. But for testing it's certainly useful as we don't know exactly what is gonna cause a problem. But it probably will be synchronization related, and every server does synchronization.
28
+ default: true,
29
+ desc: "Track all audit logs to disk. This might end up writing A LOT of data."
30
+ })
26
31
  .argv
27
32
  ;
28
33
  type QuerysubConfig = {
@@ -11,7 +11,7 @@ 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";
14
- import { watchScreenOutput, stopWatchingScreenOutput } from "../machineController";
14
+ import { watchScreenOutput, stopWatchingScreenOutput, MachineController } from "../machineController";
15
15
  import { getPathStr2 } from "../../path";
16
16
  import { ATag, Anchor } from "../../library-components/ATag";
17
17
  import { ScrollOnMount } from "../../library-components/ScrollOnMount";
@@ -38,6 +38,7 @@ export class ServiceDetailPage extends qreact.Component {
38
38
  data: t.type(""),
39
39
  callbackId: t.type(""),
40
40
  }),
41
+ isKillingRolling: t.type(false),
41
42
  });
42
43
 
43
44
 
@@ -103,6 +104,37 @@ export class ServiceDetailPage extends qreact.Component {
103
104
  await stopWatchingScreenOutput({ callbackId });
104
105
  });
105
106
  }
107
+
108
+ private async killAllRollingServices(config: ServiceConfig) {
109
+ Querysub.commit(() => {
110
+ this.state.isKillingRolling = true;
111
+ });
112
+
113
+ Querysub.onCommitFinished(async () => {
114
+ try {
115
+ let uniqueMachineIds = Array.from(new Set(config.machineIds));
116
+
117
+ for (let machineId of uniqueMachineIds) {
118
+ let machineInfo = await MachineServiceController(SocketFunction.browserNodeId()).getMachineInfo.promise(machineId);
119
+ if (!machineInfo) continue;
120
+
121
+ let applyNodeId = machineInfo.applyNodeId;
122
+ await MachineController(applyNodeId).killRollingServices.promise({
123
+ serviceKey: config.parameters.key,
124
+ });
125
+ }
126
+ } catch (e: any) {
127
+ Querysub.localCommit(() => {
128
+ this.state.saveError = `Error killing rolling services: ${e.message}`;
129
+ });
130
+ } finally {
131
+ Querysub.localCommit(() => {
132
+ this.state.isKillingRolling = false;
133
+ });
134
+ }
135
+ });
136
+ }
137
+
106
138
  private async updateConfig(updatedConfig: ServiceConfig): Promise<void> {
107
139
  const selectedServiceId = selectedServiceIdParam.value;
108
140
  if (!selectedServiceId) return;
@@ -151,6 +183,27 @@ export class ServiceDetailPage extends qreact.Component {
151
183
  const configT = config;
152
184
  const hasUnsavedChanges = !!this.state.unsavedChanges;
153
185
 
186
+ let rollingServiceCount = 0;
187
+ if (config.parameters.rollingWindow) {
188
+ let uniqueMachineIds = Array.from(new Set(config.machineIds));
189
+ for (let machineId of uniqueMachineIds) {
190
+ try {
191
+ let machineInfo = controller.getMachineInfo(machineId);
192
+ if (!machineInfo) continue;
193
+
194
+ let applyNodeId = machineInfo.applyNodeId;
195
+ let rollingInfo = MachineController(applyNodeId).getRollingServiceInfo({
196
+ serviceKey: config.parameters.key,
197
+ });
198
+ if (rollingInfo) {
199
+ rollingServiceCount += rollingInfo.length;
200
+ }
201
+ } catch (e: any) {
202
+ console.error(`Error getting rolling service info for machine ${machineId}: ${e.message}`);
203
+ }
204
+ }
205
+ }
206
+
154
207
  // Sort machines by status and heartbeat
155
208
  let nextIndexes = new Map<string, number>();
156
209
  let machineStatuses = config.machineIds.map(machineId => {
@@ -432,6 +485,22 @@ export class ServiceDetailPage extends qreact.Component {
432
485
  Poke
433
486
  </button>
434
487
 
488
+ {configT.parameters.rollingWindow && rollingServiceCount > 0 && <button
489
+ className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 70, 90)}
490
+ onClick={() => {
491
+ void this.killAllRollingServices(config);
492
+ }}
493
+ disabled={this.state.isKillingRolling}
494
+ >
495
+ {this.state.isKillingRolling ? "Killing..." : `💀 Kill ${rollingServiceCount} Rolling Service${rollingServiceCount === 1 ? "" : "s"}`}
496
+ </button>}
497
+
498
+ {configT.parameters.rollingWindow && <div>
499
+ <div className={css.colorhsl(0, 50, 50).fontSize(14).fontWeight("bold")}>
500
+ IMPORTANT! Service has a rolling window, so updates might not appear immediately.
501
+ </div>
502
+ </div>}
503
+
435
504
  {hasUnsavedChanges && <>
436
505
  <Button
437
506
  flavor="noui"
@@ -136,7 +136,7 @@ export class ServicesListPage extends qreact.Component {
136
136
  </div>
137
137
  <div className={css.vbox(4)}>
138
138
  <div>Updated {formatDateJSX(config.info.lastUpdatedTime)} AGO</div>
139
- {config.parameters.rollingWindow && <div>Rolling window: {formatTime(config.parameters.rollingWindow)}</div> || undefined}
139
+ {config.parameters.rollingWindow && <div className={css.colorhsl(0, 50, 50).fontSize(14).fontWeight("bold")}>Rolling window: {formatTime(config.parameters.rollingWindow)}</div> || undefined}
140
140
  </div>
141
141
  </div>
142
142
  </Anchor>
@@ -265,6 +265,45 @@ done`);
265
265
  })();
266
266
  }
267
267
 
268
+ export async function getRollingServiceInfo(config: {
269
+ serviceKey: string;
270
+ }): Promise<{ rollingScreenName: string; originalScreenName: string; expiresAt: number }[]> {
271
+ let rollingInfo: { rollingScreenName: string; originalScreenName: string; expiresAt: number }[] = [];
272
+
273
+ for (let [screenName, rolling] of rollingKeepScreenAlive) {
274
+ if (screenName.startsWith(config.serviceKey + "-")) {
275
+ rollingInfo.push({
276
+ rollingScreenName: rolling.rollingScreenName,
277
+ originalScreenName: rolling.originalScreenName,
278
+ expiresAt: rolling.pinnedTime + rolling.pinnedDuration,
279
+ });
280
+ }
281
+ }
282
+
283
+ return rollingInfo;
284
+ }
285
+
286
+ export async function killRollingServicesForService(config: {
287
+ serviceKey: string;
288
+ }): Promise<void> {
289
+ let toDelete: string[] = [];
290
+
291
+ for (let [screenName, rolling] of rollingKeepScreenAlive) {
292
+ if (screenName.startsWith(config.serviceKey + "-")) {
293
+ console.log(red(`Explicitly killing rolling screen ${rolling.rollingScreenName} for service ${config.serviceKey}`));
294
+ await killScreen({
295
+ screenName: rolling.rollingScreenName,
296
+ });
297
+ rollingScreens.delete(rolling.rollingScreenName);
298
+ toDelete.push(screenName);
299
+ }
300
+ }
301
+
302
+ for (let screenName of toDelete) {
303
+ rollingKeepScreenAlive.delete(screenName);
304
+ }
305
+ }
306
+
268
307
 
269
308
  const getTmuxPrefix = lazy(() => {
270
309
  if (os.platform() === "win32") {
@@ -734,6 +773,17 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
734
773
  continue;
735
774
  }
736
775
  }
776
+
777
+ let rollingInfo = rollingKeepScreenAlive.get(screenName);
778
+ if (rollingInfo) {
779
+ console.log(red(`Service ${screenName} was disabled, killing associated rolling screen ${rollingInfo.rollingScreenName}`));
780
+ await killScreen({
781
+ screenName: rollingInfo.rollingScreenName,
782
+ });
783
+ rollingScreens.delete(rollingInfo.rollingScreenName);
784
+ rollingKeepScreenAlive.delete(screenName);
785
+ }
786
+
737
787
  await killScreen({
738
788
  screenName,
739
789
  });
@@ -5,7 +5,7 @@ import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
5
5
  import { timeInMinute } from "socket-function/src/misc";
6
6
  import { assertIsManagementUser } from "../diagnostics/managementPages";
7
7
  import { isNode } from "typesafecss";
8
- import { streamScreenOutput } from "./machineApplyMainCode";
8
+ import { streamScreenOutput, getRollingServiceInfo, killRollingServicesForService } from "./machineApplyMainCode";
9
9
  import { Querysub } from "../4-querysub/QuerysubController";
10
10
  import { getPathStr2 } from "../path";
11
11
  import { getGitURLLive, setGitRef } from "../4-deploy/git";
@@ -140,6 +140,18 @@ class MachineControllerBase {
140
140
  await runPromise("bash machine-startup.sh", { cwd: os.homedir(), detach: true });
141
141
  }
142
142
 
143
+ public async getRollingServiceInfo(config: {
144
+ serviceKey: string;
145
+ }): Promise<{ rollingScreenName: string; originalScreenName: string; expiresAt: number }[]> {
146
+ return await getRollingServiceInfo(config);
147
+ }
148
+
149
+ public async killRollingServices(config: {
150
+ serviceKey: string;
151
+ }): Promise<void> {
152
+ await killRollingServicesForService(config);
153
+ }
154
+
143
155
  }
144
156
 
145
157
  let forwardedCallbacks = new Map<string, string>();
@@ -152,6 +164,8 @@ export const MachineController = getSyncedController(SocketFunction.register(
152
164
  watchOtherScreenOutput: {},
153
165
  deployMachineFromBrowser: {},
154
166
  deployMachine: {},
167
+ getRollingServiceInfo: {},
168
+ killRollingServices: {},
155
169
  }),
156
170
  () => ({
157
171
  hooks: [assertIsManagementUser],
@@ -33,7 +33,7 @@ export const SERVICE_NODE_FILE_NAME = "serviceNodeId.txt";
33
33
  export type MachineInfo = {
34
34
  machineId: string;
35
35
 
36
- // Used to tell the apply tool to update it's configs now
36
+ // Used to talk to the apply script directly (ex, recheck the service config, kill the rolling nodes, etc)
37
37
  applyNodeId: string;
38
38
 
39
39
  heartbeat: number;
@@ -23,6 +23,7 @@ import { PUBLIC_MOVE_THRESHOLD } from "./BufferIndexLogsOptimizationConstants";
23
23
  import { atomic } from "../../../2-proxy/PathValueProxyWatcher";
24
24
  import { errorToUndefined } from "querysub/src/errors";
25
25
  import { IndexedLogResults, createEmptyIndexedLogResults } from "./BufferIndexHelpers";
26
+ import { TimeFilePath } from "./TimeFileTree";
26
27
 
27
28
  let searchText = new URLParam("searchText", "");
28
29
  let readLiveData = new URLParam("readLiveData", false);
@@ -435,6 +436,19 @@ export class LogViewer3 extends qreact.Component {
435
436
  this.state.paths = allPaths;
436
437
  this.state.loadingPaths = false;
437
438
  });
439
+ return allPaths;
440
+ }
441
+
442
+ async updatePaths() {
443
+ let prevPaths = this.getPaths();
444
+ let newPaths = await this.loadPaths();
445
+ Querysub.commitLocal(() => {
446
+ function getHash(path: TimeFilePath): string {
447
+ return path.threadId + "_" + path.machineId;
448
+ }
449
+ let keep = new Set(prevPaths.map(getHash));
450
+ this.state.paths = newPaths?.filter(x => keep.has(getHash(x))) || [];
451
+ });
438
452
  }
439
453
 
440
454
  cancel = async () => {
@@ -798,11 +812,16 @@ export class LogViewer3 extends qreact.Component {
798
812
  <Button
799
813
  flavor="large"
800
814
  onClick={() => void this.loadPaths()}
801
- hue={120}
815
+ hue={this.state.paths.length ? 200 : 120}
802
816
  >
803
- Preview Files
817
+ {this.state.paths.length && "Reset Files" || "Preview Files"}
804
818
  </Button>
805
819
  )}
820
+ {this.state.paths && !savedPathsURL.value &&
821
+ <Button flavor="large" hue={120} onClick={() => void this.updatePaths()}>
822
+ Update Files
823
+ </Button>
824
+ }
806
825
  {savedPathsURL.value && (
807
826
  <Button
808
827
  flavor="large"
@@ -19,8 +19,13 @@ IMPORTANT! Now I am properly calling shutdown, so none of the streamed logs shou
19
19
 
20
20
 
21
21
 
22
- OH! The archives have to be put in the home folder. They aren't?
22
+ BUG: Fix it so we properly kill rolling screens when we disable a service
23
23
 
24
+ TODO: Show rolling services somewhere
25
+
26
+
27
+ BUG: Why are CYOA deploys not working!
28
+ I even disabled it, and nothing. It's still running. WTF...
24
29
 
25
30
  Hmm... why are not enough logs appearing?
26
31
  - We only have logs for server.ts? Wtf?
@@ -35,7 +40,7 @@ Hmm... why are not enough logs appearing?
35
40
  - Basically, just changes the code we're reading from multiple servers to select public servers instead, and then, of course, skip ourselves.
36
41
 
37
42
  BUG: UGH... live logs for the remote server isn't working...
38
- "new non-local WATCH"
43
+ new non-local WATCH
39
44
  - UGH... it being pending logs is annoying, as that's hard to debug locally...
40
45
  AH! Why do we have so few logs?
41
46