querysub 0.459.0 → 0.461.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/.claude/settings.local.json +2 -1
- package/package.json +2 -2
- package/src/-b-authorities/dnsAuthority.ts +23 -15
- package/src/-g-core-values/NodeCapabilities.ts +3 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +11 -3
- package/src/0-path-value-core/PathRouter.ts +6 -0
- package/src/0-path-value-core/PathWatcher.ts +1 -1
- package/src/0-path-value-core/pathValueCore.ts +4 -7
- package/src/1-path-client/RemoteWatcher.ts +8 -3
- package/src/1-path-client/pathValueClientWatcher.ts +3 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1 -1
- package/src/2-proxy/TransactionDelayer.ts +1 -1
- package/src/3-path-functions/PathFunctionHelpers.ts +13 -8
- package/src/3-path-functions/PathFunctionRunner.ts +2 -0
- package/src/4-querysub/Querysub.ts +0 -1
- package/src/4-querysub/QuerysubController.ts +1 -7
- package/src/config.ts +9 -0
- package/src/config2.ts +7 -1
- package/src/deployManager/components/MachinePicker.tsx +40 -0
- package/src/deployManager/components/ServiceDetailPage.tsx +2 -5
- package/src/deployManager/components/ServicesListPage.tsx +2 -0
- package/src/deployManager/components/Tools.tsx +165 -0
- package/src/deployManager/setupMachineMain.ts +74 -23
- package/src/diagnostics/charts/Chart.tsx +240 -0
- package/src/diagnostics/grossStats/GrossStatsPage.tsx +48 -83
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +22 -35
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +39 -47
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +18 -3
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +1 -0
- package/src/diagnostics/managementPages.tsx +58 -58
- package/src/diagnostics/misc-pages/DNSPage.tsx +344 -0
- package/test.ts +46 -70
- package/src/diagnostics/AuditLogPage.tsx +0 -147
- package/src/diagnostics/NodeConnectionsPage.tsx +0 -167
|
@@ -93,51 +93,12 @@ export async function registerManagementPages2(config: {
|
|
|
93
93
|
controllerName: "NodeViewerController",
|
|
94
94
|
getModule: () => import("./NodeViewer"),
|
|
95
95
|
});
|
|
96
|
+
|
|
96
97
|
inputPages.push({
|
|
97
|
-
title: "
|
|
98
|
-
componentName: "
|
|
99
|
-
controllerName: "
|
|
100
|
-
getModule: () => import("./
|
|
101
|
-
});
|
|
102
|
-
inputPages.push({
|
|
103
|
-
title: "LOG VIEWER",
|
|
104
|
-
componentName: "LogViewer3",
|
|
105
|
-
getModule: () => import("./logs/IndexedLogs/LogViewer3"),
|
|
106
|
-
});
|
|
107
|
-
inputPages.push({
|
|
108
|
-
title: "Life Cycles",
|
|
109
|
-
componentName: "LifeCyclePage",
|
|
110
|
-
controllerName: "",
|
|
111
|
-
getModule: () => import("./logs/lifeCycleAnalysis/LifeCyclePage"),
|
|
112
|
-
});
|
|
113
|
-
inputPages.push({
|
|
114
|
-
title: "Error Notifications",
|
|
115
|
-
componentName: "ErrorNotificationPage",
|
|
116
|
-
controllerName: "",
|
|
117
|
-
getModule: () => import("./logs/errorNotifications2/ErrorNotificationPage"),
|
|
118
|
-
});
|
|
119
|
-
inputPages.push({
|
|
120
|
-
title: "Security",
|
|
121
|
-
componentName: "SecurityPage",
|
|
122
|
-
getModule: () => import("../user-implementation/SecurityPage"),
|
|
123
|
-
});
|
|
124
|
-
inputPages.push({
|
|
125
|
-
title: "Audit Paths",
|
|
126
|
-
componentName: "AuditLogPage",
|
|
127
|
-
controllerName: "AuditLogController",
|
|
128
|
-
getModule: () => import("./AuditLogPage"),
|
|
129
|
-
});
|
|
130
|
-
inputPages.push({
|
|
131
|
-
title: "Node Connections",
|
|
132
|
-
componentName: "NodeConnectionsPage",
|
|
133
|
-
controllerName: "NodeConnectionsController",
|
|
134
|
-
getModule: () => import("./NodeConnectionsPage"),
|
|
135
|
-
});
|
|
136
|
-
inputPages.push({
|
|
137
|
-
title: "Time",
|
|
138
|
-
componentName: "TimeDebug",
|
|
139
|
-
controllerName: "",
|
|
140
|
-
getModule: () => import("./TimeDebug"),
|
|
98
|
+
title: "Gross Stats",
|
|
99
|
+
componentName: "GrossStatsPage",
|
|
100
|
+
controllerName: "GrossStatsController",
|
|
101
|
+
getModule: () => import("./grossStats/GrossStatsPage"),
|
|
141
102
|
});
|
|
142
103
|
inputPages.push({
|
|
143
104
|
title: "Archives",
|
|
@@ -152,27 +113,66 @@ export async function registerManagementPages2(config: {
|
|
|
152
113
|
getModule: () => import("./misc-pages/SnapshotViewer"),
|
|
153
114
|
});
|
|
154
115
|
inputPages.push({
|
|
155
|
-
title: "
|
|
156
|
-
componentName: "
|
|
157
|
-
|
|
116
|
+
title: "Authority Specs",
|
|
117
|
+
componentName: "AuthoritySpecPage",
|
|
118
|
+
controllerName: "AuthoritySpecPageController",
|
|
119
|
+
getModule: () => import("./misc-pages/AuthoritySpecPage"),
|
|
158
120
|
});
|
|
159
121
|
inputPages.push({
|
|
160
|
-
title: "
|
|
161
|
-
componentName: "
|
|
162
|
-
|
|
122
|
+
title: "DNS",
|
|
123
|
+
componentName: "DNSPage",
|
|
124
|
+
controllerName: "DNSPageController",
|
|
125
|
+
getModule: () => import("./misc-pages/DNSPage"),
|
|
163
126
|
});
|
|
164
127
|
inputPages.push({
|
|
165
|
-
title: "
|
|
166
|
-
componentName: "
|
|
167
|
-
|
|
168
|
-
getModule: () => import("./SyncTestPage"),
|
|
128
|
+
title: "LOG VIEWER",
|
|
129
|
+
componentName: "LogViewer3",
|
|
130
|
+
getModule: () => import("./logs/IndexedLogs/LogViewer3"),
|
|
169
131
|
});
|
|
132
|
+
// It's really sad to comment out the life cycles page, but I'm just never going to use it, it's just too slow and too annoying. It's easier to get the AI to search stuff.
|
|
133
|
+
// inputPages.push({
|
|
134
|
+
// title: "Life Cycles",
|
|
135
|
+
// componentName: "LifeCyclePage",
|
|
136
|
+
// controllerName: "",
|
|
137
|
+
// getModule: () => import("./logs/lifeCycleAnalysis/LifeCyclePage"),
|
|
138
|
+
// });
|
|
170
139
|
inputPages.push({
|
|
171
|
-
title: "
|
|
172
|
-
componentName: "
|
|
173
|
-
controllerName: "
|
|
174
|
-
getModule: () => import("./
|
|
140
|
+
title: "Error Notifications",
|
|
141
|
+
componentName: "ErrorNotificationPage",
|
|
142
|
+
controllerName: "",
|
|
143
|
+
getModule: () => import("./logs/errorNotifications2/ErrorNotificationPage"),
|
|
144
|
+
});
|
|
145
|
+
// NOTE: Even if we never use this, it is needed if a server or site is bootstrapping. However, they should probably include this in their own header dynamically as well.
|
|
146
|
+
inputPages.push({
|
|
147
|
+
title: "Security",
|
|
148
|
+
componentName: "SecurityPage",
|
|
149
|
+
getModule: () => import("../user-implementation/SecurityPage"),
|
|
175
150
|
});
|
|
151
|
+
// inputPages.push({
|
|
152
|
+
// title: "Time",
|
|
153
|
+
// componentName: "TimeDebug",
|
|
154
|
+
// controllerName: "",
|
|
155
|
+
// getModule: () => import("./TimeDebug"),
|
|
156
|
+
// });
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
// NOTE: Leave all the commented out pages. They're just pages I haven't touched for a while and they're clogging up the header, but they are useful.
|
|
160
|
+
// inputPages.push({
|
|
161
|
+
// title: "View Synced Paths",
|
|
162
|
+
// componentName: "LocalWatchViewer",
|
|
163
|
+
// getModule: () => import("./misc-pages/LocalWatchViewer"),
|
|
164
|
+
// });
|
|
165
|
+
// inputPages.push({
|
|
166
|
+
// title: "Require Audit",
|
|
167
|
+
// componentName: "RequireAuditPage",
|
|
168
|
+
// getModule: () => import("./misc-pages/RequireAuditPage"),
|
|
169
|
+
// });
|
|
170
|
+
// inputPages.push({
|
|
171
|
+
// title: "Sync Test",
|
|
172
|
+
// componentName: "SyncTestPage",
|
|
173
|
+
// controllerName: "SyncTestController",
|
|
174
|
+
// getModule: () => import("./SyncTestPage"),
|
|
175
|
+
// });
|
|
176
176
|
inputPages.push(...config.pages);
|
|
177
177
|
|
|
178
178
|
// NOTE: We don't store the UI in the database (here, or anywhere else, at least
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
module.allowclient = true;
|
|
2
|
+
|
|
3
|
+
import { qreact } from "../../4-dom/qreact";
|
|
4
|
+
import { css } from "typesafecss";
|
|
5
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
6
|
+
import { getBrowserUrlNode } from "../../-f-node-discovery/NodeDiscovery";
|
|
7
|
+
import { getSyncedController } from "../../library-components/SyncedController";
|
|
8
|
+
import { assertIsManagementUser } from "../managementPages";
|
|
9
|
+
import { t } from "../../2-proxy/schema2";
|
|
10
|
+
import { Querysub } from "../../4-querysub/QuerysubController";
|
|
11
|
+
import { sort, timeInDay } from "socket-function/src/misc";
|
|
12
|
+
import { isNode } from "typesafecss";
|
|
13
|
+
import { formatDateJSX } from "../../misc/formatJSX";
|
|
14
|
+
|
|
15
|
+
const RECORDS_PER_PAGE = 1000;
|
|
16
|
+
const HEX_SUBDOMAIN_LENGTH = 17;
|
|
17
|
+
const STALE_HEX_SUBDOMAIN_AGE = timeInDay * 7;
|
|
18
|
+
const HEX_SUBDOMAIN_REGEX = new RegExp(`^(\\*\\.)?[0-9a-f]{${HEX_SUBDOMAIN_LENGTH}}\\..+$`);
|
|
19
|
+
|
|
20
|
+
export type DNSRecord = {
|
|
21
|
+
id: string;
|
|
22
|
+
zoneId: string;
|
|
23
|
+
zoneName: string;
|
|
24
|
+
type: string;
|
|
25
|
+
name: string;
|
|
26
|
+
content: string;
|
|
27
|
+
proxied: boolean;
|
|
28
|
+
ttl: number;
|
|
29
|
+
createdOn: number;
|
|
30
|
+
modifiedOn: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
class DNSPageControllerBase {
|
|
34
|
+
public async getAllRecords(): Promise<DNSRecord[]> {
|
|
35
|
+
if (!isNode()) throw new Error(`getAllRecords must be called serverside`);
|
|
36
|
+
const { cloudflareGETCall } = await import("../../-b-authorities/cloudflareHelpers");
|
|
37
|
+
const { getDomain } = await import("../../config");
|
|
38
|
+
|
|
39
|
+
let zones = await cloudflareGETCall<{ id: string; name: string }[]>("/zones", {
|
|
40
|
+
per_page: String(RECORDS_PER_PAGE),
|
|
41
|
+
});
|
|
42
|
+
let domain = getDomain();
|
|
43
|
+
zones = zones.filter(z => z.name === domain);
|
|
44
|
+
|
|
45
|
+
let allRecords: DNSRecord[] = [];
|
|
46
|
+
for (let zone of zones) {
|
|
47
|
+
let page = 1;
|
|
48
|
+
while (true) {
|
|
49
|
+
let records = await cloudflareGETCall<{
|
|
50
|
+
id: string;
|
|
51
|
+
type: string;
|
|
52
|
+
name: string;
|
|
53
|
+
content: string;
|
|
54
|
+
proxied: boolean;
|
|
55
|
+
ttl: number;
|
|
56
|
+
created_on: string;
|
|
57
|
+
modified_on: string;
|
|
58
|
+
}[]>(`/zones/${zone.id}/dns_records`, {
|
|
59
|
+
per_page: String(RECORDS_PER_PAGE),
|
|
60
|
+
page: String(page),
|
|
61
|
+
});
|
|
62
|
+
for (let record of records) {
|
|
63
|
+
allRecords.push({
|
|
64
|
+
id: record.id,
|
|
65
|
+
zoneId: zone.id,
|
|
66
|
+
zoneName: zone.name,
|
|
67
|
+
type: record.type,
|
|
68
|
+
name: record.name,
|
|
69
|
+
content: record.content,
|
|
70
|
+
proxied: record.proxied,
|
|
71
|
+
ttl: record.ttl,
|
|
72
|
+
createdOn: new Date(record.created_on).getTime(),
|
|
73
|
+
modifiedOn: new Date(record.modified_on).getTime(),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (records.length < RECORDS_PER_PAGE) break;
|
|
77
|
+
page++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return allRecords;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public async deleteRecord(config: { zoneId: string; recordId: string }): Promise<void> {
|
|
84
|
+
if (!isNode()) throw new Error(`deleteRecord must be called serverside`);
|
|
85
|
+
const { cloudflareCall } = await import("../../-b-authorities/cloudflareHelpers");
|
|
86
|
+
await cloudflareCall(`/zones/${config.zoneId}/dns_records/${config.recordId}`, Buffer.from([]), "DELETE");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public async deleteRecordsByContent(config: { content: string }): Promise<number> {
|
|
90
|
+
if (!isNode()) throw new Error(`deleteRecordsByContent must be called serverside`);
|
|
91
|
+
const { cloudflareCall } = await import("../../-b-authorities/cloudflareHelpers");
|
|
92
|
+
let all = await this.getAllRecords();
|
|
93
|
+
let matching = all.filter(x => x.content === config.content);
|
|
94
|
+
for (let record of matching) {
|
|
95
|
+
await cloudflareCall(`/zones/${record.zoneId}/dns_records/${record.id}`, Buffer.from([]), "DELETE");
|
|
96
|
+
}
|
|
97
|
+
return matching.length;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const DNSPageController = SocketFunction.register(
|
|
102
|
+
"DNSPageController-3e7b1d92-44a8-4f7e-9bba-d12c4f87a6e0",
|
|
103
|
+
new DNSPageControllerBase(),
|
|
104
|
+
() => ({
|
|
105
|
+
getAllRecords: {},
|
|
106
|
+
deleteRecord: {},
|
|
107
|
+
deleteRecordsByContent: {},
|
|
108
|
+
}),
|
|
109
|
+
() => ({
|
|
110
|
+
hooks: [assertIsManagementUser],
|
|
111
|
+
}),
|
|
112
|
+
{
|
|
113
|
+
noAutoExpose: true,
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const DNSPageSynced = getSyncedController(DNSPageController, {
|
|
118
|
+
reads: {
|
|
119
|
+
getAllRecords: ["dnsRecords"],
|
|
120
|
+
},
|
|
121
|
+
writes: {
|
|
122
|
+
deleteRecord: ["dnsRecords"],
|
|
123
|
+
deleteRecordsByContent: ["dnsRecords"],
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export class DNSPage extends qreact.Component {
|
|
128
|
+
state = t.state({
|
|
129
|
+
expandedGroups: t.lookup(t.boolean),
|
|
130
|
+
busyKeys: t.lookup(t.boolean),
|
|
131
|
+
errorMessage: t.string,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
private async deleteOne(record: DNSRecord) {
|
|
135
|
+
let typed = prompt(`Type the content "${record.content}" to confirm deleting this record:\n\n${record.type} ${record.name} → ${record.content}`);
|
|
136
|
+
if (typed === null) return;
|
|
137
|
+
if (typed !== record.content) {
|
|
138
|
+
alert(`Confirmation does not match. Aborted.`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let busyKey = `record:${record.id}`;
|
|
142
|
+
Querysub.commit(() => {
|
|
143
|
+
this.state.busyKeys[busyKey] = true;
|
|
144
|
+
this.state.errorMessage = "";
|
|
145
|
+
});
|
|
146
|
+
try {
|
|
147
|
+
await DNSPageSynced(getBrowserUrlNode()).deleteRecord.promise({
|
|
148
|
+
zoneId: record.zoneId,
|
|
149
|
+
recordId: record.id,
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error(`DNS deleteRecord failed:`, (err as Error).stack ?? err);
|
|
153
|
+
Querysub.commit(() => {
|
|
154
|
+
this.state.errorMessage = (err as Error).stack ?? String(err);
|
|
155
|
+
});
|
|
156
|
+
} finally {
|
|
157
|
+
Querysub.commit(() => {
|
|
158
|
+
delete this.state.busyKeys[busyKey];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async deleteStaleHexRecords(content: string, matching: DNSRecord[]) {
|
|
164
|
+
let listText = matching.map(r => ` ${r.type} ${r.name} (modified ${new Date(r.modifiedOn).toISOString()})`).join("\n");
|
|
165
|
+
let typed = prompt(`Type "${content}" to confirm deleting ${matching.length} stale hex-subdomain record(s):\n\n${listText}`);
|
|
166
|
+
if (typed === null) return;
|
|
167
|
+
if (typed !== content) {
|
|
168
|
+
alert(`Confirmation does not match. Aborted.`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let busyKey = `staleHex:${content}`;
|
|
172
|
+
Querysub.commit(() => {
|
|
173
|
+
this.state.busyKeys[busyKey] = true;
|
|
174
|
+
this.state.errorMessage = "";
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
for (let record of matching) {
|
|
178
|
+
await DNSPageSynced(getBrowserUrlNode()).deleteRecord.promise({
|
|
179
|
+
zoneId: record.zoneId,
|
|
180
|
+
recordId: record.id,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error(`DNS deleteStaleHexRecords failed:`, (err as Error).stack ?? err);
|
|
185
|
+
Querysub.commit(() => {
|
|
186
|
+
this.state.errorMessage = (err as Error).stack ?? String(err);
|
|
187
|
+
});
|
|
188
|
+
} finally {
|
|
189
|
+
Querysub.commit(() => {
|
|
190
|
+
delete this.state.busyKeys[busyKey];
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async deleteGroup(content: string, count: number) {
|
|
196
|
+
let typed = prompt(`Type "${content}" to confirm deleting ALL ${count} record(s) pointing to it:`);
|
|
197
|
+
if (typed === null) return;
|
|
198
|
+
if (typed !== content) {
|
|
199
|
+
alert(`Confirmation does not match. Aborted.`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
let busyKey = `group:${content}`;
|
|
203
|
+
Querysub.commit(() => {
|
|
204
|
+
this.state.busyKeys[busyKey] = true;
|
|
205
|
+
this.state.errorMessage = "";
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
await DNSPageSynced(getBrowserUrlNode()).deleteRecordsByContent.promise({ content });
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error(`DNS deleteRecordsByContent failed:`, (err as Error).stack ?? err);
|
|
211
|
+
Querysub.commit(() => {
|
|
212
|
+
this.state.errorMessage = (err as Error).stack ?? String(err);
|
|
213
|
+
});
|
|
214
|
+
} finally {
|
|
215
|
+
Querysub.commit(() => {
|
|
216
|
+
delete this.state.busyKeys[busyKey];
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
render() {
|
|
222
|
+
let records = DNSPageSynced(getBrowserUrlNode()).getAllRecords();
|
|
223
|
+
if (!records) {
|
|
224
|
+
return <div className={css.pad2(16)}>Loading DNS records...</div>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let groups = new Map<string, DNSRecord[]>();
|
|
228
|
+
for (let record of records) {
|
|
229
|
+
let list = groups.get(record.content);
|
|
230
|
+
if (!list) {
|
|
231
|
+
list = [];
|
|
232
|
+
groups.set(record.content, list);
|
|
233
|
+
}
|
|
234
|
+
list.push(record);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let groupEntries = Array.from(groups.entries()).map(([content, list]) => ({ content, records: list }));
|
|
238
|
+
sort(groupEntries, x => -x.records.length);
|
|
239
|
+
for (let group of groupEntries) {
|
|
240
|
+
sort(group.records, r => -r.modifiedOn);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return <div className={css.vbox(12).pad2(16).fillWidth}>
|
|
244
|
+
<div className={css.hbox(12).alignItems("center")}>
|
|
245
|
+
<h2 className={css.flexGrow(1)}>DNS Records ({records.length} total, {groupEntries.length} unique contents)</h2>
|
|
246
|
+
</div>
|
|
247
|
+
{this.state.errorMessage && <pre className={css.colorhsl(0, 60, 40).whiteSpace("pre-wrap").pad2(8).bord2(0, 60, 60).hsl(0, 50, 95)}>{this.state.errorMessage}</pre>}
|
|
248
|
+
<div className={css.vbox(8).fillWidth}>
|
|
249
|
+
{groupEntries.map(({ content, records: list }) => {
|
|
250
|
+
let expanded = !!this.state.expandedGroups[content];
|
|
251
|
+
let groupBusy = !!this.state.busyKeys[`group:${content}`];
|
|
252
|
+
return <div key={content} className={css.vbox(0).fillWidth.bord2(0, 0, 80).hsl(0, 0, 99)}>
|
|
253
|
+
<div className={css.hbox(10).alignItems("center").pad2(10).button}
|
|
254
|
+
onClick={() => {
|
|
255
|
+
if (this.state.expandedGroups[content]) {
|
|
256
|
+
delete this.state.expandedGroups[content];
|
|
257
|
+
} else {
|
|
258
|
+
this.state.expandedGroups[content] = true;
|
|
259
|
+
}
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
<span>{expanded ? "▼" : "▶"}</span>
|
|
263
|
+
<span
|
|
264
|
+
className={css.boldStyle.fontFamily("monospace").button.pad2(4, 2).bord2(0, 0, 80).hsl(0, 0, 100).hbox(6).alignItems("center")}
|
|
265
|
+
title="Click to copy"
|
|
266
|
+
onClick={(e) => {
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
void navigator.clipboard.writeText(content);
|
|
269
|
+
}}
|
|
270
|
+
>
|
|
271
|
+
<span>{content}</span>
|
|
272
|
+
<span className={css.colorhsl(0, 0, 50)}>📋</span>
|
|
273
|
+
</span>
|
|
274
|
+
<span className={css.colorhsl(0, 0, 40)}>{list.length} record(s)</span>
|
|
275
|
+
<div className={css.flexGrow(1)} />
|
|
276
|
+
<button
|
|
277
|
+
className={css.pad2(10, 6).button.bord2(0, 80, 50)
|
|
278
|
+
+ (groupBusy ? css.hsl(0, 0, 90).colorhsl(0, 0, 50) : css.hsl(0, 80, 92).colorhsl(0, 80, 30))}
|
|
279
|
+
disabled={groupBusy}
|
|
280
|
+
onClick={(e) => {
|
|
281
|
+
e.stopPropagation();
|
|
282
|
+
void this.deleteGroup(content, list.length);
|
|
283
|
+
}}
|
|
284
|
+
>
|
|
285
|
+
{groupBusy ? "Deleting..." : `🗑️ Delete all ${list.length}`}
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
{expanded && <div className={css.vbox(4).pad2(10).hsl(0, 0, 100)}>
|
|
289
|
+
{(() => {
|
|
290
|
+
let staleHex = list.filter(r =>
|
|
291
|
+
HEX_SUBDOMAIN_REGEX.test(r.name)
|
|
292
|
+
&& Date.now() - r.modifiedOn > STALE_HEX_SUBDOMAIN_AGE
|
|
293
|
+
);
|
|
294
|
+
if (staleHex.length === 0) return undefined;
|
|
295
|
+
let staleHexBusy = !!this.state.busyKeys[`staleHex:${content}`];
|
|
296
|
+
return <div className={css.vbox(4).pad2(8).bord2(30, 60, 60).hsl(30, 70, 96)}>
|
|
297
|
+
<div className={css.hbox(10).alignItems("center")}>
|
|
298
|
+
<span className={css.boldStyle}>Stale hex-subdomain records (modified >7d ago): {staleHex.length}</span>
|
|
299
|
+
<div className={css.flexGrow(1)} />
|
|
300
|
+
<button
|
|
301
|
+
className={css.pad2(10, 6).button.bord2(0, 80, 50)
|
|
302
|
+
+ (staleHexBusy ? css.hsl(0, 0, 90).colorhsl(0, 0, 50) : css.hsl(0, 80, 92).colorhsl(0, 80, 30))}
|
|
303
|
+
disabled={staleHexBusy}
|
|
304
|
+
onClick={() => void this.deleteStaleHexRecords(content, staleHex)}
|
|
305
|
+
>
|
|
306
|
+
{staleHexBusy ? "Deleting..." : `🗑️ Delete ${staleHex.length} stale hex record(s)`}
|
|
307
|
+
</button>
|
|
308
|
+
</div>
|
|
309
|
+
<div className={css.vbox(2).fontFamily("monospace").colorhsl(0, 0, 30)}>
|
|
310
|
+
{staleHex.map(r => <div key={r.id}>{r.type} {r.name} — modified {formatDateJSX(r.modifiedOn)}</div>)}
|
|
311
|
+
</div>
|
|
312
|
+
</div>;
|
|
313
|
+
})()}
|
|
314
|
+
{list.map(record => {
|
|
315
|
+
let recordBusy = !!this.state.busyKeys[`record:${record.id}`];
|
|
316
|
+
return <div key={record.id} className={css.hbox(10).alignItems("center").pad2(6).bord2(0, 0, 92)}>
|
|
317
|
+
<span className={css.boldStyle.minWidth(50)}>{record.type}</span>
|
|
318
|
+
<span className={css.fontFamily("monospace").flexGrow(1)}>{record.name}</span>
|
|
319
|
+
{record.proxied && <span className={css.colorhsl(30, 80, 40).pad2(4, 2).bord2(30, 80, 70).hsl(30, 80, 95)}>proxied</span>}
|
|
320
|
+
<span className={css.colorhsl(0, 0, 50)}>ttl {record.ttl}</span>
|
|
321
|
+
<span className={css.colorhsl(0, 0, 50)}>
|
|
322
|
+
created {formatDateJSX(record.createdOn)}
|
|
323
|
+
</span>
|
|
324
|
+
<span className={css.colorhsl(0, 0, 50)}>
|
|
325
|
+
modified {formatDateJSX(record.modifiedOn)}
|
|
326
|
+
</span>
|
|
327
|
+
<span className={css.colorhsl(0, 0, 50)}>{record.zoneName}</span>
|
|
328
|
+
<button
|
|
329
|
+
className={css.pad2(8, 4).button.bord2(0, 0, 60)
|
|
330
|
+
+ (recordBusy ? css.hsl(0, 0, 90).colorhsl(0, 0, 50) : css.hsl(0, 0, 100))}
|
|
331
|
+
disabled={recordBusy}
|
|
332
|
+
onClick={() => void this.deleteOne(record)}
|
|
333
|
+
>
|
|
334
|
+
{recordBusy ? "Deleting..." : "🗑️ Delete"}
|
|
335
|
+
</button>
|
|
336
|
+
</div>;
|
|
337
|
+
})}
|
|
338
|
+
</div>}
|
|
339
|
+
</div>;
|
|
340
|
+
})}
|
|
341
|
+
</div>
|
|
342
|
+
</div>;
|
|
343
|
+
}
|
|
344
|
+
}
|
package/test.ts
CHANGED
|
@@ -1,81 +1,57 @@
|
|
|
1
1
|
import { chdir } from "process";
|
|
2
2
|
chdir("D:/repos/qs-cyoa/");
|
|
3
3
|
|
|
4
|
+
import { setIsPublic } from "./src/config";
|
|
5
|
+
setIsPublic(false);
|
|
6
|
+
|
|
4
7
|
import "./inject";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { Querysub, t } from "./src/4-querysub/Querysub";
|
|
10
|
-
import { archives, pathValueArchives } from "./src/0-path-value-core/pathValueArchives";
|
|
11
|
-
import { getAllAuthoritySpec } from "./src/0-path-value-core/PathRouterServerAuthoritySpec";
|
|
12
|
-
import { deploySchema } from "./src/4-deploy/deploySchema";
|
|
13
|
-
import { getDomain } from "./src/config";
|
|
14
|
-
import { getProxyPath } from "./src/2-proxy/pathValueProxy";
|
|
15
|
-
import { ClientWatcher } from "./src/1-path-client/pathValueClientWatcher";
|
|
16
|
-
import { RemoteWatcher } from "./src/1-path-client/RemoteWatcher";
|
|
17
|
-
import { PathRouter } from "./src/0-path-value-core/PathRouter";
|
|
18
|
-
import { shutdown } from "./src/diagnostics/periodic";
|
|
19
|
-
import { getShardPrefixes } from "./src/0-path-value-core/ShardPrefixes";
|
|
20
|
-
import { PathValue, epochTime } from "./src/0-path-value-core/pathValueCore";
|
|
21
|
-
import { pathValueSerializer } from "./src/-h-path-value-serialize/PathValueSerializer";
|
|
22
|
-
import { getAllNodeIds } from "./src/-f-node-discovery/NodeDiscovery";
|
|
23
|
-
import { errorToUndefinedSilent } from "./src/errors";
|
|
24
|
-
import { timeoutToUndefinedSilent } from "socket-function/src/misc";
|
|
8
|
+
import { Querysub } from "./src/4-querysub/Querysub";
|
|
9
|
+
import { getLoggers2Async, LogDatum } from "./src/diagnostics/logs/diskLogger";
|
|
10
|
+
import { SearchParams } from "./src/diagnostics/logs/IndexedLogs/BufferIndexHelpers";
|
|
11
|
+
import { formatDateTimeDetailed, formatTime } from "socket-function/src/formatting/format";
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
module: module,
|
|
32
|
-
functions: {},
|
|
33
|
-
});
|
|
13
|
+
const START_TIME = 1779598800000;
|
|
14
|
+
const END_TIME = 1779604200000;
|
|
15
|
+
const LIMIT = 1600;
|
|
16
|
+
const SEARCH = `wvupofthbgq & "__threadId":"1f72e0ea774fcc81"`;
|
|
17
|
+
const NODE_ID = "46456fa53a7392cf.a794fbcf7b104c68.querysubtest.com:44173";
|
|
34
18
|
|
|
35
19
|
async function main() {
|
|
36
20
|
await Querysub.hostService("test");
|
|
37
21
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// let test = await Querysub.commitAsync(() => {
|
|
72
|
-
// let live = deploySchema()[getDomain()].deploy.live.hash;
|
|
73
|
-
// console.log({ live });
|
|
74
|
-
// return String(live);
|
|
75
|
-
// });
|
|
76
|
-
// console.log({ test });
|
|
22
|
+
let loggers = await getLoggers2Async();
|
|
23
|
+
let infoLogs = loggers.infoLogs;
|
|
24
|
+
|
|
25
|
+
let params: SearchParams = {
|
|
26
|
+
startTime: START_TIME,
|
|
27
|
+
endTime: END_TIME,
|
|
28
|
+
limit: LIMIT,
|
|
29
|
+
findBuffer: Buffer.from(SEARCH, "utf8"),
|
|
30
|
+
searchFromStart: true,
|
|
31
|
+
forceReadProduction: true,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let emits: LogDatum[] = [];
|
|
35
|
+
let start = Date.now();
|
|
36
|
+
let result = await infoLogs.clientFind({
|
|
37
|
+
params,
|
|
38
|
+
nodeId: NODE_ID,
|
|
39
|
+
onResult: (match) => {
|
|
40
|
+
emits.push(match);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
let elapsed = Date.now() - start;
|
|
44
|
+
let earliest = emits.length > 0 ? Math.min(...emits.map(e => e.time)) : undefined;
|
|
45
|
+
let latest = emits.length > 0 ? Math.max(...emits.map(e => e.time)) : undefined;
|
|
46
|
+
console.log(
|
|
47
|
+
`emits=${emits.length} matchCount=${result?.matchCount} ` +
|
|
48
|
+
`blocksChecked=${result?.blockCheckedCount}/${result?.totalBlockCount} ` +
|
|
49
|
+
`filesScanned=${result?.backblazeFilesSearched}/${result?.totalBackblazeFiles} ` +
|
|
50
|
+
`earliest=${earliest !== undefined ? formatDateTimeDetailed(earliest) : "—"} ` +
|
|
51
|
+
`latest=${latest !== undefined ? formatDateTimeDetailed(latest) : "—"} ` +
|
|
52
|
+
`time=${formatTime(elapsed)}`
|
|
53
|
+
);
|
|
77
54
|
}
|
|
78
55
|
|
|
79
|
-
main().catch(console.error)
|
|
80
|
-
.finally(() => process.exit(0))
|
|
81
|
-
;
|
|
56
|
+
main().catch(e => console.error((e as Error).stack ?? e))
|
|
57
|
+
.finally(() => process.exit(0));
|