querysub 0.560.0 → 0.561.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.560.0",
3
+ "version": "0.561.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",
@@ -17,6 +17,7 @@ const DEAD_COLOR = { h: 0, s: 0, l: 92 };
17
17
  const OUTPUT_BUFFER_LIMIT = 1_000_000;
18
18
  const OUTPUT_BUFFER_KEPT = 100_000;
19
19
  const OUTPUT_MAX_HEIGHT = "40vh";
20
+ const DETAIL_FONT_SIZE = 12;
20
21
  const ANSI_SATURATION = 70;
21
22
  const ANSI_LIGHTNESS = 70;
22
23
 
@@ -113,6 +114,17 @@ class ProcessRow extends qreact.Component<{ record: ProcessRecord; machineNodeId
113
114
  {this.state.watching && "Hide output" || "Watch output"}
114
115
  </Button>
115
116
  </div>
117
+ {/* The ref and the command are both long, so they get their own line rather than pushing the summary around */}
118
+ <div className={css.hbox(10).wrap.fontSize(DETAIL_FONT_SIZE).colorhsl(0, 0, 30)}>
119
+ {record.parameters.gitRef && <div className={css.hbox(4)}>
120
+ <span className={css.colorhsl(0, 0, 45)}>ref</span>
121
+ <span className={css.fontFamily("monospace")}>{record.parameters.gitRef}</span>
122
+ </div>}
123
+ {record.parameters.command && <div className={css.hbox(4).flexGrow(1)}>
124
+ <span className={css.colorhsl(0, 0, 45)}>command</span>
125
+ <span className={css.fontFamily("monospace").whiteSpace("pre-wrap")}>{record.parameters.command}</span>
126
+ </div>}
127
+ </div>
116
128
  {this.state.watching && <ProcessOutput record={record} machineNodeId={this.props.machineNodeId} />}
117
129
  </div>;
118
130
  }
@@ -13,6 +13,7 @@ import { InputLabel } from "../../library-components/InputLabel";
13
13
  import { Button } from "../../library-components/Button";
14
14
  import { isDefined } from "../../misc";
15
15
  import { ProcessesView } from "./ProcessesView";
16
+ import { ResyncMachinesButton } from "./deployButtons";
16
17
  import { getPathStr2 } from "../../path";
17
18
  import { ATag, Anchor } from "../../library-components/ATag";
18
19
  import { ScrollOnMount } from "../../library-components/ScrollOnMount";
@@ -345,7 +346,10 @@ export class ServiceDetailPage extends qreact.Component {
345
346
  </div>
346
347
  {/* Machine Status */}
347
348
  {config.parameters.deploy && <div className={css.vbox(8).fillWidth}>
348
- <h3>Deployed Machines ({machineIds.length})</h3>
349
+ <div className={css.hbox(12).wrap.alignItems("center")}>
350
+ <h3 className={css.flexGrow(1)}>Deployed Machines ({machineIds.length})</h3>
351
+ <ResyncMachinesButton applyNodeIds={machineStatuses.map(x => x.machineInfo?.applyNodeId || "")} />
352
+ </div>
349
353
  <div className={css.vbox(4).fillWidth}>
350
354
  {machineStatuses.map(({ machineId, variables, machineInfo, serviceInfo, isMachineDead, hasError, isDisabled, index }) => {
351
355
  if (!machineInfo) return <div key={machineId}>Loading {machineId}...</div>;
@@ -10,7 +10,7 @@ import { sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
10
10
  import { cache } from "socket-function/src/caching";
11
11
  import { Anchor } from "../../library-components/ATag";
12
12
  import { isPublic } from "../../config";
13
- import { PendingDeployInfo, UpdateButtons, UpdateServiceButtons } from "./deployButtons";
13
+ import { PendingDeployInfo, ResyncMachinesButton, UpdateButtons, UpdateServiceButtons } from "./deployButtons";
14
14
  import { isDefined } from "../../misc";
15
15
  import { formatDateJSX } from "../../misc/formatJSX";
16
16
  import { Tools } from "./Tools";
@@ -43,6 +43,9 @@ export class ServicesListPage extends qreact.Component {
43
43
  return <div className={css.vbox(16)}>
44
44
  <div className={css.hbox(12).wrap}>
45
45
  <h2 className={css.flexGrow(1)}>Services</h2>
46
+ <ResyncMachinesButton applyNodeIds={(machineConfigList || []).map(x =>
47
+ controller.getMachineInfo(x.machineId)?.applyNodeId || ""
48
+ )} />
46
49
  <button className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 0, 100)}
47
50
  onClick={() => {
48
51
  Querysub.onCommitFinished(async () => {
@@ -19,6 +19,37 @@ export function bigEmoji(emoji: string, topOffset = 0) {
19
19
  return <span className={css.fontSize(26).marginTop(topOffset)}>{emoji}</span>;
20
20
  }
21
21
 
22
+ /** Makes the machines re-read the service configs immediately, instead of waiting out their fallback poll. Useful when a config was changed without the machines being notified, or a notification was missed. */
23
+ export class ResyncMachinesButton extends qreact.Component<{ applyNodeIds: string[] }> {
24
+ state = t.state({
25
+ isResyncing: t.type(false),
26
+ });
27
+ render() {
28
+ let applyNodeIds = unique(this.props.applyNodeIds.filter(x => x));
29
+ if (!applyNodeIds.length) return undefined;
30
+ return <button
31
+ className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(200, 70, 90)}
32
+ disabled={this.state.isResyncing}
33
+ title="Tells each machine's apply code to re-read the service configs now"
34
+ onClick={() => {
35
+ this.state.isResyncing = true;
36
+ let controller = MachineController(SocketFunction.browserNodeId());
37
+ Querysub.onCommitFinished(async () => {
38
+ try {
39
+ await Promise.all(applyNodeIds.map(nodeId => controller.resyncMachine.promise({ nodeId })));
40
+ } finally {
41
+ Querysub.commit(() => {
42
+ this.state.isResyncing = false;
43
+ });
44
+ }
45
+ });
46
+ }}
47
+ >
48
+ {this.state.isResyncing && "⏳ Resyncing..." || `🔄 Resync ${applyNodeIds.length} machine${applyNodeIds.length === 1 && "" || "s"}`}
49
+ </button>;
50
+ }
51
+ }
52
+
22
53
  export class RenderGitRefInfo extends qreact.Component<{
23
54
  gitRef: string;
24
55
  isQuerysub?: boolean;
@@ -73,6 +73,10 @@ export const OnServiceChange = SocketFunction.register(
73
73
 
74
74
  class MachineControllerBase {
75
75
  // NOTE: We don't need to worry about escaping commands here. YES, the user CAN inject code into the key. But this system is literally for running arbitrary commands, so they could just write a serviceConfig and run anything they want, on all the machines...
76
+ /** Tells a machine's apply code to re-read the service configs now, rather than waiting for its 15 minute fallback poll. Forwarded, as clients can't reach the machines directly. */
77
+ public async resyncMachine(config: { nodeId: string }): Promise<void> {
78
+ await OnServiceChange.nodes[config.nodeId].onServiceConfigChange();
79
+ }
76
80
  /** Every process this machine has a record of, running or dead. */
77
81
  public async listProcesses(): Promise<ProcessRecord[]> {
78
82
  return await listProcessRecords();
@@ -143,6 +147,7 @@ export const MachineController = getSyncedController(SocketFunction.register(
143
147
  "machine-controller-c3157d4a-580c-4e76-9dc9-072dd92e70af",
144
148
  () => new MachineControllerBase(),
145
149
  () => ({
150
+ resyncMachine: {},
146
151
  listProcesses: {},
147
152
  streamProcessOutput: {},
148
153
  listOtherProcesses: {},