querysub 0.259.0 → 0.261.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 +2 -0
- package/package.json +1 -1
- package/src/-e-certs/EdgeCertController.ts +1 -1
- package/src/-f-node-discovery/NodeDiscovery.ts +3 -3
- package/src/3-path-functions/PathFunctionHelpers.ts +5 -6
- package/src/3-path-functions/PathFunctionRunner.ts +10 -3
- package/src/3-path-functions/PathFunctionRunnerMain.ts +2 -4
- package/src/4-deploy/deployFunctions.ts +82 -0
- package/src/4-deploy/deployGetFunctionsInner.ts +94 -0
- package/src/4-deploy/deployMain.ts +9 -109
- package/src/4-deploy/edgeBootstrap.ts +3 -0
- package/src/4-deploy/edgeClientWatcher.tsx +4 -0
- package/src/4-deploy/edgeNodes.ts +7 -1
- package/src/4-querysub/Querysub.ts +45 -6
- package/src/deployManager/MachinesPage.tsx +3 -0
- package/src/deployManager/components/DeployPage.tsx +385 -0
- package/src/deployManager/components/DeployProgressView.tsx +135 -0
- package/src/deployManager/components/MachinesListPage.tsx +10 -9
- package/src/deployManager/components/ServiceDetailPage.tsx +2 -1
- package/src/deployManager/components/ServicesListPage.tsx +2 -2
- package/src/deployManager/components/deployButtons.tsx +3 -3
- package/src/deployManager/machineApplyMainCode.ts +1 -0
- package/src/deployManager/machineController.ts +1 -0
- package/src/deployManager/machineSchema.ts +77 -3
- package/src/deployManager/spec.txt +22 -17
- package/src/deployManager/urlParams.ts +1 -1
- package/src/diagnostics/NodeViewer.tsx +17 -2
- package/src/library-components/ATag.tsx +14 -9
- package/src/misc/formatJSX.tsx +1 -1
- package/src/misc.ts +8 -0
- package/src/server.ts +10 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isNodeTrue, timeInMinute } from "socket-function/src/misc";
|
|
1
|
+
import { isNodeTrue, list, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
2
2
|
import { nestArchives } from "../-a-archives/archives";
|
|
3
3
|
import { getArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
|
|
4
4
|
import { getDomain } from "../config";
|
|
@@ -10,7 +10,7 @@ import { getSyncedController } from "../library-components/SyncedController";
|
|
|
10
10
|
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
11
11
|
import { extractType } from "../misc/extractType";
|
|
12
12
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
13
|
-
import { runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
13
|
+
import { delay, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
14
14
|
import { lazy } from "socket-function/src/caching";
|
|
15
15
|
import { errorToUndefinedSilent } from "../errors";
|
|
16
16
|
import { commitAndPush, getGitRefInfo, getGitRefLive, getGitURLLive, getGitUncommitted, getLatestRefOnUpstreamBranch, setGitRef } from "../4-deploy/git";
|
|
@@ -19,6 +19,9 @@ import path from "path";
|
|
|
19
19
|
import fs from "fs";
|
|
20
20
|
import { runPromise } from "../functional/runCommand";
|
|
21
21
|
import { isNode } from "typesafecss";
|
|
22
|
+
import { DeployProgress, deployFunctions, deployGetFunctions } from "../4-deploy/deployFunctions";
|
|
23
|
+
import { FunctionSpec, functionSchema } from "../3-path-functions/PathFunctionRunner";
|
|
24
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
22
25
|
|
|
23
26
|
const SERVICE_FOLDER_NAME = "machine-services";
|
|
24
27
|
export const SERVICE_FOLDER = `${SERVICE_FOLDER_NAME}/`;
|
|
@@ -288,8 +291,73 @@ export class MachineServiceControllerBase {
|
|
|
288
291
|
message: commitMessage,
|
|
289
292
|
});
|
|
290
293
|
}
|
|
291
|
-
}
|
|
292
294
|
|
|
295
|
+
public async getPendingFunctions() {
|
|
296
|
+
return await deployGetFunctions();
|
|
297
|
+
}
|
|
298
|
+
public async getLiveFunctions() {
|
|
299
|
+
return await Querysub.commitAsync(() => {
|
|
300
|
+
let base = functionSchema()[getDomain()].PathFunctionRunner;
|
|
301
|
+
let functions: FunctionSpec[] = [];
|
|
302
|
+
for (let moduleObj of Object.values(base)) {
|
|
303
|
+
for (let functionObj of Object.values(moduleObj.Sources)) {
|
|
304
|
+
if (!functionObj) continue;
|
|
305
|
+
functions.push(functionObj);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return functions;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public async deployFunctions(config: {
|
|
313
|
+
functionSpecs: FunctionSpec[];
|
|
314
|
+
notifyRefreshDelay: number;
|
|
315
|
+
deployOnlyCode?: boolean;
|
|
316
|
+
deployOnlyUI?: boolean;
|
|
317
|
+
}) {
|
|
318
|
+
let caller = SocketFunction.getCaller();
|
|
319
|
+
const onProgress: DeployProgress = (config) => {
|
|
320
|
+
void DeployProgressController.nodes[caller.nodeId].onDeployProgress(config);
|
|
321
|
+
};
|
|
322
|
+
await deployFunctions({
|
|
323
|
+
functionSpecs: config.functionSpecs,
|
|
324
|
+
notifyRefreshDelay: config.notifyRefreshDelay,
|
|
325
|
+
deployOnlyCode: config.deployOnlyCode,
|
|
326
|
+
deployOnlyUI: config.deployOnlyUI,
|
|
327
|
+
progress: onProgress,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
let deployWatchers = new Set<DeployProgress>();
|
|
332
|
+
class DeployProgressControllerBase {
|
|
333
|
+
async onDeployProgress(config: { section: string; progress: number; }) {
|
|
334
|
+
for (let deployWatcher of deployWatchers) {
|
|
335
|
+
deployWatcher(config);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const DeployProgressController = SocketFunction.register(
|
|
340
|
+
" DeployProgressController-9827a7e2-6ace-4156-b623-b4d1a7bab066",
|
|
341
|
+
() => new DeployProgressControllerBase(),
|
|
342
|
+
() => ({
|
|
343
|
+
onDeployProgress: {},
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
export async function deployFunctionsWithProgress(config: {
|
|
347
|
+
functionSpecs: FunctionSpec[];
|
|
348
|
+
notifyRefreshDelay: number;
|
|
349
|
+
deployOnlyCode?: boolean;
|
|
350
|
+
deployOnlyUI?: boolean;
|
|
351
|
+
onProgress: DeployProgress;
|
|
352
|
+
}) {
|
|
353
|
+
const { onProgress, ...rest } = config;
|
|
354
|
+
deployWatchers.add(onProgress);
|
|
355
|
+
try {
|
|
356
|
+
await MachineServiceController(SocketFunction.browserNodeId()).deployFunctions.promise(rest);
|
|
357
|
+
} finally {
|
|
358
|
+
deployWatchers.delete(onProgress);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
293
361
|
|
|
294
362
|
export const MachineServiceController = getSyncedController(
|
|
295
363
|
SocketFunction.register(
|
|
@@ -311,6 +379,9 @@ export const MachineServiceController = getSyncedController(
|
|
|
311
379
|
getGitRefInfo: {},
|
|
312
380
|
commitPushService: {},
|
|
313
381
|
commitPushAndPublishQuerysub: {},
|
|
382
|
+
getPendingFunctions: {},
|
|
383
|
+
deployFunctions: {},
|
|
384
|
+
getLiveFunctions: {},
|
|
314
385
|
}),
|
|
315
386
|
() => ({
|
|
316
387
|
hooks: [assertIsManagementUser],
|
|
@@ -326,6 +397,7 @@ export const MachineServiceController = getSyncedController(
|
|
|
326
397
|
deleteServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
327
398
|
commitPushService: ["gitInfo", "ServiceConfig"],
|
|
328
399
|
commitPushAndPublishQuerysub: ["gitInfo", "ServiceConfig"],
|
|
400
|
+
deployFunctions: ["gitInfo"],
|
|
329
401
|
},
|
|
330
402
|
reads: {
|
|
331
403
|
getMachineList: ["MachineInfoList"],
|
|
@@ -333,6 +405,8 @@ export const MachineServiceController = getSyncedController(
|
|
|
333
405
|
getServiceList: ["ServiceConfigList"],
|
|
334
406
|
getServiceConfig: ["ServiceConfig"],
|
|
335
407
|
getGitInfo: ["gitInfo"],
|
|
408
|
+
getPendingFunctions: ["gitInfo"],
|
|
409
|
+
getLiveFunctions: ["gitInfo"],
|
|
336
410
|
}
|
|
337
411
|
}
|
|
338
412
|
);
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
2
|
+
8) Fix deploy user notification issue, where the refresh button doesn't work?
|
|
3
|
+
- For application level updates
|
|
4
|
+
- Just an application commit, then deploy the service, then the refresh button should show up, and work
|
|
3
5
|
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
What about cloudflare registration?
|
|
8
|
+
8) Use a special service for the HTTP bootstrapper, and then have 2 others that are on other ports
|
|
9
|
+
- Find a way to prevent the bootstrapper from being picked as the backend as well, so we don't need to update it!
|
|
10
|
+
|
|
11
|
+
--bootstraponly is added, and should work?
|
|
6
12
|
|
|
7
13
|
9) Rolling service updates
|
|
8
14
|
- Add rollingWindow to the definition
|
|
9
|
-
-
|
|
10
|
-
|
|
15
|
+
- Set for public facing services (but not for scripts)
|
|
16
|
+
- PathValueServer - 10 minutes
|
|
17
|
+
- FunctionRunner - 10 minutes
|
|
18
|
+
- HTTP - 4 hours
|
|
19
|
+
- Show the rolling time in the update buttons (both list and the save button on the details page), so we know it will be a rolling update
|
|
20
|
+
- Keep the oldest service alive when we update
|
|
11
21
|
- Tracked per serviceId
|
|
22
|
+
- Update the rolling time, so if we remove the rolling window we kill the old service immediately
|
|
12
23
|
- Notify when servers are outdated
|
|
13
24
|
- In HTTP server, notify users, in the same way we notify for hash updates, that they will need to switch servers
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
10) Add RAM and cpu count to the machine info, as well as to the machine list page
|
|
17
|
-
- ALSO, disk size, and disk utilization?
|
|
18
|
-
- Yeah... I mean, we have similar metrics for nodes, but... I think nodes and machines will always have some overlap, and both are useful, and have their own distinct uses (top down vs bottom up, where top down is easy to identify that a problem exists, but not where it is, where as bottom up has too much detail and the problem might only exist due to many nodes, but if you see a problem it's easier to find where it is... and some problems might not exist at a machine level too, such as a single core being maxed out, or high latency of requests, or a specific resource being slow, etc, etc)
|
|
25
|
+
- Use the node registration to know the nodeId to talk to, and allow servers to register to get told when they are outdated (and how long before they are shutdown).
|
|
26
|
+
- Verify this update works with a relatively low rolling update window, ensuring it force refreshes before the server actually restarts.
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
- Show a nice list of all the changed files and functions
|
|
26
|
-
- Give better progress, so we can see what we're waiting on, and how long we've been waiting
|
|
27
|
-
- ALSO, add a button which we can add to our main header, which will show if we have unpushed changes, and which links to the deploy page
|
|
28
|
+
10) Add RAM total, ram % used, cpu count, CPU %, disk size, disk % used to machine info
|
|
29
|
+
- Show this all in the list page, with nice bars?
|
|
30
|
+
- Filled, but the total size will also depend on the maximum (to a degree), so it's very nice.
|
|
31
|
+
- A bar, with a label, or something. The AI can give us a few options
|
|
32
|
+
(Yes, it overlaps with node metrics, but that's okay, sometimes we want to look at machines, other times services, and the types of errors that show up in either changes).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { URLParam } from "../library-components/URLParam";
|
|
2
2
|
|
|
3
|
-
export type ViewType = "services" | "machines" | "service-detail" | "machine-detail";
|
|
3
|
+
export type ViewType = "services" | "machines" | "service-detail" | "machine-detail" | "deploy";
|
|
4
4
|
|
|
5
5
|
export const currentViewParam = new URLParam<ViewType>("machineview", "machines");
|
|
6
6
|
export const selectedServiceIdParam = new URLParam("serviceId", "");
|
|
@@ -85,7 +85,13 @@ export class NodeViewer extends qreact.Component {
|
|
|
85
85
|
let time = Date.now();
|
|
86
86
|
let data: NodeData = { nodeId };
|
|
87
87
|
try {
|
|
88
|
+
if (nodeId.includes("b3a757")) {
|
|
89
|
+
debugger;
|
|
90
|
+
}
|
|
88
91
|
data.table = await controller.getMiscInfo(nodeId);
|
|
92
|
+
if (nodeId.includes("b3a757")) {
|
|
93
|
+
debugger;
|
|
94
|
+
}
|
|
89
95
|
data.live_isReadReady = await controller.live_isReadReady(nodeId);
|
|
90
96
|
data.live_exposedControllers = await controller.getExposedControllers(nodeId);
|
|
91
97
|
data.live_entryPoint = await controller.live_getEntryPoint(nodeId);
|
|
@@ -99,8 +105,14 @@ export class NodeViewer extends qreact.Component {
|
|
|
99
105
|
} else {
|
|
100
106
|
data.devToolsURL = nodeId;
|
|
101
107
|
}
|
|
108
|
+
if (nodeId.includes("b3a757")) {
|
|
109
|
+
debugger;
|
|
110
|
+
}
|
|
102
111
|
} catch (e: any) {
|
|
103
112
|
data.apiError = "Error: " + e.stack;
|
|
113
|
+
if (nodeId.includes("b3a757")) {
|
|
114
|
+
debugger;
|
|
115
|
+
}
|
|
104
116
|
}
|
|
105
117
|
time = Date.now() - time;
|
|
106
118
|
data.loadTime = time;
|
|
@@ -144,8 +156,11 @@ export class NodeViewer extends qreact.Component {
|
|
|
144
156
|
return <div>Verifying access...</div>;
|
|
145
157
|
}
|
|
146
158
|
let nodeDatas = Object.values(this.state.nodes);
|
|
159
|
+
let deadCount = 0;
|
|
147
160
|
if (!showNotReadyNodes.value) {
|
|
148
|
-
|
|
161
|
+
let ready = nodeDatas.filter(x => x.live_isReadReady);
|
|
162
|
+
deadCount = nodeDatas.length - ready.length;
|
|
163
|
+
nodeDatas = ready;
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
function mergeTables(tables: TableType[]): TableType {
|
|
@@ -240,7 +255,7 @@ export class NodeViewer extends qreact.Component {
|
|
|
240
255
|
this.populateNodeInfos.reset();
|
|
241
256
|
this.populateNodeInfos();
|
|
242
257
|
}}>Refresh</Button>
|
|
243
|
-
<InputLabelURL label=
|
|
258
|
+
<InputLabelURL label={`Show Not Ready (${deadCount})`} checkbox url={showNotReadyNodes} />
|
|
244
259
|
{this.state.error && <div class={errorMessage}>{this.state.error}</div>}
|
|
245
260
|
</div>
|
|
246
261
|
<div class={css.vbox(10)}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import preact from "preact";
|
|
2
|
-
import { css } from "typesafecss";
|
|
2
|
+
import { css, isNode } from "typesafecss";
|
|
3
3
|
import { URLParam, parseSearchString, encodeSearchString } from "./URLParam";
|
|
4
4
|
import { qreact } from "../4-dom/qreact";
|
|
5
5
|
|
|
@@ -97,14 +97,8 @@ export class ATag extends qreact.Component<ATagProps> {
|
|
|
97
97
|
// NOTE: Calculate the href dynamically, to prevent pages with a lot of links that
|
|
98
98
|
// reference frequently changing values from resulting in a huge number of DOM changes
|
|
99
99
|
// every time a value changes.
|
|
100
|
-
let urlObj = new URL(document.location.href);
|
|
101
|
-
let params = parseSearchString(urlObj.search);
|
|
102
100
|
let resolvedValues = typeof values === "function" ? values() : values;
|
|
103
|
-
|
|
104
|
-
params[value.param.urlKey] = value.value;
|
|
105
|
-
}
|
|
106
|
-
urlObj.search = encodeSearchString(params);
|
|
107
|
-
e.currentTarget.href = urlObj.toString();
|
|
101
|
+
e.currentTarget.href = createLink(resolvedValues || []);
|
|
108
102
|
props.onMouseOver?.(e);
|
|
109
103
|
}}
|
|
110
104
|
onKeyDown={e => {
|
|
@@ -120,4 +114,15 @@ export class ATag extends qreact.Component<ATagProps> {
|
|
|
120
114
|
}
|
|
121
115
|
}
|
|
122
116
|
export const Anchor = ATag;
|
|
123
|
-
export const Link = ATag;
|
|
117
|
+
export const Link = ATag;
|
|
118
|
+
|
|
119
|
+
export function createLink(values: URLOverride[]) {
|
|
120
|
+
if (isNode()) throw new Error(`Create link is not implemented yet on node.`);
|
|
121
|
+
let urlObj = new URL(document.location.href);
|
|
122
|
+
let params = parseSearchString(urlObj.search);
|
|
123
|
+
for (let value of values) {
|
|
124
|
+
params[value.param.urlKey] = value.value;
|
|
125
|
+
}
|
|
126
|
+
urlObj.search = encodeSearchString(params);
|
|
127
|
+
return urlObj.toString();
|
|
128
|
+
}
|
package/src/misc/formatJSX.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { qreact } from "../4-dom/qreact";
|
|
2
2
|
import { formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function formatDateJSX(time: number) {
|
|
5
5
|
return <span title={formatVeryNiceDateTime(time)}>{formatTime(Date.now() - time)}</span>;
|
|
6
6
|
}
|
package/src/misc.ts
CHANGED
|
@@ -53,4 +53,12 @@ export function clamp(value: number, min: number, max: number) {
|
|
|
53
53
|
|
|
54
54
|
export function unique<T>(array: T[]): T[] {
|
|
55
55
|
return Array.from(new Set(array));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function mostCommon<T>(array: T[]): T | undefined {
|
|
59
|
+
let counts = new Map<T, number>();
|
|
60
|
+
for (let item of array) {
|
|
61
|
+
counts.set(item, (counts.get(item) || 0) + 1);
|
|
62
|
+
}
|
|
63
|
+
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
56
64
|
}
|
package/src/server.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { isNode, isNodeTrue } from "socket-function/src/misc";
|
|
|
5
5
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
6
6
|
import { getThreadKeyCert } from "./-a-auth/certs";
|
|
7
7
|
import { logErrors } from "./errors";
|
|
8
|
-
import { publishMachineARecords } from "./-e-certs/EdgeCertController";
|
|
9
8
|
import { pathValueAuthority2 } from "./0-path-value-core/NodePathAuthorities";
|
|
10
9
|
import { green } from "socket-function/src/formatting/logColors";
|
|
11
10
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
@@ -14,9 +13,11 @@ import { formatTime } from "socket-function/src/formatting/format";
|
|
|
14
13
|
// we DO want to register some static diagnostics from Querysub, so... import it explicitly.
|
|
15
14
|
import "./4-querysub/Querysub";
|
|
16
15
|
import { getOurAuthorities } from "./config2";
|
|
17
|
-
import { isPublic } from "./config";
|
|
16
|
+
import { getDomain, isPublic } from "./config";
|
|
18
17
|
|
|
19
18
|
import yargs from "yargs";
|
|
19
|
+
import { Querysub } from "./4-querysub/Querysub";
|
|
20
|
+
import { createSourceCheck } from "./user-implementation/canSeeSource";
|
|
20
21
|
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
21
22
|
.option("authority", { type: "string", desc: `Defines the base paths we are an authority on (the domain is prepended to them). Either a file path to a JSON(AuthorityPath[]), or a base64 representation of the JSON(AuthorityPath[]).` })
|
|
22
23
|
.option("verbose", { type: "boolean", desc: "Log all writes and reads" })
|
|
@@ -50,13 +51,12 @@ async function main() {
|
|
|
50
51
|
pathValueAuthority2.becomePathAuthority(authority);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
port:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
await Querysub.hostServer({
|
|
55
|
+
trustedDomains: [getDomain(), "noproxy." + getDomain()],
|
|
56
|
+
port: 443,
|
|
57
|
+
rootPath: __dirname + "/../",
|
|
58
|
+
clientsideEntryPoint: "./src/browser.ts",
|
|
59
|
+
hotReload: true,
|
|
60
|
+
sourceCheck: createSourceCheck(),
|
|
59
61
|
});
|
|
60
|
-
|
|
61
|
-
await publishMachineARecords();
|
|
62
62
|
}
|