querysub 0.153.0 → 0.154.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 +6 -6
- package/src/-b-authorities/cloudflareHelpers.ts +11 -2
- package/src/3-path-functions/PathFunctionRunner.ts +168 -97
- package/src/3-path-functions/PathFunctionRunnerMain.ts +8 -2
- package/src/3-path-functions/pathFunctionLoader.ts +11 -6
- package/src/3-path-functions/syncSchema.ts +10 -1
- package/src/4-deploy/edgeBootstrap.ts +10 -1
- package/src/4-querysub/Querysub.ts +77 -3
- package/src/4-querysub/QuerysubController.ts +22 -2
- package/src/4-querysub/permissions.ts +33 -2
- package/src/4-querysub/querysubPrediction.ts +52 -18
- package/src/archiveapps/archiveGCEntry.tsx +38 -0
- package/src/archiveapps/archiveJoinEntry.ts +121 -0
- package/src/archiveapps/archiveMergeEntry.tsx +47 -0
- package/src/archiveapps/compressTest.tsx +59 -0
- package/src/archiveapps/lockTest.ts +127 -0
- package/src/config.ts +5 -0
- package/src/diagnostics/managementPages.tsx +55 -0
- package/src/diagnostics/misc-pages/ArchiveInspect.tsx +325 -0
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +781 -0
- package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +156 -0
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +573 -0
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +129 -0
- package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +431 -0
- package/src/diagnostics/misc-pages/RequireAuditPage.tsx +218 -0
- package/src/diagnostics/misc-pages/SnapshotViewer.tsx +206 -0
- package/src/diagnostics/misc-pages/TimeRangeView.tsx +648 -0
- package/src/diagnostics/misc-pages/archiveViewerFilter.tsx +221 -0
- package/src/diagnostics/misc-pages/archiveViewerShared.tsx +76 -0
- package/src/email/postmark.tsx +40 -0
- package/src/email/sendgrid.tsx +44 -0
- package/src/functional/UndoWatch.tsx +133 -0
- package/src/functional/diff.ts +858 -0
- package/src/functional/promiseCache.ts +67 -0
- package/src/functional/random.ts +9 -0
- package/src/functional/runCommand.ts +42 -0
- package/src/functional/runOnce.ts +7 -0
- package/src/functional/stats.ts +61 -0
- package/src/functional/throttleRerender.tsx +80 -0
- package/src/library-components/AspectSizedComponent.tsx +88 -0
- package/src/library-components/Histogram.tsx +338 -0
- package/src/library-components/InlinePopup.tsx +67 -0
- package/src/library-components/Notifications.tsx +153 -0
- package/src/library-components/RenderIfVisible.tsx +80 -0
- package/src/library-components/SimpleNotification.tsx +133 -0
- package/src/library-components/TabbedUI.tsx +39 -0
- package/src/library-components/animateAnyElement.tsx +65 -0
- package/src/library-components/errorNotifications.tsx +81 -0
- package/src/library-components/placeholder.ts +18 -0
- package/src/misc/format2.ts +48 -0
- package/src/misc.ts +33 -0
- package/src/misc2.ts +5 -0
- package/src/server.ts +2 -1
- package/src/storage/diskCache.ts +227 -0
- package/src/storage/diskCache2.ts +122 -0
- package/src/storage/fileSystemPointer.ts +72 -0
- package/src/user-implementation/LoginPage.tsx +78 -0
- package/src/user-implementation/RequireAuditPage.tsx +219 -0
- package/src/user-implementation/SecurityPage.tsx +212 -0
- package/src/user-implementation/UserPage.tsx +320 -0
- package/src/user-implementation/addSuperUser.ts +21 -0
- package/src/user-implementation/canSeeSource.ts +41 -0
- package/src/user-implementation/loginEmail.tsx +159 -0
- package/src/user-implementation/setEmailKey.ts +20 -0
- package/src/user-implementation/userData.ts +974 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import preact from "preact"; import { qreact } from "../../4-dom/qreact";
|
|
2
|
+
import { Querysub } from "../../4-querysub/Querysub";
|
|
3
|
+
import { css } from "../../4-dom/css";
|
|
4
|
+
import { isDefined } from "../../misc";
|
|
5
|
+
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
6
|
+
import { getPathStr1 } from "../../path";
|
|
7
|
+
import { URLParam, createURLSync } from "../../library-components/URLParam";
|
|
8
|
+
import { Input } from "../../library-components/Input";
|
|
9
|
+
import { ButtonSelector } from "../../library-components/ButtonSelector";
|
|
10
|
+
|
|
11
|
+
type ModuleFolderEntry = {
|
|
12
|
+
path: string;
|
|
13
|
+
children: ModuleTreeEntry[];
|
|
14
|
+
sum: number;
|
|
15
|
+
count: number;
|
|
16
|
+
};
|
|
17
|
+
type ModuleTreeEntry = {
|
|
18
|
+
module: NodeJS.Module;
|
|
19
|
+
} | ModuleFolderEntry;
|
|
20
|
+
|
|
21
|
+
const filterURL = createURLSync<string>("filter", "");
|
|
22
|
+
const expandedURL = createURLSync<string>("expanded", "");
|
|
23
|
+
|
|
24
|
+
const valueType = new URLParam("requireauditvalue", "size" as "size" | "evaltime");
|
|
25
|
+
export class RequireAuditPage extends qreact.Component {
|
|
26
|
+
getModuleValue(module: NodeJS.Module) {
|
|
27
|
+
if (valueType.value === "evaltime") return (module.evalEndTime ?? 0) - (module.evalStartTime ?? 0);
|
|
28
|
+
return module.size || 0;
|
|
29
|
+
}
|
|
30
|
+
formatValue(value: number) {
|
|
31
|
+
if (valueType.value === "evaltime") return formatTime(value);
|
|
32
|
+
return formatNumber(value) + "B";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getFontSize(size: number, total: number) {
|
|
36
|
+
if (size / total > 0.20) return 30;
|
|
37
|
+
if (size / total > 0.02) return 20;
|
|
38
|
+
return 12;
|
|
39
|
+
return (
|
|
40
|
+
Math.max((size / total) ** 0.5 * 50, 10)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
renderModule(module: NodeJS.Module, depth: number) {
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<div>{module.filename}</div>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
renderTreeLevel(entry: ModuleTreeEntry[], root = true, depth = 0, config: {
|
|
52
|
+
totalSum: number;
|
|
53
|
+
maxSum: number;
|
|
54
|
+
expanded: string;
|
|
55
|
+
}): preact.ComponentChild {
|
|
56
|
+
const renderLine = (entry: ModuleTreeEntry, contents: preact.ComponentChild) => {
|
|
57
|
+
let size = "module" in entry ? this.getModuleValue(entry.module) : entry.sum;
|
|
58
|
+
let path = "module" in entry ? entry.module.filename : entry.path;
|
|
59
|
+
let expanded = config.expanded.includes(getPathStr1(path));
|
|
60
|
+
const isTreeNode = !("module" in entry);
|
|
61
|
+
return (
|
|
62
|
+
<div class={
|
|
63
|
+
css.hbox(10).marginLeft(depth * 20).relative.fillWidth
|
|
64
|
+
.fontSize(this.getFontSize(size, config.totalSum))
|
|
65
|
+
+ (isTreeNode && css.button)
|
|
66
|
+
}
|
|
67
|
+
onClick={(e) => {
|
|
68
|
+
if (!isTreeNode) return;
|
|
69
|
+
if (expanded) {
|
|
70
|
+
expandedURL.value = config.expanded.replace(getPathStr1(entry.path), "");
|
|
71
|
+
} else {
|
|
72
|
+
expandedURL.value += getPathStr1(entry.path);
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<div
|
|
77
|
+
class={
|
|
78
|
+
css.absolute.pos(0, 2).height("calc(100% - 4px)" as "100%").width((size / config.maxSum) * 100 + "%" as "100%")
|
|
79
|
+
.hsl(isTreeNode ? 180 : 210, 75, 50).opacity(0.5)
|
|
80
|
+
.borderRadius(2)
|
|
81
|
+
.zIndex(-1)
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
{contents}
|
|
85
|
+
<span class={css.opacity(0.4)}>
|
|
86
|
+
{
|
|
87
|
+
"module" in entry
|
|
88
|
+
? <>({this.formatValue(this.getModuleValue(entry.module))}) / {(size / config.totalSum * 100).toFixed(1)}%</>
|
|
89
|
+
: <>({formatNumber(entry.count)} / {this.formatValue(entry.sum)}) / {(size / config.totalSum * 100).toFixed(1)}%</>
|
|
90
|
+
}
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
if (entry.length === 1) {
|
|
96
|
+
let singleEntry = entry[0];
|
|
97
|
+
if ("module" in singleEntry) {
|
|
98
|
+
return renderLine(singleEntry, this.renderModule(singleEntry.module, depth));
|
|
99
|
+
} else {
|
|
100
|
+
return this.renderTreeLevel(singleEntry.children, root, depth, config);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return (
|
|
104
|
+
<div class={css.vbox(0).fillWidth}>
|
|
105
|
+
{entry.map(entry => {
|
|
106
|
+
if ("module" in entry) return renderLine(entry, this.renderModule(entry.module, depth));
|
|
107
|
+
let expanded = config.expanded.includes(getPathStr1(entry.path));
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={css.vbox(0).fillWidth
|
|
111
|
+
+ (expanded && css.paddingBottom(10))
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
{renderLine(entry,
|
|
115
|
+
<span>{entry.path}</span>
|
|
116
|
+
)}
|
|
117
|
+
{expanded && this.renderTreeLevel(entry.children, false, depth + 1, config)}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
render() {
|
|
125
|
+
const self = this;
|
|
126
|
+
|
|
127
|
+
// NOTE: We might need to re-render periodically, in order to catch new requires that are added.
|
|
128
|
+
// However... we want to avoid this, as rendering when the user is typing can cause issues.
|
|
129
|
+
// Querysub.timeDelayed(5000);
|
|
130
|
+
|
|
131
|
+
let rootModules: ModuleFolderEntry[] = [];
|
|
132
|
+
let modulesList = Object.values(require.cache).filter(isDefined);
|
|
133
|
+
let filter = filterURL.value.toLowerCase();
|
|
134
|
+
if (filter) {
|
|
135
|
+
modulesList = modulesList.filter(module => module.filename.toLowerCase().includes(filter));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let entryByPath = new Map<string, ModuleFolderEntry>();
|
|
139
|
+
function getParentPath(path: string) {
|
|
140
|
+
return path.split("/").slice(0, -1).join("/");
|
|
141
|
+
}
|
|
142
|
+
function getTreeEntry(path: string) {
|
|
143
|
+
let entry = entryByPath.get(path);
|
|
144
|
+
if (!entry) {
|
|
145
|
+
entry = { children: [], path, sum: 0, count: 0, };
|
|
146
|
+
entryByPath.set(path, entry);
|
|
147
|
+
let parent = getParentPath(path);
|
|
148
|
+
if (parent) {
|
|
149
|
+
getTreeEntry(parent).children.push(entry);
|
|
150
|
+
} else {
|
|
151
|
+
rootModules.push(entry);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return entry;
|
|
155
|
+
}
|
|
156
|
+
for (let module of modulesList) {
|
|
157
|
+
getTreeEntry(getParentPath(module.filename)).children.push({ module });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function calculateSizes(entry: ModuleFolderEntry) {
|
|
161
|
+
entry.sum = 0;
|
|
162
|
+
entry.count = 0;
|
|
163
|
+
for (let child of entry.children) {
|
|
164
|
+
if ("module" in child) {
|
|
165
|
+
entry.sum += self.getModuleValue(child.module);
|
|
166
|
+
entry.count++;
|
|
167
|
+
} else {
|
|
168
|
+
calculateSizes(child);
|
|
169
|
+
entry.sum += child.sum;
|
|
170
|
+
entry.count += child.count;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (let rootModule of rootModules) {
|
|
175
|
+
calculateSizes(rootModule);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
while (rootModules.length === 1) {
|
|
179
|
+
rootModules = rootModules[0].children as ModuleFolderEntry[] || [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let totalCount = rootModules.reduce((sum, entry) => sum + entry.count, 0);
|
|
183
|
+
let totalSum = rootModules.reduce((sum, entry) => sum + entry.sum, 0);
|
|
184
|
+
let maxSum = Math.max(...rootModules.map(entry => entry.sum));
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div class={css.pad(20).vbox(20).fillWidth.overflowY("auto").overflowX("hidden")}>
|
|
188
|
+
<div class={css.hbox(40)}>
|
|
189
|
+
<h1>
|
|
190
|
+
{totalCount} / {this.formatValue(totalSum)}
|
|
191
|
+
</h1>
|
|
192
|
+
<ButtonSelector
|
|
193
|
+
options={[
|
|
194
|
+
{ value: "size", title: "Size" },
|
|
195
|
+
{ value: "evaltime", title: "Eval Time (Includes Child Time)" },
|
|
196
|
+
]}
|
|
197
|
+
value={valueType.value}
|
|
198
|
+
onChange={value => valueType.value = value as any}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
<Input
|
|
202
|
+
flavor="large"
|
|
203
|
+
focusOnMount
|
|
204
|
+
placeholder={"Filter"}
|
|
205
|
+
className={css.width("60vw")}
|
|
206
|
+
value={filterURL.value}
|
|
207
|
+
hot
|
|
208
|
+
onChange={e => filterURL.value = e.currentTarget.value}
|
|
209
|
+
/>
|
|
210
|
+
{this.renderTreeLevel(rootModules, undefined, undefined, {
|
|
211
|
+
totalSum,
|
|
212
|
+
maxSum,
|
|
213
|
+
expanded: expandedURL.value,
|
|
214
|
+
})}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
module.allowclient = true;
|
|
2
|
+
|
|
3
|
+
import { ArchiveSnapshotOverview, ArchiveSnapshotRead, getSnapshot, getSnapshotList, loadSnapshot, saveSnapshot } from "../../0-path-value-core/archiveLocks/archiveSnapshots";
|
|
4
|
+
import { qreact } from "../../4-dom/qreact";
|
|
5
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
6
|
+
import { ValueAuditController } from "../../5-diagnostics/memoryValueAudit";
|
|
7
|
+
import { getBrowserUrlNode } from "../../-f-node-discovery/NodeDiscovery";
|
|
8
|
+
import { Table } from "../../5-diagnostics/Table";
|
|
9
|
+
import { css } from "typesafecss";
|
|
10
|
+
import { t } from "../../4-querysub/Querysub";
|
|
11
|
+
import { ATag, Anchor } from "../../library-components/ATag";
|
|
12
|
+
import { URLParam } from "../../library-components/URLParam";
|
|
13
|
+
import { formatDateTime, formatNumber } from "socket-function/src/formatting/format";
|
|
14
|
+
import { Button } from "../../library-components/Button";
|
|
15
|
+
import { showModal } from "../../5-diagnostics/Modal";
|
|
16
|
+
import { FullscreenModal } from "../../5-diagnostics/FullscreenModal";
|
|
17
|
+
import { InputLabel } from "../../library-components/InputLabel";
|
|
18
|
+
import { green, magenta, red } from "socket-function/src/formatting/logColors";
|
|
19
|
+
import { archiveViewerHistoryPaths } from "./ArchiveViewer";
|
|
20
|
+
import { assertIsManagementUser, managementPageURL } from "../../diagnostics/managementPages";
|
|
21
|
+
import { getSyncedController } from "../../library-components/SyncedController";
|
|
22
|
+
|
|
23
|
+
// TODO: Create a transaction viewer, so we can view transactions in the recycle bin and data,
|
|
24
|
+
// to see why various files were moved.
|
|
25
|
+
// - This can help debug bad archives, who accidentally delete the entire database, create new files, etc.
|
|
26
|
+
// - Which apparently... we have? Although maybe we fixed it?
|
|
27
|
+
|
|
28
|
+
const selectedSnapshot = new URLParam("selectedSnapshot", "");
|
|
29
|
+
export class SnapshotViewer extends qreact.Component {
|
|
30
|
+
state = t.state({
|
|
31
|
+
// file => true
|
|
32
|
+
expanded: t.lookup(t.boolean)
|
|
33
|
+
});
|
|
34
|
+
render() {
|
|
35
|
+
let controller = SnapshotViewerSynced(getBrowserUrlNode());
|
|
36
|
+
let snapshotList = controller.getSnapshotList();
|
|
37
|
+
if (!snapshotList) return <div>Loading...</div>;
|
|
38
|
+
let file = selectedSnapshot.value;
|
|
39
|
+
if (!file) {
|
|
40
|
+
return (
|
|
41
|
+
<div class={css.pad2(10)}>
|
|
42
|
+
<Table
|
|
43
|
+
rows={snapshotList}
|
|
44
|
+
columns={{
|
|
45
|
+
time: {},
|
|
46
|
+
fileCount: {},
|
|
47
|
+
valueCount: {},
|
|
48
|
+
byteCount: {},
|
|
49
|
+
file: {
|
|
50
|
+
title: "Actions",
|
|
51
|
+
formatter: (file, context) => {
|
|
52
|
+
return <div class={css.hbox(10)}>
|
|
53
|
+
<ATag values={[{ param: selectedSnapshot, value: file }]}>
|
|
54
|
+
View
|
|
55
|
+
</ATag>
|
|
56
|
+
{context?.row?.file === "live" &&
|
|
57
|
+
<Button onClick={async () => {
|
|
58
|
+
await SnapshotViewerController.nodes[getBrowserUrlNode()].saveLiveSnapshot();
|
|
59
|
+
console.log("Saved live snapshot.");
|
|
60
|
+
}}>
|
|
61
|
+
Save
|
|
62
|
+
</Button>
|
|
63
|
+
}
|
|
64
|
+
</div>;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
const entry = snapshotList.find(x => x.file === file);
|
|
75
|
+
let snapshotInfo = controller.getSnapshot(file);
|
|
76
|
+
if (!entry) return <div>Snapshot not found, {file}</div>;
|
|
77
|
+
return (
|
|
78
|
+
<div class={css.pad2(10).vbox(10)}>
|
|
79
|
+
<div class={css.hbox(10)}>
|
|
80
|
+
<Anchor values={[{ param: selectedSnapshot, value: "" }]}>
|
|
81
|
+
Back
|
|
82
|
+
</Anchor>
|
|
83
|
+
<h2>Snapshot: {file}</h2>
|
|
84
|
+
</div>
|
|
85
|
+
{entry &&
|
|
86
|
+
<div class={css.vbox(2)}>
|
|
87
|
+
<div>Time: {formatDateTime(entry.time)}</div>
|
|
88
|
+
<div>File Count: {formatNumber(entry.fileCount)}</div>
|
|
89
|
+
<div>Value Count: {formatNumber(entry.valueCount)}</div>
|
|
90
|
+
<div>Byte Count: {formatNumber(entry.byteCount)}</div>
|
|
91
|
+
</div>
|
|
92
|
+
}
|
|
93
|
+
<Button onClick={async () => {
|
|
94
|
+
let didConfirm = await seriousConfirm({
|
|
95
|
+
message: `Restoring will break all servers. This won't break your data, but you will have to restart all servers before the system will be stable again. Before then data will be random, and invalid writes might be allowed. It is recommend to run this with only 1 querysub server running, on a developers machine. Consider writing a database iterate script instead of restoring (similar to how gc and join work).`,
|
|
96
|
+
});
|
|
97
|
+
if (!didConfirm) {
|
|
98
|
+
console.log(red("Aborted restore"));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
console.log(magenta("Restoring snapshot..."));
|
|
102
|
+
await SnapshotViewerController.nodes[getBrowserUrlNode()].loadSnapshot({
|
|
103
|
+
overview: entry
|
|
104
|
+
});
|
|
105
|
+
console.log(green("Restored snapshot"));
|
|
106
|
+
window.location.reload();
|
|
107
|
+
}}>
|
|
108
|
+
Restore
|
|
109
|
+
</Button>
|
|
110
|
+
|
|
111
|
+
{snapshotInfo && <Anchor values={[
|
|
112
|
+
{ param: archiveViewerHistoryPaths, value: snapshotInfo.files.map(x => x.file) },
|
|
113
|
+
{ param: managementPageURL, value: "ArchiveViewer" },
|
|
114
|
+
]}>
|
|
115
|
+
View in Archive Viewer
|
|
116
|
+
</Anchor>}
|
|
117
|
+
{snapshotInfo &&
|
|
118
|
+
<Table
|
|
119
|
+
rows={snapshotInfo.files}
|
|
120
|
+
columns={{
|
|
121
|
+
//file: {},
|
|
122
|
+
state: {},
|
|
123
|
+
time: {},
|
|
124
|
+
valueCount: {},
|
|
125
|
+
byteCount: {},
|
|
126
|
+
source: {},
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function seriousConfirm(config: {
|
|
136
|
+
message: string;
|
|
137
|
+
}): Promise<boolean> {
|
|
138
|
+
return await new Promise<boolean>((resolve, reject) => {
|
|
139
|
+
let modal = showModal({
|
|
140
|
+
content: <FullscreenModal onCancel={() => {
|
|
141
|
+
modal.close();
|
|
142
|
+
resolve(false);
|
|
143
|
+
}}>
|
|
144
|
+
<div>{config.message}</div>
|
|
145
|
+
<InputLabel
|
|
146
|
+
label={<pre>
|
|
147
|
+
Kill the PathValueServer first.
|
|
148
|
+
Type "confirm" and press enter to continue.
|
|
149
|
+
Then watch querysub logs and wait until "finished loading snapshot" is logged.
|
|
150
|
+
Then kill querysub, and start all the processes normally.
|
|
151
|
+
</pre>}
|
|
152
|
+
onChangeValue={value => {
|
|
153
|
+
if (value === "confirm") {
|
|
154
|
+
modal.close();
|
|
155
|
+
resolve(true);
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
</FullscreenModal>
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class SnapshotViewerControllerBase {
|
|
165
|
+
public async getSnapshotList(): Promise<ArchiveSnapshotOverview[]> {
|
|
166
|
+
return await getSnapshotList();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public async loadSnapshot(config: {
|
|
170
|
+
overview: ArchiveSnapshotOverview,
|
|
171
|
+
}): Promise<void> {
|
|
172
|
+
await loadSnapshot({
|
|
173
|
+
overview: config.overview,
|
|
174
|
+
async audit(nodeId) {
|
|
175
|
+
await ValueAuditController.nodes[nodeId].diskAuditNow();
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public async getSnapshot(snapshotFile: string | "live"): Promise<ArchiveSnapshotRead> {
|
|
181
|
+
return await getSnapshot(snapshotFile);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public async saveLiveSnapshot() {
|
|
185
|
+
let files = await getSnapshot("live");
|
|
186
|
+
await saveSnapshot({ files: files.files.map(x => x.file) });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const SnapshotViewerController = SocketFunction.register(
|
|
191
|
+
"SnapshotViewerController-a78ae3cf-5024-49da-b71a-f064a5d4b5f4",
|
|
192
|
+
new SnapshotViewerControllerBase(),
|
|
193
|
+
() => ({
|
|
194
|
+
getSnapshotList: {},
|
|
195
|
+
loadSnapshot: {},
|
|
196
|
+
getSnapshot: {},
|
|
197
|
+
saveLiveSnapshot: {},
|
|
198
|
+
}),
|
|
199
|
+
() => ({
|
|
200
|
+
hooks: [assertIsManagementUser],
|
|
201
|
+
}),
|
|
202
|
+
{
|
|
203
|
+
noAutoExpose: true,
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
const SnapshotViewerSynced = getSyncedController(SnapshotViewerController);
|