querysub 0.256.0 → 0.258.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 +1 -1
- package/src/-b-authorities/dnsAuthority.ts +8 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +5 -0
- package/src/4-querysub/Querysub.ts +10 -0
- package/src/deployManager/components/ServiceDetailPage.tsx +22 -0
- package/src/deployManager/components/deployButtons.tsx +21 -13
- package/src/deployManager/machineApplyMainCode.ts +18 -9
- package/src/deployManager/machineSchema.ts +26 -7
- package/src/deployManager/spec.txt +5 -18
- package/src/persistentLocalStore.ts +6 -1
package/package.json
CHANGED
|
@@ -105,7 +105,10 @@ export async function setRecord(type: string, key: string, value: string, proxie
|
|
|
105
105
|
});
|
|
106
106
|
// NOTE: Apparently... even if the record didn't exist, we still have to wait...
|
|
107
107
|
console.log(`Waiting ${ttl} seconds for DNS to propagate...`);
|
|
108
|
-
|
|
108
|
+
for (let ttlLeft = ttl; ttlLeft > 0; ttlLeft--) {
|
|
109
|
+
await delay(1000);
|
|
110
|
+
console.log(`${ttlLeft} seconds left...`);
|
|
111
|
+
}
|
|
109
112
|
console.log(`Done waiting for DNS to update.`);
|
|
110
113
|
|
|
111
114
|
}
|
|
@@ -127,6 +130,9 @@ export async function addRecord(type: string, key: string, value: string, proxie
|
|
|
127
130
|
proxied: proxied === "proxied",
|
|
128
131
|
});
|
|
129
132
|
console.log(`Waiting ${ttl} seconds for DNS to propagate...`);
|
|
130
|
-
|
|
133
|
+
for (let ttlLeft = ttl; ttlLeft > 0; ttlLeft--) {
|
|
134
|
+
await delay(1000);
|
|
135
|
+
console.log(`${ttlLeft} seconds left...`);
|
|
136
|
+
}
|
|
131
137
|
console.log(`Done waiting for DNS to update.`);
|
|
132
138
|
}
|
|
@@ -472,6 +472,11 @@ if (isServer()) {
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
export async function forceRemoveNode(nodeId: string) {
|
|
476
|
+
await archives().del(nodeId);
|
|
477
|
+
void tellEveryoneNodesChanges();
|
|
478
|
+
}
|
|
479
|
+
|
|
475
480
|
|
|
476
481
|
/** Called on shutdown, to completely remove this node from discovery. */
|
|
477
482
|
export async function nodeDiscoveryShutdown() {
|
|
@@ -48,6 +48,8 @@ import yargs, { check } from "yargs";
|
|
|
48
48
|
import { parseArgsFactory } from "../misc/rawParams";
|
|
49
49
|
|
|
50
50
|
import * as typesafecss from "typesafecss";
|
|
51
|
+
|
|
52
|
+
|
|
51
53
|
typesafecss.setMeasureBlock(measureBlock);
|
|
52
54
|
typesafecss.setDelayFnc(async fnc => {
|
|
53
55
|
await (clientWatcher.waitForBeforeTriggerFinished() || Promise.resolve());
|
|
@@ -1254,6 +1256,13 @@ setImmediate(async () => {
|
|
|
1254
1256
|
await import("../5-diagnostics/nodeMetadata");
|
|
1255
1257
|
});
|
|
1256
1258
|
|
|
1259
|
+
setImmediate(async () => {
|
|
1260
|
+
if (isNode()) {
|
|
1261
|
+
const { doRegisterNodeForMachineCleanup } = await import("../deployManager/machineSchema");
|
|
1262
|
+
doRegisterNodeForMachineCleanup();
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1257
1266
|
registerGetCompressNetwork(() => Querysub.COMPRESS_NETWORK);
|
|
1258
1267
|
registerGetCompressDisk(() => Querysub.COMPRESS_DISK);
|
|
1259
1268
|
|
|
@@ -1269,4 +1278,5 @@ import { css } from "../4-dom/css";
|
|
|
1269
1278
|
import { getCountPerPaint } from "../functional/onNextPaint";
|
|
1270
1279
|
import { addEpsilons } from "../bits";
|
|
1271
1280
|
import { blue } from "socket-function/src/formatting/logColors";
|
|
1281
|
+
import { MachineController } from "../deployManager/machineController";
|
|
1272
1282
|
|
|
@@ -18,6 +18,7 @@ import { ScrollOnMount } from "../../library-components/ScrollOnMount";
|
|
|
18
18
|
import { StickyBottomScroll } from "../../library-components/StickyBottomScroll";
|
|
19
19
|
import { PrimitiveDisplay } from "../../diagnostics/logs/ObjectDisplay";
|
|
20
20
|
import { parseAnsiColors, rgbToHsl } from "../../diagnostics/logs/ansiFormat";
|
|
21
|
+
import { RenderGitRefInfo, UpdateServiceButtons, bigEmoji, buttonStyle } from "./deployButtons";
|
|
21
22
|
|
|
22
23
|
// Type declarations for Monaco editor
|
|
23
24
|
declare global {
|
|
@@ -199,6 +200,8 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
199
200
|
const selectedServiceId = selectedServiceIdParam.value;
|
|
200
201
|
if (!selectedServiceId) return <div>No service selected</div>;
|
|
201
202
|
|
|
203
|
+
const gitInfo = MachineServiceController(SocketFunction.browserNodeId()).getGitInfo();
|
|
204
|
+
|
|
202
205
|
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
203
206
|
let originalConfig = controller.getServiceConfig(selectedServiceId);
|
|
204
207
|
let serviceConfigType = controller.getServiceConfigType();
|
|
@@ -511,6 +514,25 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
511
514
|
</button>
|
|
512
515
|
</>}
|
|
513
516
|
|
|
517
|
+
{gitInfo?.repoUrl === config.parameters.repoUrl && gitInfo.latestRef !== config.parameters.gitRef &&
|
|
518
|
+
<button
|
|
519
|
+
className={buttonStyle.hsl(120, 70, 90).alignSelf("stretch")}
|
|
520
|
+
onClick={async () => {
|
|
521
|
+
configT.parameters.gitRef = gitInfo.latestRef;
|
|
522
|
+
this.updateUnsavedChanges(configT);
|
|
523
|
+
}}
|
|
524
|
+
>
|
|
525
|
+
<div className={css.hbox(5)}>
|
|
526
|
+
<b>New</b>
|
|
527
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
528
|
+
</div>
|
|
529
|
+
<div className={css.hbox(5)}>
|
|
530
|
+
<b>Current</b>
|
|
531
|
+
<RenderGitRefInfo gitRef={config.parameters.gitRef} />
|
|
532
|
+
</div>
|
|
533
|
+
</button>
|
|
534
|
+
}
|
|
535
|
+
|
|
514
536
|
{this.state.isSaving && <div className={css.pad2(12, 8).bord2(0, 0, 20) + " rainbow-flash"}>
|
|
515
537
|
Deploying changes
|
|
516
538
|
</div>}
|
|
@@ -12,7 +12,7 @@ import { unique } from "../../misc";
|
|
|
12
12
|
|
|
13
13
|
module.hotreload = true;
|
|
14
14
|
|
|
15
|
-
function bigEmoji(emoji: string, topOffset = 0) {
|
|
15
|
+
export function bigEmoji(emoji: string, topOffset = 0) {
|
|
16
16
|
return <span className={css.fontSize(26).marginTop(topOffset)}>{emoji}</span>;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -33,7 +33,7 @@ export class RenderGitRefInfo extends qreact.Component<{
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const buttonStyle = css.pad2(12, 8).button.bord2(0, 0, 20).fontWeight("bold").vbox(0).alignItems("center");
|
|
36
|
+
export const buttonStyle = css.pad2(12, 8).button.bord2(0, 0, 20).fontWeight("bold").vbox(0).alignItems("center");
|
|
37
37
|
|
|
38
38
|
export class UpdateButtons extends qreact.Component<{
|
|
39
39
|
services: ServiceConfig[];
|
|
@@ -50,7 +50,7 @@ export class UpdateButtons extends qreact.Component<{
|
|
|
50
50
|
|
|
51
51
|
return <>
|
|
52
52
|
{!!gitInfo?.querysubUncommitted.length && <button
|
|
53
|
-
className={buttonStyle.hsl(260, 60,
|
|
53
|
+
className={buttonStyle.hsl(260, 60, 90)}
|
|
54
54
|
title={gitInfo?.querysubUncommitted.join("\n")}
|
|
55
55
|
onClick={() => {
|
|
56
56
|
showFullscreenModal({
|
|
@@ -61,10 +61,14 @@ export class UpdateButtons extends qreact.Component<{
|
|
|
61
61
|
textarea
|
|
62
62
|
fillWidth
|
|
63
63
|
focusOnMount
|
|
64
|
-
|
|
64
|
+
noEnterKeyCommit
|
|
65
|
+
onKeyDown={async e => {
|
|
66
|
+
let value = e.currentTarget.value;
|
|
65
67
|
if (!value) return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
if (e.key === "Enter" && e.shiftKey) {
|
|
69
|
+
await controller.commitPushAndPublishQuerysub.promise(value);
|
|
70
|
+
closeAllModals();
|
|
71
|
+
}
|
|
68
72
|
}}
|
|
69
73
|
/>
|
|
70
74
|
<div className={css.vbox(5)}>
|
|
@@ -88,7 +92,7 @@ export class UpdateButtons extends qreact.Component<{
|
|
|
88
92
|
</div>
|
|
89
93
|
</button>}
|
|
90
94
|
{!!gitInfo?.uncommitted.length && <button
|
|
91
|
-
className={buttonStyle.hsl(210, 40,
|
|
95
|
+
className={buttonStyle.hsl(210, 40, 90)}
|
|
92
96
|
title={gitInfo?.uncommitted.join("\n")}
|
|
93
97
|
onClick={() => {
|
|
94
98
|
showFullscreenModal({
|
|
@@ -99,10 +103,14 @@ export class UpdateButtons extends qreact.Component<{
|
|
|
99
103
|
textarea
|
|
100
104
|
fillWidth
|
|
101
105
|
focusOnMount
|
|
102
|
-
|
|
106
|
+
noEnterKeyCommit
|
|
107
|
+
onKeyDown={async e => {
|
|
108
|
+
let value = e.currentTarget.value;
|
|
103
109
|
if (!value) return;
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
if (e.key === "Enter" && e.shiftKey) {
|
|
111
|
+
await controller.commitPushService.promise(value);
|
|
112
|
+
closeAllModals();
|
|
113
|
+
}
|
|
106
114
|
}}
|
|
107
115
|
/>
|
|
108
116
|
<div className={css.vbox(5)}>
|
|
@@ -121,7 +129,7 @@ export class UpdateButtons extends qreact.Component<{
|
|
|
121
129
|
|
|
122
130
|
{outdatedServices.length > 0 && gitInfo &&
|
|
123
131
|
<button
|
|
124
|
-
className={buttonStyle.hsl(120, 70,
|
|
132
|
+
className={buttonStyle.hsl(120, 70, 90)}
|
|
125
133
|
onClick={async () => {
|
|
126
134
|
for (let service of outdatedServices) {
|
|
127
135
|
service.parameters.gitRef = gitInfo.latestRef;
|
|
@@ -158,14 +166,14 @@ export class UpdateServiceButtons extends qreact.Component<{
|
|
|
158
166
|
|
|
159
167
|
return <>
|
|
160
168
|
{gitRef !== gitInfo.latestRef && <button
|
|
161
|
-
className={buttonStyle.hsl(120, 70,
|
|
169
|
+
className={buttonStyle.hsl(120, 70, 90).alignSelf("stretch")}
|
|
162
170
|
onClick={async () => {
|
|
163
171
|
this.props.service.parameters.gitRef = gitInfo.latestRef;
|
|
164
172
|
await controller.setServiceConfigs.promise([this.props.service]);
|
|
165
173
|
}}
|
|
166
174
|
>
|
|
167
175
|
<div>
|
|
168
|
-
{bigEmoji("🎯", -4)} <span>
|
|
176
|
+
{bigEmoji("🎯", -4)} <span>Update to New & Restart</span>
|
|
169
177
|
</div>
|
|
170
178
|
<div className={css.hbox(5)}>
|
|
171
179
|
<b>New</b>
|
|
@@ -2,9 +2,9 @@ import "../inject";
|
|
|
2
2
|
|
|
3
3
|
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
4
4
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
5
|
-
import { getOurNodeId, getOurNodeIdAssert } from "../-f-node-discovery/NodeDiscovery";
|
|
5
|
+
import { forceRemoveNode, getOurNodeId, getOurNodeIdAssert } from "../-f-node-discovery/NodeDiscovery";
|
|
6
6
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
7
|
-
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, serviceConfigs, SERVICE_FOLDER, machineInfos } from "./machineSchema";
|
|
7
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, serviceConfigs, SERVICE_FOLDER, machineInfos, SERVICE_NODE_FILE_NAME } from "./machineSchema";
|
|
8
8
|
import { runPromise } from "../functional/runCommand";
|
|
9
9
|
import { getExternalIP } from "socket-function/src/networking";
|
|
10
10
|
import { errorToUndefined, errorToUndefinedSilent } from "../errors";
|
|
@@ -305,10 +305,17 @@ const getScreenState = measureWrap(async function getScreenState(populateIsProce
|
|
|
305
305
|
|
|
306
306
|
return screenList;
|
|
307
307
|
});
|
|
308
|
+
async function removeOldNodeId(screenName: string) {
|
|
309
|
+
let screenNameFile = os.homedir() + "/" + SERVICE_FOLDER + screenName + "/" + SERVICE_NODE_FILE_NAME;
|
|
310
|
+
if (fs.existsSync(screenNameFile)) {
|
|
311
|
+
let nodeId = await fs.promises.readFile(screenNameFile, "utf8");
|
|
312
|
+
await fs.promises.unlink(screenNameFile);
|
|
313
|
+
await forceRemoveNode(nodeId);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
308
316
|
const runScreenCommand = measureWrap(async function runScreenCommand(config: {
|
|
309
317
|
screenName: string;
|
|
310
318
|
command: string;
|
|
311
|
-
folder: string;
|
|
312
319
|
}) {
|
|
313
320
|
let screenName = config.screenName;
|
|
314
321
|
// C:/cygwin64/bin/tmux has-session -t myproj 2>/dev/null || C:/cygwin64/bin/tmux new-session -d -s myproj
|
|
@@ -321,6 +328,7 @@ const runScreenCommand = measureWrap(async function runScreenCommand(config: {
|
|
|
321
328
|
await runPromise(`${prefix}tmux send-keys -t ${screenName} 'C-c' Enter`);
|
|
322
329
|
await delay(1000);
|
|
323
330
|
|
|
331
|
+
|
|
324
332
|
let screens = await getScreenState();
|
|
325
333
|
let screen = screens.find(x => x.screenName === screenName);
|
|
326
334
|
let pid = screen?.pid;
|
|
@@ -343,23 +351,24 @@ const runScreenCommand = measureWrap(async function runScreenCommand(config: {
|
|
|
343
351
|
await runScreenCommand({
|
|
344
352
|
screenName,
|
|
345
353
|
command: config.command,
|
|
346
|
-
folder: config.folder,
|
|
347
354
|
});
|
|
348
355
|
return;
|
|
349
356
|
}
|
|
350
357
|
}
|
|
351
358
|
}
|
|
352
|
-
await
|
|
359
|
+
await removeOldNodeId(screenName);
|
|
360
|
+
let folder = os.homedir() + "/" + SERVICE_FOLDER + screenName + "/";
|
|
361
|
+
await runPromise(`${prefix}tmux send-keys -t ${screenName} 'cd ${folder}git' Enter`);
|
|
353
362
|
let command = `#!/bin/bash
|
|
354
363
|
${config.command}
|
|
355
364
|
`;
|
|
356
|
-
await fs.promises.writeFile(
|
|
365
|
+
await fs.promises.writeFile(folder + "command.sh", command);
|
|
357
366
|
await runPromise(`${prefix}tmux send-keys -t ${screenName} 'bash ../command.sh' Enter`);
|
|
358
367
|
|
|
359
368
|
// Setup pipe-pane as well
|
|
360
369
|
|
|
361
|
-
let pipeFile = path.resolve(
|
|
362
|
-
let pipeScript = path.resolve(
|
|
370
|
+
let pipeFile = path.resolve(folder + "pipe.txt");
|
|
371
|
+
let pipeScript = path.resolve(folder + "pipe.sh");
|
|
363
372
|
await fs.promises.writeFile(pipeScript, `#!/bin/bash
|
|
364
373
|
line_count=0
|
|
365
374
|
while IFS= read -r line; do
|
|
@@ -390,6 +399,7 @@ const killScreen = measureWrap(async function killScreen(config: {
|
|
|
390
399
|
}) {
|
|
391
400
|
let prefix = getTmuxPrefix();
|
|
392
401
|
await runPromise(`${prefix}tmux kill-session -t ${config.screenName}`);
|
|
402
|
+
await removeOldNodeId(config.screenName);
|
|
393
403
|
});
|
|
394
404
|
const ensureGitSynced = measureWrap(async function ensureGitSynced(config: {
|
|
395
405
|
// Ensures existingFolder is the repo, as in, cwd to it, and git clone ., if needed. Reset the hash, in whatever way we need to, to force it to gitRef (git add --all and stash, etc, etc).
|
|
@@ -515,7 +525,6 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
515
525
|
await runScreenCommand({
|
|
516
526
|
screenName,
|
|
517
527
|
command: config.parameters.command,
|
|
518
|
-
folder: gitFolder,
|
|
519
528
|
});
|
|
520
529
|
await delay(2000);
|
|
521
530
|
let newScreens = await getScreenState(false);
|
|
@@ -18,9 +18,12 @@ import { OnServiceChange } from "./machineController";
|
|
|
18
18
|
import path from "path";
|
|
19
19
|
import fs from "fs";
|
|
20
20
|
import { runPromise } from "../functional/runCommand";
|
|
21
|
+
import { isNode } from "typesafecss";
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
const SERVICE_FOLDER_NAME = "machine-services";
|
|
24
|
+
export const SERVICE_FOLDER = `${SERVICE_FOLDER_NAME}/`;
|
|
23
25
|
export const MACHINE_RESYNC_INTERVAL = timeInMinute * 15;
|
|
26
|
+
export const SERVICE_NODE_FILE_NAME = "serviceNodeId.txt";
|
|
24
27
|
|
|
25
28
|
export type MachineInfo = {
|
|
26
29
|
machineId: string;
|
|
@@ -83,6 +86,20 @@ export type ServiceConfig = {
|
|
|
83
86
|
export const machineInfos = archiveJSONT<MachineInfo>(() => nestArchives("machines/machine-heartbeats/", getArchivesBackblaze(getDomain())));
|
|
84
87
|
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => nestArchives("machines/service-configs/", getArchivesBackblaze(getDomain())));
|
|
85
88
|
|
|
89
|
+
export function doRegisterNodeForMachineCleanup() {
|
|
90
|
+
if (isNode()) {
|
|
91
|
+
void SocketFunction.mountPromise.finally(() => {
|
|
92
|
+
void registerNodeForMachineCleanup(SocketFunction.mountedNodeId);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function registerNodeForMachineCleanup(nodeId: string) {
|
|
97
|
+
let currentPath = path.resolve(".").replaceAll("\\", "/");
|
|
98
|
+
let array = currentPath.split("/");
|
|
99
|
+
if (array.at(-1) === "git" && array.at(-2) === SERVICE_FOLDER_NAME) {
|
|
100
|
+
await fs.promises.writeFile(array.slice(0, -2).join("/") + "/" + SERVICE_NODE_FILE_NAME, nodeId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
86
103
|
|
|
87
104
|
export class MachineServiceControllerBase {
|
|
88
105
|
|
|
@@ -168,13 +185,15 @@ export class MachineServiceControllerBase {
|
|
|
168
185
|
throw new Error(`Service ${serviceId} does not exist`);
|
|
169
186
|
}
|
|
170
187
|
|
|
171
|
-
// Set the new service config
|
|
172
188
|
await serviceConfigs.set(serviceId, config);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
// Only notify we were or are deployed. If it's not deployed, this will be ignored anyways.
|
|
190
|
+
if (config.parameters.deploy || serviceConfig.parameters.deploy) {
|
|
191
|
+
for (let machineId of config.machineIds) {
|
|
192
|
+
newMachines.add(machineId);
|
|
193
|
+
}
|
|
194
|
+
for (let machineId of serviceConfig.machineIds) {
|
|
195
|
+
oldMachines.add(machineId);
|
|
196
|
+
}
|
|
178
197
|
}
|
|
179
198
|
}
|
|
180
199
|
await this.notifyMachines([...newMachines], [...oldMachines]);
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
OH
|
|
1
|
+
OH! Each service has a different machine id?
|
|
2
|
+
Hmm... well... it's the WORST thing every. It is a bit annoying...
|
|
3
|
+
- That explains the slow startup time...
|
|
4
|
+
|
|
2
5
|
|
|
3
6
|
|
|
4
|
-
7)
|
|
5
|
-
- 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
|
|
6
|
-
- 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)
|
|
7
|
-
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
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.
|
|
12
|
-
|
|
13
|
-
AH, I think I fixed that bug. I think it was an issue with using old hosts?
|
|
14
|
-
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
|
|
15
|
-
- 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...
|
|
16
|
-
- Try changing the key, so it syncs again, so we start fresh with the server
|
|
17
|
-
- Reduce it to just 1 instance as well?
|
|
18
|
-
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?
|
|
19
|
-
- But maybe it was a node polling issue? But we are supposed to send notifications when nodes are launched? Hmm...
|
|
20
|
-
FORTUNATELY, looking at logs is now very simple, as well as updating nodes, so... this shouldn't be too hard to debug
|
|
7
|
+
7.1) Verify quick node removal work by having a repeatedly crashing service, verifying the node exists, and then goes away on crash.
|
|
21
8
|
|
|
22
9
|
|
|
23
10
|
8) Fix deploy user notification issue, where the refresh button doesn't work?
|
|
@@ -5,12 +5,17 @@ import fs from "fs";
|
|
|
5
5
|
import { getStorageDir, getSubFolder } from "./fs";
|
|
6
6
|
import { isClient } from "./config2";
|
|
7
7
|
import { getDomain } from "./config";
|
|
8
|
+
import os from "os";
|
|
8
9
|
|
|
9
10
|
export function createKeyStore<T>(key: string): {
|
|
10
11
|
value: T | null;
|
|
11
12
|
} {
|
|
12
13
|
if (isNode()) {
|
|
13
|
-
let
|
|
14
|
+
let keyStoreDir = os.homedir() + "/querysub/";
|
|
15
|
+
if (!fs.existsSync(keyStoreDir)) {
|
|
16
|
+
fs.mkdirSync(keyStoreDir);
|
|
17
|
+
}
|
|
18
|
+
let path = keyStoreDir + key + ".json";
|
|
14
19
|
return {
|
|
15
20
|
get value() {
|
|
16
21
|
let contents: string | undefined = undefined;
|