querysub 0.152.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,129 @@
|
|
|
1
|
+
import { ExternalRenderClass, qreact } from "../../4-dom/qreact";
|
|
2
|
+
import { throttleRerender } from "../../functional/throttleRerender";
|
|
3
|
+
import { pathWatcher } from "../../0-path-value-core/pathValueCore";
|
|
4
|
+
import { css } from "../../4-dom/css";
|
|
5
|
+
import { TimeRangeView } from "./TimeRangeView";
|
|
6
|
+
import { URLParam } from "../../library-components/URLParam";
|
|
7
|
+
import { InputLabelURL } from "../../library-components/InputLabel";
|
|
8
|
+
import { addEpsilons } from "../../bits";
|
|
9
|
+
import { formatNumber, formatPercent, formatTime } from "socket-function/src/formatting/format";
|
|
10
|
+
import { nextId, sort } from "socket-function/src/misc";
|
|
11
|
+
import { ATag } from "../../library-components/ATag";
|
|
12
|
+
import { managementPageURL, showingManagementURL } from "../../diagnostics/managementPages";
|
|
13
|
+
import { filterURL } from "../../diagnostics/logs/DiskLoggerPage";
|
|
14
|
+
import { watchShowLocalPaths, watchValueType } from "./LocalWatchViewer";
|
|
15
|
+
import { filtersURL, viewMode } from "./archiveViewerShared";
|
|
16
|
+
import { proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
|
|
17
|
+
import { LOCAL_DOMAIN_PATH } from "../../0-path-value-core/NodePathAuthorities";
|
|
18
|
+
|
|
19
|
+
const historySecondsURL = new URLParam("historySeconds", 10);
|
|
20
|
+
|
|
21
|
+
export class ComponentSyncStats extends qreact.Component<{ component: ExternalRenderClass }> {
|
|
22
|
+
state = {
|
|
23
|
+
expanded: false,
|
|
24
|
+
};
|
|
25
|
+
@throttleRerender({})
|
|
26
|
+
render() {
|
|
27
|
+
const { component } = this.props;
|
|
28
|
+
|
|
29
|
+
if (!this.state.expanded) {
|
|
30
|
+
return <button onClick={() => this.state.expanded = true}>View Syncing Time Chart</button>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let allPaths = new Set<string>();
|
|
34
|
+
for (let watcher of proxyWatcher.getAllWatchers()) {
|
|
35
|
+
let { paths, parentPaths } = watcher.lastWatches;
|
|
36
|
+
for (let path of paths) {
|
|
37
|
+
if (path.startsWith(LOCAL_DOMAIN_PATH)) continue;
|
|
38
|
+
allPaths.add(path);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let nestedPaths = new Set<string>();
|
|
43
|
+
function addNestedPaths(component: ExternalRenderClass) {
|
|
44
|
+
for (let path of component.renderWatcher.lastWatches.paths) {
|
|
45
|
+
nestedPaths.add(path);
|
|
46
|
+
}
|
|
47
|
+
for (let child of component.childComponents.values()) {
|
|
48
|
+
let childComponent = component.getInstance(child);
|
|
49
|
+
if (childComponent) {
|
|
50
|
+
addNestedPaths(childComponent);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
addNestedPaths(component);
|
|
55
|
+
|
|
56
|
+
let syncHistory = pathWatcher.debug_getSyncHistory();
|
|
57
|
+
syncHistory = syncHistory.filter(x => !x.path.startsWith(LOCAL_DOMAIN_PATH));
|
|
58
|
+
let endTime = addEpsilons(syncHistory.at(-1)?.end ?? Date.now(), 1);
|
|
59
|
+
let startTime = Math.max(syncHistory.at(0)?.start ?? 0, endTime - historySecondsURL.value * 1000);
|
|
60
|
+
|
|
61
|
+
let extentTime = (syncHistory.at(-1)?.end ?? 0) - (syncHistory.at(0)?.start ?? 0);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className={css.pad2(6, 2).hsla(0, 0, 0, 0.2).vbox(6)}>
|
|
65
|
+
<button onClick={() => this.state.expanded = false}>Collapse</button>
|
|
66
|
+
<div className={css.hbox(20)}>
|
|
67
|
+
<button onClick={() => this.forceUpdate()}>Refresh</button>
|
|
68
|
+
<InputLabelURL
|
|
69
|
+
label={`History seconds (${formatTime(extentTime)} extent)`}
|
|
70
|
+
url={historySecondsURL}
|
|
71
|
+
/>
|
|
72
|
+
<div>
|
|
73
|
+
<b>All paths</b>: {syncHistory.length}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<>
|
|
77
|
+
<b>
|
|
78
|
+
<ATag lightMode values={[
|
|
79
|
+
filtersURL.getOverride({}),
|
|
80
|
+
managementPageURL.getOverride("LocalWatchViewer"),
|
|
81
|
+
watchValueType.getOverride("watch"),
|
|
82
|
+
viewMode.getOverride("tree"),
|
|
83
|
+
showingManagementURL.getOverride(true),
|
|
84
|
+
watchShowLocalPaths.getOverride(false),
|
|
85
|
+
]}>
|
|
86
|
+
All Paths
|
|
87
|
+
</ATag>
|
|
88
|
+
</b>
|
|
89
|
+
<ComponentSyncStatsBase startTime={startTime} endTime={endTime} paths={allPaths} />
|
|
90
|
+
</>
|
|
91
|
+
<b>
|
|
92
|
+
<ATag lightMode values={[
|
|
93
|
+
filtersURL.getOverride({ ...filtersURL.value, [nextId()]: { column: "path", match: component.debugName, type: "and" } }),
|
|
94
|
+
managementPageURL.getOverride("LocalWatchViewer"),
|
|
95
|
+
watchValueType.getOverride("component"),
|
|
96
|
+
viewMode.getOverride("tree"),
|
|
97
|
+
showingManagementURL.getOverride(true),
|
|
98
|
+
watchShowLocalPaths.getOverride(false),
|
|
99
|
+
]}>
|
|
100
|
+
Current + NestedPaths
|
|
101
|
+
</ATag>
|
|
102
|
+
</b>
|
|
103
|
+
<ComponentSyncStatsBase startTime={startTime} endTime={endTime} paths={nestedPaths} />
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class ComponentSyncStatsBase extends qreact.Component<{
|
|
110
|
+
startTime: number;
|
|
111
|
+
endTime: number;
|
|
112
|
+
paths: Set<string>;
|
|
113
|
+
}> {
|
|
114
|
+
render() {
|
|
115
|
+
const { startTime, endTime, paths } = this.props;
|
|
116
|
+
let syncHistory = pathWatcher.debug_getSyncHistory();
|
|
117
|
+
syncHistory = syncHistory.filter(x => paths.has(x.path));
|
|
118
|
+
syncHistory = syncHistory.filter(x => !(x.end < startTime || x.start >= endTime));
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<TimeRangeView
|
|
122
|
+
ranges={syncHistory}
|
|
123
|
+
startTime={startTime}
|
|
124
|
+
endTime={endTime}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { PathValue, ReadLock, authorityStorage, epochTime, pathValueArchives, pathWatcher } from "../../0-path-value-core/pathValueCore";
|
|
2
|
+
import { PathValueProxyWatcher, doAtomicWrites, proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
|
|
3
|
+
import { ExternalRenderClass, qreact } from "../../4-dom/qreact";
|
|
4
|
+
import { Querysub } from "../../4-querysub/Querysub";
|
|
5
|
+
import { Table } from "../../5-diagnostics/Table";
|
|
6
|
+
import { appendToPathStr, getPathFromStr, getPathStr } from "../../path";
|
|
7
|
+
import { css } from "typesafecss";
|
|
8
|
+
import { filtersURL, formatPath, viewMode } from "./archiveViewerShared";
|
|
9
|
+
import { nextId, sort } from "socket-function/src/misc";
|
|
10
|
+
import { ArchiveViewerFilterUI, filterDatums } from "./archiveViewerFilter";
|
|
11
|
+
import { ArchiveViewerTree } from "./ArchiveViewerTree";
|
|
12
|
+
import { Button } from "../../library-components/Button";
|
|
13
|
+
import { URLParam } from "../../library-components/URLParam";
|
|
14
|
+
import { Anchor } from "../../library-components/ATag";
|
|
15
|
+
import { ButtonSelector } from "../../library-components/ButtonSelector";
|
|
16
|
+
import { throttleRerender } from "../../functional/throttleRerender";
|
|
17
|
+
import { InputLabel, InputLabelURL } from "../../library-components/InputLabel";
|
|
18
|
+
import { formatPercent, formatTime } from "socket-function/src/formatting/format";
|
|
19
|
+
import { LOCAL_DOMAIN_PATH } from "../../0-path-value-core/NodePathAuthorities";
|
|
20
|
+
import { TimeRange, TimeRangeView, getTimeRangeStats, getUniqueTimeByPath, normalizeTimeRanges } from "./TimeRangeView";
|
|
21
|
+
import { ComponentSyncStatsBase } from "./ComponentSyncStats";
|
|
22
|
+
import { addEpsilons } from "../../bits";
|
|
23
|
+
import { pathValueSerializer } from "../../-h-path-value-serialize/PathValueSerializer";
|
|
24
|
+
|
|
25
|
+
export const watchValueType = new URLParam("valueType", "watch" as "watch" | "component");
|
|
26
|
+
export const watchShowLocalPaths = new URLParam("showLocalPaths", false);
|
|
27
|
+
const filterToComponentsWithPath = new URLParam("filterToComponentsWithPath", "");
|
|
28
|
+
const filterToPathsWithComponent = new URLParam("filterToPathsWithComponent", "");
|
|
29
|
+
|
|
30
|
+
export class LocalWatchViewer extends qreact.Component {
|
|
31
|
+
// NOTE: We don't save this in the URL, because on refresh the times will be entirely different,
|
|
32
|
+
// so old time filters are unlikely to be relevant
|
|
33
|
+
state = {
|
|
34
|
+
filterStartTime: 0,
|
|
35
|
+
filterEndTime: 0,
|
|
36
|
+
};
|
|
37
|
+
getValues(syncHistory: { path: string, start: number, end: number }[]) {
|
|
38
|
+
let watchesAdded = new Set<string>();
|
|
39
|
+
let parentPathsAdded = new Set<string>();
|
|
40
|
+
let fakePathsSet = new Set<string>();
|
|
41
|
+
let fakePaths: PathValue[] = [];
|
|
42
|
+
let time = epochTime;
|
|
43
|
+
let locks: ReadLock[] = [];
|
|
44
|
+
|
|
45
|
+
let componentToPaths = new Map<string, string[]>();
|
|
46
|
+
let pathToComponents = new Map<string, string[]>();
|
|
47
|
+
let showLocalPaths = watchShowLocalPaths.value;
|
|
48
|
+
|
|
49
|
+
let uniqueTimePerPath = getUniqueTimeByPath(syncHistory);
|
|
50
|
+
|
|
51
|
+
for (let watcher of proxyWatcher.getAllWatchers()) {
|
|
52
|
+
let { paths, parentPaths } = watcher.lastWatches;
|
|
53
|
+
for (let path of paths) {
|
|
54
|
+
if (watchesAdded.has(path)) continue;
|
|
55
|
+
if (!showLocalPaths && path.startsWith(LOCAL_DOMAIN_PATH)) continue;
|
|
56
|
+
watchesAdded.add(path);
|
|
57
|
+
if (!fakePathsSet.has(path)) {
|
|
58
|
+
fakePathsSet.add(path);
|
|
59
|
+
fakePaths.push({ path, value: uniqueTimePerPath.get(path) || 0, time, locks, lockCount: 0 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (let parentPath of parentPaths) {
|
|
63
|
+
if (parentPathsAdded.has(parentPath)) continue;
|
|
64
|
+
if (!showLocalPaths && parentPath.startsWith(LOCAL_DOMAIN_PATH)) continue;
|
|
65
|
+
parentPathsAdded.add(parentPath);
|
|
66
|
+
let path = appendToPathStr(parentPath, "%entries()");
|
|
67
|
+
if (!fakePathsSet.has(path)) {
|
|
68
|
+
fakePathsSet.add(path);
|
|
69
|
+
fakePaths.push({ path, value: uniqueTimePerPath.get(path) || 0, time, locks, lockCount: 0 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let componentPaths: PathValue[] = [];
|
|
75
|
+
for (let component of qreact.allComponents) {
|
|
76
|
+
let path: string[] = [];
|
|
77
|
+
{
|
|
78
|
+
let cur: ExternalRenderClass | undefined = component;
|
|
79
|
+
while (cur) {
|
|
80
|
+
path.unshift(cur.debugName);
|
|
81
|
+
cur = cur.getParent();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let pathStr = getPathStr(path);
|
|
85
|
+
let watchCount = component.renderWatcher.lastWatches.paths.size;
|
|
86
|
+
|
|
87
|
+
let curPaths = Array.from(component.renderWatcher.lastWatches.paths);
|
|
88
|
+
if (!showLocalPaths) {
|
|
89
|
+
curPaths = curPaths.filter(a => !a.startsWith(LOCAL_DOMAIN_PATH));
|
|
90
|
+
if (curPaths.length === 0) continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
componentPaths.push({ path: pathStr, value: watchCount, time, locks, lockCount: 0 });
|
|
94
|
+
componentToPaths.set(pathStr, curPaths);
|
|
95
|
+
for (let componentPath of curPaths) {
|
|
96
|
+
let components = pathToComponents.get(componentPath);
|
|
97
|
+
if (!components) {
|
|
98
|
+
components = [];
|
|
99
|
+
pathToComponents.set(componentPath, components);
|
|
100
|
+
}
|
|
101
|
+
components.push(pathStr);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
watches: fakePaths,
|
|
107
|
+
components: componentPaths,
|
|
108
|
+
componentToPaths,
|
|
109
|
+
pathToComponents,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
renderTable(values: PathValue[], componentToPaths: Map<string, string[]>, pathToComponents: Map<string, string[]>, paths: Set<string>, syncHistory: { path: string, start: number, end: number }[]) {
|
|
113
|
+
let filters = filtersURL.value;
|
|
114
|
+
let timesByPath = new Map<string, TimeRange[]>();
|
|
115
|
+
for (let time of syncHistory) {
|
|
116
|
+
let path = time.path;
|
|
117
|
+
let times = timesByPath.get(path);
|
|
118
|
+
if (!times) {
|
|
119
|
+
times = [];
|
|
120
|
+
timesByPath.set(path, times);
|
|
121
|
+
}
|
|
122
|
+
times.push(time);
|
|
123
|
+
}
|
|
124
|
+
let allTimes = syncHistory.filter(x => paths.has(x.path));
|
|
125
|
+
let startTime = this.state.filterStartTime;
|
|
126
|
+
let endTime = this.state.filterEndTime;
|
|
127
|
+
//let allStats = getTimeRangeStats({ ranges: allTimes, startTime, endTime });
|
|
128
|
+
let uniqueTimes = getUniqueTimeByPath(syncHistory);
|
|
129
|
+
let totalUniqueTime = Array.from(uniqueTimes.values()).reduce((a, b) => a + b, 0);
|
|
130
|
+
return (
|
|
131
|
+
<div class={css.vbox(4).fillWidth}>
|
|
132
|
+
<Table
|
|
133
|
+
cellClass={css.pad2(4, 1).bord(1, { h: 0, s: 0, l: 50 }).hsla(0, 0, 95, 0.25)}
|
|
134
|
+
rows={values}
|
|
135
|
+
columns={{
|
|
136
|
+
canGCValue: {
|
|
137
|
+
title: "Debug",
|
|
138
|
+
formatter: (value, context) => {
|
|
139
|
+
const pathValue = context?.row;
|
|
140
|
+
if (!pathValue) return undefined;
|
|
141
|
+
return <div>
|
|
142
|
+
<button onClick={() => {
|
|
143
|
+
console.log(authorityStorage.getValue(pathValue.path));
|
|
144
|
+
}}>Log Current Value</button>
|
|
145
|
+
</div>;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
value: {
|
|
149
|
+
title: "Debug Breakpoints",
|
|
150
|
+
formatter: (value, context) => {
|
|
151
|
+
let pathValue = context?.row;
|
|
152
|
+
if (!pathValue) return undefined;
|
|
153
|
+
let path = pathValue.path;
|
|
154
|
+
let breakOnWrites = PathValueProxyWatcher.BREAK_ON_WRITES.has(path);
|
|
155
|
+
let breakOnReads = PathValueProxyWatcher.BREAK_ON_READS.has(path);
|
|
156
|
+
let logOnRead = PathValueProxyWatcher.LOG_WRITES_INCLUDES.has(path);
|
|
157
|
+
let selectedButton = css.hsl(120, 75, 75);
|
|
158
|
+
return (
|
|
159
|
+
<div class={css.hbox(10, 0).wrap}>
|
|
160
|
+
<button class={breakOnWrites && selectedButton || ""} onClick={() => {
|
|
161
|
+
if (breakOnWrites) {
|
|
162
|
+
PathValueProxyWatcher.BREAK_ON_WRITES.delete(path);
|
|
163
|
+
} else {
|
|
164
|
+
PathValueProxyWatcher.BREAK_ON_WRITES.add(path);
|
|
165
|
+
}
|
|
166
|
+
this.forceUpdate();
|
|
167
|
+
}}>
|
|
168
|
+
=
|
|
169
|
+
</button>
|
|
170
|
+
<button class={breakOnReads && selectedButton || ""} onClick={() => {
|
|
171
|
+
if (breakOnReads) {
|
|
172
|
+
PathValueProxyWatcher.BREAK_ON_READS.delete(path);
|
|
173
|
+
} else {
|
|
174
|
+
PathValueProxyWatcher.BREAK_ON_READS.add(path);
|
|
175
|
+
}
|
|
176
|
+
this.forceUpdate();
|
|
177
|
+
}}>
|
|
178
|
+
===
|
|
179
|
+
</button>
|
|
180
|
+
<button class={logOnRead && selectedButton || ""} onClick={() => {
|
|
181
|
+
if (logOnRead) {
|
|
182
|
+
PathValueProxyWatcher.LOG_WRITES_INCLUDES.delete(path);
|
|
183
|
+
} else {
|
|
184
|
+
PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
|
|
185
|
+
}
|
|
186
|
+
this.forceUpdate();
|
|
187
|
+
}}>
|
|
188
|
+
Log
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
event: {
|
|
195
|
+
title: "Syncing Time",
|
|
196
|
+
formatter: (value, context) => {
|
|
197
|
+
let pathValue = context?.row;
|
|
198
|
+
if (!pathValue) return undefined;
|
|
199
|
+
let paths = [pathValue.path];
|
|
200
|
+
if (watchValueType.value === "component") {
|
|
201
|
+
paths = componentToPaths.get(pathValue.path) ?? [];
|
|
202
|
+
}
|
|
203
|
+
let time = uniqueTimes.get(pathValue.path) || 0;
|
|
204
|
+
return <div className={css.relative.padding(0, "important")}>
|
|
205
|
+
<div className={
|
|
206
|
+
css.hsla(0, 0, 0, 0.2).absolute.pos(0, 0).fillHeight
|
|
207
|
+
.width(`${(time / totalUniqueTime) * 100}%`)
|
|
208
|
+
} />
|
|
209
|
+
<div className={css.relative.pad2(4, 1)}>
|
|
210
|
+
{formatTime(time)}
|
|
211
|
+
</div>
|
|
212
|
+
</div>;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
time: (
|
|
216
|
+
watchValueType.value === "watch" && ({
|
|
217
|
+
title: "Components",
|
|
218
|
+
formatter: (value, context) => {
|
|
219
|
+
let pathValue = context?.row;
|
|
220
|
+
if (!pathValue) return undefined;
|
|
221
|
+
let components = pathToComponents.get(pathValue.path);
|
|
222
|
+
return (
|
|
223
|
+
<Anchor values={[
|
|
224
|
+
{ param: filtersURL, value: undefined },
|
|
225
|
+
{ param: watchValueType, value: "component" },
|
|
226
|
+
{ param: filterToComponentsWithPath, value: pathValue.path }]
|
|
227
|
+
} onClick={() => {
|
|
228
|
+
this.state.filterStartTime = 0;
|
|
229
|
+
this.state.filterEndTime = 0;
|
|
230
|
+
}}>
|
|
231
|
+
{components?.length || 0}
|
|
232
|
+
</Anchor>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|| ({
|
|
237
|
+
title: "Paths",
|
|
238
|
+
formatter: (value, context) => {
|
|
239
|
+
let pathValue = context?.row;
|
|
240
|
+
if (!pathValue) return undefined;
|
|
241
|
+
let paths = componentToPaths.get(pathValue.path);
|
|
242
|
+
return (
|
|
243
|
+
<Anchor values={[
|
|
244
|
+
{ param: filtersURL, value: undefined },
|
|
245
|
+
{ param: watchValueType, value: "watch" },
|
|
246
|
+
{ param: filterToPathsWithComponent, value: pathValue.path },
|
|
247
|
+
]}>
|
|
248
|
+
{paths?.length || 0}
|
|
249
|
+
</Anchor>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
),
|
|
254
|
+
source: {
|
|
255
|
+
title: "Name",
|
|
256
|
+
formatter: (value, context) => {
|
|
257
|
+
let row = context?.row;
|
|
258
|
+
if (!row) return undefined;
|
|
259
|
+
let pathParts = getPathFromStr(row.path);
|
|
260
|
+
let debugName = pathParts.at(-1);
|
|
261
|
+
return (
|
|
262
|
+
<div>
|
|
263
|
+
<div className={css.hbox(10).whiteSpace("nowrap")}>
|
|
264
|
+
{debugName}
|
|
265
|
+
|
|
266
|
+
<a href={"#"} onClick={(e) => {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
let component = Array.from(qreact.allComponents).find(a => a.debugName === debugName);
|
|
270
|
+
if (component) {
|
|
271
|
+
window.open(component.getVSCodeLink(), "_blank");
|
|
272
|
+
}
|
|
273
|
+
}}>
|
|
274
|
+
IDE
|
|
275
|
+
</a>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
path: {
|
|
282
|
+
formatter: (value) => {
|
|
283
|
+
let pathParts = getPathFromStr(value);
|
|
284
|
+
return (
|
|
285
|
+
<div>
|
|
286
|
+
<div class={css.hbox(0).wrap}>
|
|
287
|
+
{formatPath(pathParts, (part, index) => {
|
|
288
|
+
let newPath = getPathStr(pathParts.slice(0, index + 1));
|
|
289
|
+
filters[nextId()] = { column: "path", match: newPath, type: "and" };
|
|
290
|
+
})}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
}}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
renderTree(values: PathValue[]) {
|
|
302
|
+
return <ArchiveViewerTree
|
|
303
|
+
data={values}
|
|
304
|
+
lite
|
|
305
|
+
valueLabel={"Sync Time"}
|
|
306
|
+
sumFormatter={formatTime}
|
|
307
|
+
/>;
|
|
308
|
+
}
|
|
309
|
+
@throttleRerender({})
|
|
310
|
+
render() {
|
|
311
|
+
let now = Date.now();
|
|
312
|
+
let filterStartTime = this.state.filterStartTime;
|
|
313
|
+
let filterEndTime = this.state.filterEndTime;
|
|
314
|
+
|
|
315
|
+
let syncHistory = pathWatcher.debug_getSyncHistory();
|
|
316
|
+
let paths = new Set<string>();
|
|
317
|
+
if (filterStartTime || filterEndTime) {
|
|
318
|
+
syncHistory = normalizeTimeRanges(syncHistory, filterStartTime, filterEndTime);
|
|
319
|
+
paths = new Set(syncHistory.map(a => a.path));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let filters = filtersURL.value;
|
|
323
|
+
let state = this.getValues(syncHistory);
|
|
324
|
+
let values = (
|
|
325
|
+
watchValueType.value === "watch" && state.watches
|
|
326
|
+
|| watchValueType.value === "component" && state.components
|
|
327
|
+
|| []
|
|
328
|
+
);
|
|
329
|
+
let filterObj = filterDatums(values, Object.values(filters));
|
|
330
|
+
if (watchValueType.value === "component" && filterToComponentsWithPath.value) {
|
|
331
|
+
let filter = filterToComponentsWithPath.value;
|
|
332
|
+
filterObj = { ...filterObj };
|
|
333
|
+
filterObj.filtered = filterObj.filtered.filter(a => state.componentToPaths.get(a.path)?.includes(filter));
|
|
334
|
+
}
|
|
335
|
+
if (watchValueType.value === "watch" && filterToPathsWithComponent.value) {
|
|
336
|
+
let filter = filterToPathsWithComponent.value;
|
|
337
|
+
filterObj = { ...filterObj };
|
|
338
|
+
filterObj.filtered = filterObj.filtered.filter(a => state.pathToComponents.get(a.path)?.includes(filter));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (filterStartTime || filterEndTime) {
|
|
342
|
+
filterObj.filtered = filterObj.filtered.filter(a => paths.has(a.path));
|
|
343
|
+
} else {
|
|
344
|
+
let paths = new Set(filterObj.filtered.map(a => a.path));
|
|
345
|
+
if (watchValueType.value === "component") {
|
|
346
|
+
paths = new Set(filterObj.filtered.map(a => a.path).flatMap(a => state.componentToPaths.get(a) ?? []));
|
|
347
|
+
}
|
|
348
|
+
syncHistory = syncHistory.filter(x => paths.has(x.path));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let firstTime = syncHistory.at(0)?.start ?? 0;
|
|
352
|
+
let lastTime = syncHistory.at(-1)?.end ?? 0;
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
if (!filterStartTime) {
|
|
356
|
+
filterStartTime = firstTime;
|
|
357
|
+
} else {
|
|
358
|
+
firstTime = filterStartTime;
|
|
359
|
+
}
|
|
360
|
+
if (!filterEndTime) {
|
|
361
|
+
filterEndTime = addEpsilons(lastTime, 1);
|
|
362
|
+
} else {
|
|
363
|
+
lastTime = filterEndTime;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<div class={css.vbox(4).fillWidth.pad2(10, 4)}>
|
|
368
|
+
<div class={css.hbox(10)}>
|
|
369
|
+
<ButtonSelector<typeof watchValueType.value>
|
|
370
|
+
value={watchValueType.value}
|
|
371
|
+
onChange={value => watchValueType.value = value}
|
|
372
|
+
options={[
|
|
373
|
+
{ value: "watch", title: "Watch" },
|
|
374
|
+
{ value: "component", title: "Component" },
|
|
375
|
+
]}
|
|
376
|
+
/>
|
|
377
|
+
{filterToComponentsWithPath.value && <Button onClick={() => filterToComponentsWithPath.value = ""}>Clear Component Path Filter</Button>}
|
|
378
|
+
{filterToPathsWithComponent.value && <Button onClick={() => filterToPathsWithComponent.value = ""}>Clear Path Component Filter</Button>}
|
|
379
|
+
</div>
|
|
380
|
+
<ButtonSelector<typeof viewMode.value>
|
|
381
|
+
value={viewMode.value}
|
|
382
|
+
onChange={value => viewMode.value = value}
|
|
383
|
+
options={[
|
|
384
|
+
{ value: "table", title: "Table" },
|
|
385
|
+
{ value: "tree", title: "Tree" },
|
|
386
|
+
]}
|
|
387
|
+
/>
|
|
388
|
+
<div class={css.hbox(10)}>
|
|
389
|
+
<button onClick={() => this.forceUpdate()}>Refresh</button>
|
|
390
|
+
<InputLabelURL label="Show Local Paths" url={watchShowLocalPaths} checkbox />
|
|
391
|
+
<InputLabel
|
|
392
|
+
label="History Start Offset (s)" number
|
|
393
|
+
value={Math.round((now - filterStartTime) / 1000)}
|
|
394
|
+
onChangeValue={value => {
|
|
395
|
+
let duration = filterEndTime - filterStartTime;
|
|
396
|
+
this.state.filterStartTime = Date.now() - +value * 1000;
|
|
397
|
+
this.state.filterEndTime = this.state.filterStartTime + duration;
|
|
398
|
+
}}
|
|
399
|
+
/>
|
|
400
|
+
<InputLabel
|
|
401
|
+
label="History Duration (s)" number
|
|
402
|
+
value={Math.round((filterEndTime - filterStartTime) / 1000)}
|
|
403
|
+
onChangeValue={value => {
|
|
404
|
+
this.state.filterStartTime = filterStartTime;
|
|
405
|
+
this.state.filterEndTime = filterStartTime + +value * 1000;
|
|
406
|
+
}}
|
|
407
|
+
/>
|
|
408
|
+
<button onClick={() => {
|
|
409
|
+
this.state.filterStartTime = 0;
|
|
410
|
+
this.state.filterEndTime = 0;
|
|
411
|
+
}}>Reset Time Filter</button>
|
|
412
|
+
</div>
|
|
413
|
+
<TimeRangeView
|
|
414
|
+
ranges={syncHistory}
|
|
415
|
+
startTime={firstTime}
|
|
416
|
+
endTime={lastTime}
|
|
417
|
+
selectTimeRange={(startTime, endTime) => {
|
|
418
|
+
console.log("selectTimeRange", startTime, endTime);
|
|
419
|
+
this.state.filterStartTime = startTime;
|
|
420
|
+
this.state.filterEndTime = endTime;
|
|
421
|
+
}}
|
|
422
|
+
/>
|
|
423
|
+
<ArchiveViewerFilterUI filterObj={filterObj} />
|
|
424
|
+
{
|
|
425
|
+
viewMode.value === "table" && this.renderTable(filterObj.filtered, state.componentToPaths, state.pathToComponents, paths, syncHistory)
|
|
426
|
+
|| this.renderTree(filterObj.filtered)
|
|
427
|
+
}
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|