querysub 0.357.0 → 0.358.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/.cursorrules +1 -0
- package/package.json +2 -1
- package/src/-a-archives/archivesDisk.ts +13 -6
- package/src/-a-archives/archivesMemoryCache.ts +41 -17
- package/src/deployManager/components/MachineDetailPage.tsx +43 -2
- package/src/deployManager/components/MachinesListPage.tsx +10 -2
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineSchema.ts +39 -0
- package/src/diagnostics/NodeViewer.tsx +2 -1
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +124 -123
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +83 -1
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +2 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +21 -24
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +186 -25
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +231 -144
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +307 -108
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +37 -7
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +51 -33
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -180
- package/src/functional/limitProcessing.ts +39 -0
package/.cursorrules
CHANGED
|
@@ -26,6 +26,7 @@ NEVER EVER pass state to qreact.Component as a template parameter. It should ALS
|
|
|
26
26
|
|
|
27
27
|
t.lookup objects cannot be set with `this.state.someLookup = {}`. You can add keys or remove keys, and keys are added implicitly (`this.state.someLookup["someKey"] = { x: 1, y: 1 }`, always works, the key is automatically added if it doesn't exist). Removal uses delete `delete this.state.someLookup["someKey"]`.
|
|
28
28
|
|
|
29
|
+
Follow the rule of minimum scoping. If something can be a local variable, it should be a local variable, it shouldn't be a class variable. If it can be a class variable, it should be a class variable, not a state variable. Etc, etc. Keep it simple, reference things closer, and write less code.
|
|
29
30
|
|
|
30
31
|
Try not to use "null", and instead always use "undefined".
|
|
31
32
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.358.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",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
57
57
|
"pako": "^2.1.0",
|
|
58
58
|
"peggy": "^5.0.6",
|
|
59
|
+
"querysub": "^0.357.0",
|
|
59
60
|
"socket-function": "^1.1.2",
|
|
60
61
|
"terser": "^5.31.0",
|
|
61
62
|
"typesafecss": "^0.28.0",
|
|
@@ -75,7 +75,7 @@ class ArchivesDisk {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
// We don't time the file system writes as it's almost never blocking, it's not stopping our processing. And if it is stopping our processing, then we actually want to know the parent function which is calling it, and then we want it to just void it instead of awaiting it (And the only way to know the parent caller is to not time these functions).
|
|
79
79
|
public async set(fileName: string, data: Buffer): Promise<void> {
|
|
80
80
|
await this.init();
|
|
81
81
|
|
|
@@ -88,7 +88,6 @@ class ArchivesDisk {
|
|
|
88
88
|
|
|
89
89
|
await fs.promises.writeFile(this.LOCAL_ARCHIVE_FOLDER + fileName, data);
|
|
90
90
|
}
|
|
91
|
-
@measureFnc
|
|
92
91
|
public async append(fileName: string, data: Buffer): Promise<void> {
|
|
93
92
|
await this.init();
|
|
94
93
|
|
|
@@ -101,7 +100,6 @@ class ArchivesDisk {
|
|
|
101
100
|
|
|
102
101
|
await fs.promises.appendFile(this.LOCAL_ARCHIVE_FOLDER + fileName, data);
|
|
103
102
|
}
|
|
104
|
-
@measureFnc
|
|
105
103
|
public async del(fileName: string): Promise<void> {
|
|
106
104
|
await this.init();
|
|
107
105
|
this.log(blue(`Deleting file ${fileName}`));
|
|
@@ -112,8 +110,16 @@ class ArchivesDisk {
|
|
|
112
110
|
try {
|
|
113
111
|
await fs.promises.rm(this.LOCAL_ARCHIVE_FOLDER + fileName, { recursive: true });
|
|
114
112
|
} catch { }
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
try {
|
|
114
|
+
let dir = fileName;
|
|
115
|
+
while (dir.length > 0) {
|
|
116
|
+
dir = dir.replaceAll("\\", "/").split("/").slice(0, -1).join("/");
|
|
117
|
+
if (dir.endsWith(":")) break;
|
|
118
|
+
let files = await fs.promises.readdir(this.LOCAL_ARCHIVE_FOLDER + dir);
|
|
119
|
+
if (files.length > 0) break;
|
|
120
|
+
await fs.promises.rmdir(this.LOCAL_ARCHIVE_FOLDER + dir);
|
|
121
|
+
}
|
|
122
|
+
} catch { }
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
public async setLargeFile(config: { path: string; getNextData(): Promise<Buffer | undefined>; }): Promise<void> {
|
|
@@ -180,7 +186,8 @@ class ArchivesDisk {
|
|
|
180
186
|
* it all at once).
|
|
181
187
|
* @fsErrorRetryCount Retries count for file system error (not user errors). Ex, too many open files, etc.
|
|
182
188
|
*/
|
|
183
|
-
|
|
189
|
+
// NOTE: I've commented out the gate measuring so we can determine what is taking the time. We might want to remove all the timing from this file, as if we find any timing this file is slow, it doesn't help us optimize at all, because we then need to figure out what is calling it.
|
|
190
|
+
//@measureFnc
|
|
184
191
|
public async get(fileNameInput: string, config?: { range?: { start: number; end: number; }; retryCount?: number }): Promise<Buffer | undefined> {
|
|
185
192
|
await this.init();
|
|
186
193
|
this.log(blue(`Start read file ${fileNameInput}`));
|
|
@@ -36,13 +36,14 @@ export function createArchivesMemoryCache(
|
|
|
36
36
|
// - Also, it means if there's a range read that happens, if that range read doesn't return enough bytes, for example, we try to read from 0 to 100, but we only get 50 bytes back, we can only cache the 0 to 50, and then if they read from 0 to 100 again, we can use the cache, but we have to also read 50 to 100 from the source, because the file might have gotten larger.
|
|
37
37
|
fullyImmutable?: boolean;
|
|
38
38
|
stats?: ArchivesMemoryCacheStats;
|
|
39
|
+
sizeCache?: Map<string, number>;
|
|
39
40
|
}
|
|
40
41
|
): Archives {
|
|
41
42
|
let {
|
|
42
43
|
maxSize = 1024 * 1024 * 1024 * 4,
|
|
43
44
|
maxCount = 1000 * 1000,
|
|
44
45
|
fullyImmutable = false,
|
|
45
|
-
extraReadSize = 1024
|
|
46
|
+
extraReadSize = 1024 * 1024
|
|
46
47
|
} = config ?? {};
|
|
47
48
|
|
|
48
49
|
// Cache structure: Map from path to sorted array of ranges
|
|
@@ -199,13 +200,34 @@ export function createArchivesMemoryCache(
|
|
|
199
200
|
}
|
|
200
201
|
});
|
|
201
202
|
|
|
203
|
+
function updateStats(cached: boolean, size: number | undefined) {
|
|
204
|
+
if (!config?.stats) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (cached) {
|
|
208
|
+
config.stats.cachedReads++;
|
|
209
|
+
if (size !== undefined) {
|
|
210
|
+
config.stats.cachedReadSize += size;
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
config.stats.uncachedReads++;
|
|
214
|
+
if (size !== undefined) {
|
|
215
|
+
config.stats.uncachedReadSize += size;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
config.stats.totalCacheSize = totalSize;
|
|
219
|
+
config.stats.totalCacheCount = lruArray.length;
|
|
220
|
+
}
|
|
221
|
+
|
|
202
222
|
async function cachedGet(path: string, getConfig?: {
|
|
203
223
|
range?: { start: number; end: number; };
|
|
204
224
|
retryCount?: number;
|
|
205
225
|
fastRead?: boolean;
|
|
206
226
|
}): Promise<Buffer | undefined> {
|
|
207
227
|
if (cacheDisabled) {
|
|
208
|
-
|
|
228
|
+
let result = await archives.get(path, getConfig);
|
|
229
|
+
updateStats(false, result?.length);
|
|
230
|
+
return result;
|
|
209
231
|
}
|
|
210
232
|
|
|
211
233
|
let range = getConfig?.range;
|
|
@@ -214,12 +236,15 @@ export function createArchivesMemoryCache(
|
|
|
214
236
|
if (!range) {
|
|
215
237
|
// If not fully immutable, don't cache entire file reads (might have appends)
|
|
216
238
|
if (!fullyImmutable) {
|
|
217
|
-
|
|
239
|
+
let result = await archives.get(path, getConfig);
|
|
240
|
+
updateStats(false, result?.length);
|
|
241
|
+
return result;
|
|
218
242
|
}
|
|
219
243
|
|
|
220
244
|
// Get file info to determine size, then treat as range read
|
|
221
245
|
let info = await archives.getInfo(path);
|
|
222
246
|
if (!info) {
|
|
247
|
+
updateStats(false, undefined);
|
|
223
248
|
return undefined;
|
|
224
249
|
}
|
|
225
250
|
|
|
@@ -234,12 +259,20 @@ export function createArchivesMemoryCache(
|
|
|
234
259
|
{
|
|
235
260
|
for (let entry of cacheByPath.get(path) || []) {
|
|
236
261
|
if (entry.start <= start && entry.end >= end) {
|
|
237
|
-
|
|
262
|
+
let result = entry.data.slice(start - entry.start, end - entry.start);
|
|
263
|
+
updateStats(true, result.length);
|
|
264
|
+
return result;
|
|
238
265
|
}
|
|
239
266
|
}
|
|
240
267
|
}
|
|
241
268
|
|
|
242
|
-
let
|
|
269
|
+
let cachedSize = config?.sizeCache?.get(path);
|
|
270
|
+
if (cachedSize === undefined) {
|
|
271
|
+
cachedSize = ((await archives.getInfo(path))?.size || 0);
|
|
272
|
+
config?.sizeCache?.set(path, cachedSize);
|
|
273
|
+
updateStats(false, undefined);
|
|
274
|
+
}
|
|
275
|
+
let size = cachedSize;
|
|
243
276
|
if (!size) {
|
|
244
277
|
return undefined;
|
|
245
278
|
}
|
|
@@ -250,7 +283,7 @@ export function createArchivesMemoryCache(
|
|
|
250
283
|
let aligned = alignRange(start, end);
|
|
251
284
|
|
|
252
285
|
let readStart = aligned.start;
|
|
253
|
-
let readEnd = Math.min(aligned.end, size
|
|
286
|
+
let readEnd = Math.min(aligned.end, size);
|
|
254
287
|
|
|
255
288
|
let overlapping = findOverlappingEntries(path, start, end);
|
|
256
289
|
|
|
@@ -283,6 +316,7 @@ export function createArchivesMemoryCache(
|
|
|
283
316
|
});
|
|
284
317
|
|
|
285
318
|
if (!data) {
|
|
319
|
+
updateStats(false, undefined);
|
|
286
320
|
return undefined;
|
|
287
321
|
}
|
|
288
322
|
|
|
@@ -329,17 +363,7 @@ export function createArchivesMemoryCache(
|
|
|
329
363
|
let result = coveringEntry.data.slice(offsetStart, offsetEnd);
|
|
330
364
|
|
|
331
365
|
// Update statistics at the end when we know the final result
|
|
332
|
-
|
|
333
|
-
if (didRead) {
|
|
334
|
-
config.stats.uncachedReads++;
|
|
335
|
-
config.stats.uncachedReadSize += result.length;
|
|
336
|
-
} else {
|
|
337
|
-
config.stats.cachedReads++;
|
|
338
|
-
config.stats.cachedReadSize += result.length;
|
|
339
|
-
}
|
|
340
|
-
config.stats.totalCacheSize = totalSize;
|
|
341
|
-
config.stats.totalCacheCount = lruArray.length;
|
|
342
|
-
}
|
|
366
|
+
updateStats(!didRead, result.length);
|
|
343
367
|
|
|
344
368
|
return result;
|
|
345
369
|
}
|
|
@@ -9,6 +9,8 @@ import { ATag, Anchor } from "../../library-components/ATag";
|
|
|
9
9
|
import { ShowMore } from "../../library-components/ShowMore";
|
|
10
10
|
import { filterParam } from "../../diagnostics/logs/FastArchiveViewer";
|
|
11
11
|
import { managementPageURL } from "../../diagnostics/managementPages";
|
|
12
|
+
import { t } from "../../2-proxy/schema2";
|
|
13
|
+
import { Querysub } from "../../4-querysub/QuerysubController";
|
|
12
14
|
|
|
13
15
|
export class MachineDetailPage extends qreact.Component {
|
|
14
16
|
render() {
|
|
@@ -18,12 +20,14 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
18
20
|
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
19
21
|
let machineInfo = controller.getMachineInfo(selectedMachineId);
|
|
20
22
|
let serviceList = controller.getServiceList();
|
|
23
|
+
let machineConfig = controller.getMachineConfig(selectedMachineId);
|
|
21
24
|
|
|
22
25
|
if (controller.isAnyLoading()) return <div>Loading machine info...</div>;
|
|
23
26
|
if (!machineInfo) return <div>Machine not found</div>;
|
|
24
27
|
if (!serviceList) return <div>Service list not found</div>;
|
|
25
28
|
|
|
26
|
-
const machine = machineInfo;
|
|
29
|
+
const machine = machineInfo;
|
|
30
|
+
const isDisabled = machineConfig?.disabled || false;
|
|
27
31
|
|
|
28
32
|
// Get all service configs that target this machine
|
|
29
33
|
let relevantServiceConfigs = new Map<string, ServiceConfig>();
|
|
@@ -47,12 +51,49 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
47
51
|
|
|
48
52
|
const isMachineDead = Date.now() - machine.heartbeat > (MACHINE_RESYNC_INTERVAL * 4);
|
|
49
53
|
|
|
54
|
+
let failingServices = Object.keys(machine.services).filter(serviceId => {
|
|
55
|
+
return machine.services[serviceId].errorFromLastRun;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let backgroundColor = (
|
|
59
|
+
isDisabled && css.hsl(0, 0, 50)
|
|
60
|
+
|| failingServices.length > 0 && css.hsl(0, 50, 60)
|
|
61
|
+
|| isMachineDead && css.hsl(45, 80, 80)
|
|
62
|
+
|| css.hsl(0, 0, 100)
|
|
63
|
+
);
|
|
64
|
+
|
|
50
65
|
return <div className={css.vbox(16)}>
|
|
51
|
-
<div className={css.hbox(12)}>
|
|
66
|
+
<div className={css.hbox(12).pad2(16).bord2(0, 0, 20) + backgroundColor}>
|
|
52
67
|
<h2 className={css.flexGrow(1)}>{selectedMachineId}</h2>
|
|
53
68
|
{isMachineDead && <div className={css.colorhsl(0, 80, 60)}>
|
|
54
69
|
⚠️ Machine is likely dead
|
|
55
70
|
</div>}
|
|
71
|
+
{isDisabled && <div className={css.colorhsl(30, 80, 60).pad2(8, 4).bord2(30, 80, 60).hsl(30, 80, 95)}>
|
|
72
|
+
⚠️ Machine is disabled
|
|
73
|
+
</div>}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className={css.hbox(12).pad2(16).bord2(0, 0, 20).hsl(0, 0, 95)}>
|
|
77
|
+
<div className={css.flexGrow(1).vbox(4)}>
|
|
78
|
+
<div className={css.fontSize(16).colorhsl(0, 0, 20)}>
|
|
79
|
+
<b>Machine Status</b>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
{isDisabled && "When disabled, services will not be deployed to this machine. Re-enabling will restore all service assignments." || "This machine is enabled and available for service deployments."}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<button
|
|
86
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20) + (isDisabled ? css.hsl(120, 60, 90) : css.hsl(30, 80, 90))}
|
|
87
|
+
onClick={() => {
|
|
88
|
+
Querysub.onCommitFinished(async () => {
|
|
89
|
+
await controller.setMachineConfig.promise(selectedMachineId, {
|
|
90
|
+
machineId: selectedMachineId,
|
|
91
|
+
disabled: !isDisabled
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}}>
|
|
95
|
+
{isDisabled && "Enable Machine" || "Disable Machine"}
|
|
96
|
+
</button>
|
|
56
97
|
</div>
|
|
57
98
|
|
|
58
99
|
<div className={css.vbox(12)}>
|
|
@@ -28,6 +28,8 @@ export class MachinesListPage extends qreact.Component {
|
|
|
28
28
|
let machines = machineList.map(machineId => [machineId, controller.getMachineInfo(machineId)] as const).filter(x => x[1]);
|
|
29
29
|
sort(machines, x => -(x[1]?.heartbeat || 0));
|
|
30
30
|
|
|
31
|
+
let machineConfigs = controller.getMachineConfigList();
|
|
32
|
+
|
|
31
33
|
const selectedMachineIds = Object.keys(this.state.selectedForDeletion);
|
|
32
34
|
const hasSelectedMachines = selectedMachineIds.length > 0;
|
|
33
35
|
const DEAD_MACHINE_THRESHOLD = MACHINE_RESYNC_INTERVAL * 4;
|
|
@@ -121,6 +123,8 @@ export class MachinesListPage extends qreact.Component {
|
|
|
121
123
|
const serviceCount = Object.keys(machineInfo.services).length;
|
|
122
124
|
const isMachineDead = Date.now() - machineInfo.heartbeat > (MACHINE_RESYNC_INTERVAL * 4);
|
|
123
125
|
const isSelected = this.state.selectedForDeletion[machineId];
|
|
126
|
+
const machineConfig = machineConfigs?.find(x => x.machineId === machineId);
|
|
127
|
+
const isDisabled = machineConfig?.disabled || false;
|
|
124
128
|
|
|
125
129
|
let failingServices = Object.keys(machineInfo.services).filter(serviceId => {
|
|
126
130
|
return machineInfo.services[serviceId].errorFromLastRun;
|
|
@@ -136,8 +140,9 @@ export class MachinesListPage extends qreact.Component {
|
|
|
136
140
|
className={
|
|
137
141
|
css.pad2(12).bord2(0, 0, 20).button
|
|
138
142
|
+ (
|
|
139
|
-
|
|
140
|
-
||
|
|
143
|
+
isDisabled && css.hsl(0, 0, 50)
|
|
144
|
+
|| failingServices.length > 0 && css.hsl(0, 50, 60)
|
|
145
|
+
|| isMachineDead && css.hsl(45, 80, 80)
|
|
141
146
|
|| css.hsl(0, 0, 100)
|
|
142
147
|
)
|
|
143
148
|
+ (this.state.isDeleteMode && isSelected && css.bord2(200, 80, 60, 2))
|
|
@@ -165,6 +170,9 @@ export class MachinesListPage extends qreact.Component {
|
|
|
165
170
|
/>
|
|
166
171
|
</div>
|
|
167
172
|
)}
|
|
173
|
+
{isDisabled && <div className={css.colorhsl(30, 80, 60).pad2(4, 2).bord2(30, 80, 60).hsl(30, 80, 95)}>
|
|
174
|
+
🚫 Disabled
|
|
175
|
+
</div>}
|
|
168
176
|
{isMachineDead && <div className={css.colorhsl(0, 80, 50)}>
|
|
169
177
|
⚠️ Machine is likely dead
|
|
170
178
|
</div>}
|
|
@@ -4,7 +4,7 @@ import { measureWrap } from "socket-function/src/profiling/measure";
|
|
|
4
4
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
5
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, SERVICE_NODE_FILE_NAME } from "./machineSchema";
|
|
7
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, serviceConfigs, SERVICE_FOLDER, machineInfos, SERVICE_NODE_FILE_NAME, getEffectiveServiceConfigs } 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";
|
|
@@ -579,7 +579,7 @@ async function quickIsOutdated() {
|
|
|
579
579
|
return true;
|
|
580
580
|
}
|
|
581
581
|
}
|
|
582
|
-
let configs = await
|
|
582
|
+
let configs = await getEffectiveServiceConfigs();
|
|
583
583
|
let relevantConfigs = configs.filter(config => config.machineIds.includes(machineId)).filter(x => x.parameters.deploy);
|
|
584
584
|
let screens = await getScreenState();
|
|
585
585
|
let root = os.homedir() + "/" + SERVICE_FOLDER;
|
|
@@ -608,7 +608,7 @@ async function quickIsOutdated() {
|
|
|
608
608
|
const resyncServicesBase = runInSerial(measureWrap(async function resyncServices() {
|
|
609
609
|
console.log(magenta("Resyncing services"));
|
|
610
610
|
let machineId = getOwnMachineId();
|
|
611
|
-
let configs = await
|
|
611
|
+
let configs = await getEffectiveServiceConfigs();
|
|
612
612
|
let relevantConfigs = configs.filter(config => config.machineIds.includes(machineId)).filter(x => x.parameters.deploy);
|
|
613
613
|
|
|
614
614
|
let machineInfo = await getLiveMachineInfo();
|
|
@@ -93,8 +93,13 @@ export type ServiceConfig = {
|
|
|
93
93
|
lastUpdatedTime: number;
|
|
94
94
|
};
|
|
95
95
|
};
|
|
96
|
+
export type MachineConfig = {
|
|
97
|
+
machineId: string;
|
|
98
|
+
disabled: boolean;
|
|
99
|
+
};
|
|
96
100
|
export const machineInfos = archiveJSONT<MachineInfo>(() => nestArchives("machines/machine-heartbeats/", getArchivesBackblaze(getDomain())));
|
|
97
101
|
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => nestArchives("machines/service-configs/", getArchivesBackblaze(getDomain())));
|
|
102
|
+
export const machineConfigs = archiveJSONT<MachineConfig>(() => nestArchives("machines/machine-configs/", getArchivesBackblaze(getDomain())));
|
|
98
103
|
|
|
99
104
|
export function doRegisterNodeForMachineCleanup() {
|
|
100
105
|
if (isNode()) {
|
|
@@ -153,6 +158,16 @@ export class MachineServiceControllerBase {
|
|
|
153
158
|
return await serviceConfigs.get(serviceId);
|
|
154
159
|
}
|
|
155
160
|
|
|
161
|
+
public async getMachineConfigList() {
|
|
162
|
+
return await machineConfigs.values();
|
|
163
|
+
}
|
|
164
|
+
public async getMachineConfig(machineId: string): Promise<MachineConfig | undefined> {
|
|
165
|
+
return await machineConfigs.get(machineId);
|
|
166
|
+
}
|
|
167
|
+
public async setMachineConfig(machineId: string, config: MachineConfig) {
|
|
168
|
+
await machineConfigs.set(machineId, config);
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
private async notifyMachines(newMachineIds: string[], oldMachineIds: string[]) {
|
|
157
172
|
let addedNodeIds = new Set<string>();
|
|
158
173
|
let removedNodeIds = new Set<string>();
|
|
@@ -358,6 +373,24 @@ export class MachineServiceControllerBase {
|
|
|
358
373
|
});
|
|
359
374
|
}
|
|
360
375
|
}
|
|
376
|
+
|
|
377
|
+
export async function getEffectiveServiceConfigs(): Promise<ServiceConfig[]> {
|
|
378
|
+
let configs = await serviceConfigs.values();
|
|
379
|
+
let allMachineConfigs = await machineConfigs.values();
|
|
380
|
+
|
|
381
|
+
let disabledMachineIds = new Set<string>();
|
|
382
|
+
for (let machineConfig of allMachineConfigs) {
|
|
383
|
+
if (machineConfig.disabled) {
|
|
384
|
+
disabledMachineIds.add(machineConfig.machineId);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return configs.map(config => ({
|
|
389
|
+
...config,
|
|
390
|
+
machineIds: config.machineIds.filter(machineId => !disabledMachineIds.has(machineId)),
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
|
|
361
394
|
let deployWatchers = new Set<DeployProgress>();
|
|
362
395
|
class DeployProgressControllerBase {
|
|
363
396
|
async onDeployProgress(config: { section: string; progress: number; }) {
|
|
@@ -401,6 +434,9 @@ export const MachineServiceController = getSyncedController(
|
|
|
401
434
|
setMachineInfo: {},
|
|
402
435
|
getServiceList: {},
|
|
403
436
|
getServiceConfig: {},
|
|
437
|
+
getMachineConfigList: {},
|
|
438
|
+
getMachineConfig: {},
|
|
439
|
+
setMachineConfig: {},
|
|
404
440
|
addServiceConfig: {},
|
|
405
441
|
setServiceConfigs: {},
|
|
406
442
|
getServiceConfigType: {},
|
|
@@ -422,6 +458,7 @@ export const MachineServiceController = getSyncedController(
|
|
|
422
458
|
deleteMachineIds: ["MachineInfo", "MachineInfoList"],
|
|
423
459
|
addMachineInfo: ["MachineInfo", "MachineInfoList"],
|
|
424
460
|
setMachineInfo: ["MachineInfo"],
|
|
461
|
+
setMachineConfig: ["MachineConfig", "MachineConfigList"],
|
|
425
462
|
addServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
426
463
|
setServiceConfigs: ["ServiceConfig"],
|
|
427
464
|
deleteServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
@@ -432,6 +469,8 @@ export const MachineServiceController = getSyncedController(
|
|
|
432
469
|
reads: {
|
|
433
470
|
getMachineList: ["MachineInfoList"],
|
|
434
471
|
getMachineInfo: ["MachineInfo"],
|
|
472
|
+
getMachineConfigList: ["MachineConfigList"],
|
|
473
|
+
getMachineConfig: ["MachineConfig"],
|
|
435
474
|
getServiceList: ["ServiceConfigList"],
|
|
436
475
|
getServiceConfig: ["ServiceConfig"],
|
|
437
476
|
getGitInfo: ["gitInfo"],
|
|
@@ -80,6 +80,7 @@ export class NodeViewer extends qreact.Component {
|
|
|
80
80
|
Querysub.commit(() => this.state.nodeIds = nodeIds);
|
|
81
81
|
|
|
82
82
|
let ourIP = await controller.getCallerIP();
|
|
83
|
+
let ourExternalIP = await getExternalIP();
|
|
83
84
|
|
|
84
85
|
await Promise.allSettled(nodeIds.map(async nodeId => {
|
|
85
86
|
let time = Date.now();
|
|
@@ -97,7 +98,7 @@ export class NodeViewer extends qreact.Component {
|
|
|
97
98
|
data.live_entryPoint = await controller.live_getEntryPoint(nodeId);
|
|
98
99
|
data.live_authorityPaths = await controller.live_getAuthorityPaths(nodeId);
|
|
99
100
|
data.ip = await controller.getNodeIP(nodeId);
|
|
100
|
-
if (data.ip ===
|
|
101
|
+
if (data.ip === ourExternalIP || data.ip === ourIP) {
|
|
101
102
|
data.devToolsURL = await controller.getInspectURL(nodeId);
|
|
102
103
|
if (data.devToolsURL) {
|
|
103
104
|
data.devToolsURL = data.devToolsURL + "<inspect>";
|