querysub 0.251.0 → 0.253.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 +3 -3
- package/src/0-path-value-core/NodePathAuthorities.ts +1 -0
- package/src/3-path-functions/PathFunctionRunner.ts +1 -0
- package/src/4-deploy/edgeBootstrap.ts +10 -0
- package/src/4-deploy/edgeNodes.ts +1 -1
- package/src/4-deploy/git.ts +34 -1
- package/src/deployManager/MachinesPage.tsx +7 -10
- package/src/deployManager/components/MachineDetailPage.tsx +13 -21
- package/src/deployManager/components/MachinesListPage.tsx +60 -49
- package/src/deployManager/components/ServiceDetailPage.tsx +43 -12
- package/src/deployManager/components/ServicesListPage.tsx +42 -32
- package/src/deployManager/components/deployButtons.tsx +212 -0
- package/src/deployManager/machineApplyMainCode.ts +11 -5
- package/src/deployManager/machineController.ts +31 -4
- package/src/deployManager/machineSchema.ts +135 -46
- package/src/deployManager/spec.txt +11 -35
- package/src/deployManager/urlParams.ts +1 -2
- package/src/diagnostics/errorLogs/ErrorLogController.ts +1 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +6 -0
- package/src/diagnostics/managementPages.tsx +6 -5
- package/src/library-components/SyncedController.ts +91 -31
- package/src/misc/formatJSX.tsx +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.253.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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"js-sha512": "^0.9.0",
|
|
23
23
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
24
24
|
"pako": "^2.1.0",
|
|
25
|
-
"socket-function": "^0.
|
|
25
|
+
"socket-function": "^0.134.0",
|
|
26
26
|
"terser": "^5.31.0",
|
|
27
27
|
"typesafecss": "^0.22.0",
|
|
28
28
|
"yaml": "^2.5.0",
|
|
@@ -64,4 +64,4 @@
|
|
|
64
64
|
"resolutions": {
|
|
65
65
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"
|
|
66
66
|
}
|
|
67
|
-
}
|
|
67
|
+
}
|
|
@@ -366,6 +366,7 @@ class NodePathAuthorities {
|
|
|
366
366
|
// as being out of date.
|
|
367
367
|
let now = Date.now();
|
|
368
368
|
let isReadReady = await errorToUndefinedSilent(PathController.nodes[nodeId].isReadReady());
|
|
369
|
+
|
|
369
370
|
// If the node is gone, delete it from authorities
|
|
370
371
|
if (isReadReady === undefined) {
|
|
371
372
|
console.error(yellow(`Node errored out, removing from authorities ${nodeId}`));
|
|
@@ -354,6 +354,7 @@ export class PathFunctionRunner {
|
|
|
354
354
|
let runCallPromise = self.runCall(call, functionSpec);
|
|
355
355
|
void runCallPromise.finally(() => {
|
|
356
356
|
outstandingCalls--;
|
|
357
|
+
// IMPORTANT! We remove the call, NOT for GC, but! Because if the call writes are rejected (due to contention), then the Result will also be rejected, changing it back to undefined, so (as long as little enough time has passed), we will run the call again! Or rather, we will try to, as long as runningCalls doesn't have the value!
|
|
357
358
|
runningCalls.delete(call.CallId);
|
|
358
359
|
});
|
|
359
360
|
logErrors(runCallPromise);
|
|
@@ -290,6 +290,16 @@ async function edgeNodeFunction(config: {
|
|
|
290
290
|
}
|
|
291
291
|
async function getEdgeNodeConfig(): Promise<EdgeNodeConfig> {
|
|
292
292
|
let edgeIndex = cachedConfig || await (await fetch(config.edgeNodeConfigURL)).json() as EdgeNodesIndex;
|
|
293
|
+
|
|
294
|
+
edgeIndex.edgeNodes.sort((a, b) => -(a.bootTime - b.bootTime));
|
|
295
|
+
// Filter out duplicate hostnames (taking the max bootTime). We can't have multiple services using the same port, so duplicates are DEFINITELY dead. This fixes some issues with accessing old services, and generally makes reading the logs easier.
|
|
296
|
+
let hostNames = new Set<string>();
|
|
297
|
+
edgeIndex.edgeNodes = edgeIndex.edgeNodes.filter(x => {
|
|
298
|
+
if (hostNames.has(x.host)) return false;
|
|
299
|
+
hostNames.add(x.host);
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
|
|
293
303
|
cachedConfig = undefined;
|
|
294
304
|
let liveHashForced = false;
|
|
295
305
|
if (Date.now() < liveHashOverrideExpiryTime) {
|
|
@@ -7,7 +7,7 @@ import { getArchivesBackblazePublic } from "../-a-archives/archivesBackBlaze";
|
|
|
7
7
|
import { nestArchives } from "../-a-archives/archives";
|
|
8
8
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
9
9
|
import { runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
10
|
-
import { compare, compareArray, isNodeTrue, timeInMinute } from "socket-function/src/misc";
|
|
10
|
+
import { compare, compareArray, isNodeTrue, sort, timeInMinute } from "socket-function/src/misc";
|
|
11
11
|
import { cacheLimited, lazy } from "socket-function/src/caching";
|
|
12
12
|
import { canHaveChildren } from "socket-function/src/types";
|
|
13
13
|
import { shutdown } from "../diagnostics/periodic";
|
package/src/4-deploy/git.ts
CHANGED
|
@@ -11,6 +11,40 @@ export async function getGitRefLive(gitDir = ".") {
|
|
|
11
11
|
return (await runPromise(`git rev-parse HEAD`, { cwd: gitDir })).trim();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export async function getGitUncommitted(gitDir = "."): Promise<string[]> {
|
|
15
|
+
return (await runPromise(`git status --porcelain`, { cwd: gitDir })).split("\n").map(x => x.trim()).filter(x => x);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function getLatestRefOnUpstreamBranch(gitDir = ".") {
|
|
19
|
+
await runPromise(`git fetch`, { cwd: gitDir });
|
|
20
|
+
return (await runPromise(`git rev-parse @{upstream}`, { cwd: gitDir })).trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function commitAndPush(config: {
|
|
24
|
+
gitDir: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}) {
|
|
27
|
+
await runPromise(`git add --all`, { cwd: config.gitDir });
|
|
28
|
+
await runPromise(`git commit -m "${config.message}"`, { cwd: config.gitDir });
|
|
29
|
+
await runPromise(`git push`, { cwd: config.gitDir });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getGitRefInfo(config: {
|
|
33
|
+
gitDir: string;
|
|
34
|
+
ref: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
time: number;
|
|
37
|
+
description: string;
|
|
38
|
+
}> {
|
|
39
|
+
const timeOutput = await runPromise(`git show --format="%ct" -s ${config.ref}`, { cwd: config.gitDir });
|
|
40
|
+
const descriptionOutput = await runPromise(`git show --format="%s" -s ${config.ref}`, { cwd: config.gitDir });
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
time: parseInt(timeOutput.trim(), 10) * 1000,
|
|
44
|
+
description: descriptionOutput.trim()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
14
48
|
export const getGitURLSync = cache(function getGitURLSync(gitDir?: string) {
|
|
15
49
|
return child_process.execSync(`git remote get-url origin`, { cwd: gitDir || "." }).toString().trim();
|
|
16
50
|
});
|
|
@@ -20,7 +54,6 @@ export const getGitRefSync = cache(function getGitRefSync(gitDir?: string) {
|
|
|
20
54
|
|
|
21
55
|
export const setGitRef = measureWrap(async function setGitRef(config: {
|
|
22
56
|
gitFolder: string;
|
|
23
|
-
repoUrl: string;
|
|
24
57
|
gitRef: string;
|
|
25
58
|
}) {
|
|
26
59
|
await fs.promises.mkdir(config.gitFolder, { recursive: true });
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../4-dom/qreact";
|
|
3
|
-
import {
|
|
3
|
+
import { MachineServiceController } from "./machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { currentViewParam, selectedServiceIdParam, selectedMachineIdParam } from "./urlParams";
|
|
6
6
|
import { ServicesListPage } from "./components/ServicesListPage";
|
|
7
7
|
import { MachinesListPage } from "./components/MachinesListPage";
|
|
8
8
|
import { ServiceDetailPage } from "./components/ServiceDetailPage";
|
|
9
9
|
import { MachineDetailPage } from "./components/MachineDetailPage";
|
|
10
|
+
import { Anchor } from "../library-components/ATag";
|
|
10
11
|
|
|
11
12
|
export class MachinesPage extends qreact.Component {
|
|
12
13
|
private renderTabs() {
|
|
@@ -19,24 +20,20 @@ export class MachinesPage extends qreact.Component {
|
|
|
19
20
|
{ key: "services", label: "Services", otherKeys: ["service-detail"] },
|
|
20
21
|
].map(tab => {
|
|
21
22
|
let isActive = currentViewParam.value === tab.key || tab.otherKeys.includes(currentViewParam.value);
|
|
22
|
-
return <
|
|
23
|
+
return <Anchor noStyles key={tab.key}
|
|
24
|
+
values={[currentViewParam.getOverride(tab.key as any), selectedServiceIdParam.getOverride(""), selectedMachineIdParam.getOverride("")]}
|
|
23
25
|
className={css.pad2(12, 8).button
|
|
24
26
|
+ (isActive && css.hsl(0, 0, 100).bord2(210, 50, 50))
|
|
25
27
|
+ (!isActive && css.hsl(0, 0, 70).colorhsl(0, 0, 50).bord2(0, 0, 50))
|
|
26
|
-
}
|
|
27
|
-
onClick={() => {
|
|
28
|
-
currentViewParam.value = tab.key as any;
|
|
29
|
-
selectedServiceIdParam.value = "";
|
|
30
|
-
selectedMachineIdParam.value = "";
|
|
31
|
-
}}>
|
|
28
|
+
}>
|
|
32
29
|
{tab.label}
|
|
33
|
-
</
|
|
30
|
+
</Anchor>;
|
|
34
31
|
})}
|
|
35
32
|
</div>;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
render() {
|
|
39
|
-
let controller =
|
|
36
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
40
37
|
let serviceConfigType = controller.getServiceConfigType();
|
|
41
38
|
if (!serviceConfigType) return <div>Loading...</div>;
|
|
42
39
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MACHINE_RESYNC_INTERVAL,
|
|
3
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { currentViewParam, selectedMachineIdParam, selectedServiceIdParam } from "../urlParams";
|
|
6
6
|
import { formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
7
7
|
import { sort } from "socket-function/src/misc";
|
|
8
|
+
import { Anchor } from "../../library-components/ATag";
|
|
8
9
|
|
|
9
10
|
export class MachineDetailPage extends qreact.Component {
|
|
10
11
|
render() {
|
|
11
12
|
const selectedMachineId = selectedMachineIdParam.value;
|
|
12
13
|
if (!selectedMachineId) return <div>No machine selected</div>;
|
|
13
14
|
|
|
14
|
-
let controller =
|
|
15
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
15
16
|
let machineInfo = controller.getMachineInfo(selectedMachineId);
|
|
16
17
|
let serviceList = controller.getServiceList();
|
|
17
18
|
|
|
@@ -77,14 +78,10 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
77
78
|
<h3>⚠️ Configured Services Not Running ({configuredButNotRunning.length})</h3>
|
|
78
79
|
<div className={css.vbox(8)}>
|
|
79
80
|
{configuredButNotRunning.map(([serviceId, serviceConfig]) => (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
onClick={() => {
|
|
85
|
-
currentViewParam.value = "service-detail";
|
|
86
|
-
selectedServiceIdParam.value = serviceId;
|
|
87
|
-
}}>
|
|
81
|
+
<Anchor noStyles
|
|
82
|
+
key={serviceId}
|
|
83
|
+
values={[currentViewParam.getOverride("service-detail"), selectedServiceIdParam.getOverride(serviceId)]}
|
|
84
|
+
className={css.pad2(12).button.bord2(0, 0, 20).hsl(0, 70, 90)}>
|
|
88
85
|
<div className={css.hbox(12)}>
|
|
89
86
|
<div className={css.flexGrow(1)}>
|
|
90
87
|
{serviceConfig.info.title} ({serviceConfig.parameters.key} / {serviceId})
|
|
@@ -96,7 +93,7 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
96
93
|
Deploy: {serviceConfig.parameters.deploy ? "Yes" : "No"}
|
|
97
94
|
</div>
|
|
98
95
|
</div>
|
|
99
|
-
</
|
|
96
|
+
</Anchor>
|
|
100
97
|
))}
|
|
101
98
|
</div>
|
|
102
99
|
</div>
|
|
@@ -118,15 +115,10 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
return (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
onClick={() => {
|
|
127
|
-
currentViewParam.value = "service-detail";
|
|
128
|
-
selectedServiceIdParam.value = serviceId;
|
|
129
|
-
}}>
|
|
118
|
+
<Anchor noStyles
|
|
119
|
+
key={serviceId}
|
|
120
|
+
values={[currentViewParam.getOverride("service-detail"), selectedServiceIdParam.getOverride(serviceId)]}
|
|
121
|
+
className={css.pad2(12).button.bord2(0, 0, 20) + backgroundColor}>
|
|
130
122
|
<div className={css.hbox(12)}>
|
|
131
123
|
<div className={css.flexGrow(1)}>
|
|
132
124
|
{serviceConfig && <>{serviceConfig.info.title} ({serviceConfig.parameters.key})</> || <>serviceId = {serviceId}</>}
|
|
@@ -148,7 +140,7 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
148
140
|
</div>
|
|
149
141
|
)}
|
|
150
142
|
</div>
|
|
151
|
-
</
|
|
143
|
+
</Anchor>
|
|
152
144
|
);
|
|
153
145
|
})}
|
|
154
146
|
</div>
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MACHINE_RESYNC_INTERVAL,
|
|
3
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceController } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { t } from "../../2-proxy/schema2";
|
|
6
6
|
import { Querysub } from "../../4-querysub/QuerysubController";
|
|
7
7
|
import { currentViewParam, selectedMachineIdParam } from "../urlParams";
|
|
8
8
|
import { formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
9
9
|
import { sort } from "socket-function/src/misc";
|
|
10
|
+
import { formatTimeJSX } from "../../misc/formatJSX";
|
|
11
|
+
import { DeployMachineButtons, RenderGitRefInfo } from "./deployButtons";
|
|
12
|
+
import { isDefined } from "../../misc";
|
|
10
13
|
|
|
11
14
|
export class MachinesListPage extends qreact.Component {
|
|
12
15
|
state = t.state({
|
|
@@ -16,7 +19,7 @@ export class MachinesListPage extends qreact.Component {
|
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
render() {
|
|
19
|
-
let controller =
|
|
22
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
20
23
|
let machineList = controller.getMachineList();
|
|
21
24
|
|
|
22
25
|
if (!machineList) return <div>Loading machines...</div>;
|
|
@@ -48,10 +51,12 @@ export class MachinesListPage extends qreact.Component {
|
|
|
48
51
|
this.state.selectedForDeletion[machineId] = true;
|
|
49
52
|
}
|
|
50
53
|
}}>
|
|
51
|
-
🗑️ Delete Machines
|
|
54
|
+
🗑️ Delete Dead Machines
|
|
52
55
|
</button>
|
|
53
56
|
)}
|
|
54
57
|
|
|
58
|
+
<DeployMachineButtons machines={machines.map(x => x[1]).filter(isDefined)} />
|
|
59
|
+
|
|
55
60
|
{this.state.isDeleteMode && (
|
|
56
61
|
<div className={css.hbox(8)}>
|
|
57
62
|
<div className={css.pad2(8, 4)}>
|
|
@@ -116,58 +121,64 @@ export class MachinesListPage extends qreact.Component {
|
|
|
116
121
|
return acc + (service.totalTimesLaunched || 0);
|
|
117
122
|
}, 0);
|
|
118
123
|
|
|
119
|
-
return <div
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (this.state.
|
|
132
|
-
|
|
124
|
+
return <div className={css.hbox(10)}>
|
|
125
|
+
<div
|
|
126
|
+
className={
|
|
127
|
+
css.pad2(12).bord2(0, 0, 20).button
|
|
128
|
+
+ (
|
|
129
|
+
failingServices.length > 0 && css.hsl(0, 50, 60)
|
|
130
|
+
|| isMachineDead && css.hsl(0, 0, 50)
|
|
131
|
+
|| css.hsl(0, 0, 100)
|
|
132
|
+
)
|
|
133
|
+
+ (this.state.isDeleteMode && isSelected && css.bord2(200, 80, 60, 2))
|
|
134
|
+
}
|
|
135
|
+
onClick={() => {
|
|
136
|
+
if (this.state.isDeleteMode) {
|
|
137
|
+
if (this.state.selectedForDeletion[machineId]) {
|
|
138
|
+
delete this.state.selectedForDeletion[machineId];
|
|
139
|
+
} else {
|
|
140
|
+
this.state.selectedForDeletion[machineId] = true;
|
|
141
|
+
}
|
|
133
142
|
} else {
|
|
134
|
-
|
|
143
|
+
currentViewParam.value = "machine-detail";
|
|
144
|
+
selectedMachineIdParam.value = machineId;
|
|
135
145
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
Last heartbeat {formatVeryNiceDateTime(machineInfo.heartbeat)}
|
|
158
|
-
</div>
|
|
159
|
-
<div className={css.vbox(4).flexGrow(1)}>
|
|
160
|
-
<div>
|
|
161
|
-
{machineId}
|
|
162
|
-
</div>
|
|
163
|
-
<div>
|
|
164
|
-
{machineInfo.info["getExternalIP"]}
|
|
146
|
+
}}>
|
|
147
|
+
<div className={css.hbox(12)}>
|
|
148
|
+
{this.state.isDeleteMode && (
|
|
149
|
+
<div className={css.flexShrink0}>
|
|
150
|
+
<input
|
|
151
|
+
type="checkbox"
|
|
152
|
+
checked={isSelected}
|
|
153
|
+
onChange={() => { }} // Controlled by onClick above
|
|
154
|
+
onClick={(e) => e.stopPropagation()} // Prevent double-toggle
|
|
155
|
+
className={css.size(16, 16)}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
{isMachineDead && <div className={css.colorhsl(0, 80, 50)}>
|
|
160
|
+
⚠️ Machine is likely dead
|
|
161
|
+
</div>}
|
|
162
|
+
<div className={css.vbox(4)}>
|
|
163
|
+
<div>
|
|
164
|
+
Last heartbeat {formatTimeJSX(machineInfo.heartbeat)}
|
|
165
|
+
</div>
|
|
166
|
+
<RenderGitRefInfo gitRef={machineInfo.gitRef} />
|
|
165
167
|
</div>
|
|
166
|
-
<div>
|
|
167
|
-
|
|
168
|
+
<div className={css.vbox(4).flexGrow(1)}>
|
|
169
|
+
<div>
|
|
170
|
+
{machineId}
|
|
171
|
+
</div>
|
|
172
|
+
<div>
|
|
173
|
+
{machineInfo.info["getExternalIP"]}
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
{serviceCount} services {failingServices.length > 0 ? `(${failingServices.length} failing)` : ""} • {totalLaunches} launches
|
|
177
|
+
</div>
|
|
168
178
|
</div>
|
|
169
179
|
</div>
|
|
170
180
|
</div>
|
|
181
|
+
<DeployMachineButtons machines={[machineInfo]} />
|
|
171
182
|
</div>;
|
|
172
183
|
})}
|
|
173
184
|
</div>
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MACHINE_RESYNC_INTERVAL,
|
|
3
|
+
import { 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/QuerysubController";
|
|
7
7
|
import { currentViewParam, selectedServiceIdParam, selectedMachineIdParam } from "../urlParams";
|
|
8
8
|
import { formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
9
9
|
import { InputPicker } from "../../library-components/InputPicker";
|
|
10
|
-
import { nextId, sort } from "socket-function/src/misc";
|
|
10
|
+
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
14
|
import { watchScreenOutput, stopWatchingScreenOutput } from "../machineController";
|
|
15
15
|
import { getPathStr2 } from "../../path";
|
|
16
|
+
import { Anchor } from "../../library-components/ATag";
|
|
16
17
|
import { ScrollOnMount } from "../../library-components/ScrollOnMount";
|
|
17
18
|
import { StickyBottomScroll } from "../../library-components/StickyBottomScroll";
|
|
18
19
|
import { PrimitiveDisplay } from "../../diagnostics/logs/ObjectDisplay";
|
|
@@ -89,6 +90,8 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
private updateUnsavedChanges(updatedConfig: ServiceConfig) {
|
|
93
|
+
// Do not let them update the serviceId, as that would break things
|
|
94
|
+
updatedConfig.serviceId = selectedServiceIdParam.value;
|
|
92
95
|
const newConfigStr = JSON.stringify(updatedConfig, null, 4);
|
|
93
96
|
this.state.unsavedChanges = updatedConfig;
|
|
94
97
|
|
|
@@ -159,6 +162,8 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
159
162
|
if (!selectedServiceId) return;
|
|
160
163
|
|
|
161
164
|
Querysub.commit(() => {
|
|
165
|
+
// Do not let them update the serviceId, as that would break things
|
|
166
|
+
updatedConfig.serviceId = selectedServiceIdParam.value;
|
|
162
167
|
this.state.isSaving = true;
|
|
163
168
|
this.state.saveError = "";
|
|
164
169
|
});
|
|
@@ -175,7 +180,7 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
175
180
|
model.setValue(`const config: ServiceConfig = ${newConfigStr}`);
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
|
-
await
|
|
183
|
+
await MachineServiceController(SocketFunction.browserNodeId()).setServiceConfigs.promise([updatedConfig]);
|
|
179
184
|
} catch (error) {
|
|
180
185
|
Querysub.localCommit(() => {
|
|
181
186
|
this.state.saveError = error instanceof Error ? error.message : String(error);
|
|
@@ -194,7 +199,7 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
194
199
|
const selectedServiceId = selectedServiceIdParam.value;
|
|
195
200
|
if (!selectedServiceId) return <div>No service selected</div>;
|
|
196
201
|
|
|
197
|
-
let controller =
|
|
202
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
198
203
|
let originalConfig = controller.getServiceConfig(selectedServiceId);
|
|
199
204
|
let serviceConfigType = controller.getServiceConfigType();
|
|
200
205
|
|
|
@@ -333,8 +338,10 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
333
338
|
<div
|
|
334
339
|
className={css.hbox(12)}
|
|
335
340
|
>
|
|
336
|
-
<div className={css.
|
|
337
|
-
|
|
341
|
+
<div className={css.vbox(5)}>
|
|
342
|
+
<div>
|
|
343
|
+
{machineId} ({machineInfo.info["getExternalIP"]})
|
|
344
|
+
</div>
|
|
338
345
|
</div>
|
|
339
346
|
{isMachineDead && (
|
|
340
347
|
<div className={css.colorhsl(0, 80, 50)}>
|
|
@@ -365,15 +372,12 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
365
372
|
</div>
|
|
366
373
|
)}
|
|
367
374
|
|
|
368
|
-
<
|
|
375
|
+
<Anchor noStyles
|
|
376
|
+
values={[currentViewParam.getOverride("machine-detail"), selectedMachineIdParam.getOverride(machineId)]}
|
|
369
377
|
className={css.button.pad2(16, 8).bord2(0, 0, 10) + css.hsl(200, 70, 90)}
|
|
370
|
-
onClick={() => {
|
|
371
|
-
currentViewParam.value = "machine-detail";
|
|
372
|
-
selectedMachineIdParam.value = machineId;
|
|
373
|
-
}}
|
|
374
378
|
>
|
|
375
379
|
View Machine
|
|
376
|
-
</
|
|
380
|
+
</Anchor>
|
|
377
381
|
|
|
378
382
|
|
|
379
383
|
<div
|
|
@@ -513,6 +517,33 @@ type ServiceConfig = ${serviceConfigType}
|
|
|
513
517
|
|
|
514
518
|
<div className={css.marginAuto}></div>
|
|
515
519
|
|
|
520
|
+
<button className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(110, 70, 90)}
|
|
521
|
+
onClick={() => {
|
|
522
|
+
if (!selectedServiceId) return;
|
|
523
|
+
|
|
524
|
+
const newServiceId = `service-${Math.round(Date.now())}`;
|
|
525
|
+
const clonedConfig = deepCloneJSON(config);
|
|
526
|
+
clonedConfig.serviceId = newServiceId;
|
|
527
|
+
clonedConfig.parameters.deploy = false;
|
|
528
|
+
clonedConfig.info.title += " (Copy)";
|
|
529
|
+
|
|
530
|
+
Querysub.onCommitFinished(async () => {
|
|
531
|
+
try {
|
|
532
|
+
await controller.addServiceConfig.promise(newServiceId, clonedConfig);
|
|
533
|
+
Querysub.commit(() => {
|
|
534
|
+
selectedServiceIdParam.value = newServiceId;
|
|
535
|
+
this.state.unsavedChanges = undefined;
|
|
536
|
+
});
|
|
537
|
+
} catch (error) {
|
|
538
|
+
Querysub.localCommit(() => {
|
|
539
|
+
this.state.saveError = error instanceof Error ? error.message : String(error);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}}>
|
|
544
|
+
✚ Duplicate Service
|
|
545
|
+
</button>
|
|
546
|
+
|
|
516
547
|
<button className={css.pad2(12, 8).button.bord2(0, 80, 50).hsl(0, 80, 90)}
|
|
517
548
|
onClick={() => {
|
|
518
549
|
if (!selectedServiceId) return;
|