querysub 0.395.0 → 0.396.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 +1 -1
- package/src/-c-identity/IdentityController.ts +19 -1
- package/src/0-path-value-core/pathValueCore.ts +8 -8
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +3 -4
- package/src/diagnostics/logs/IndexedLogs/bufferSearchFindMatcher.ts +1 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +358 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +149 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +33 -705
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +291 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +151 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +61 -34
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +4 -3
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -3
- package/src/library-components/StartEllipsis.tsx +1 -1
- package/tempnotes.txt +0 -0
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ import { delay } from "socket-function/src/batching";
|
|
|
17
17
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
18
18
|
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
19
19
|
import { red } from "socket-function/src/formatting/logColors";
|
|
20
|
+
import { isNode } from "typesafecss";
|
|
20
21
|
|
|
21
22
|
let callerInfo = new Map<CallerContext, {
|
|
22
23
|
reconnectNodeId: string | undefined;
|
|
@@ -108,6 +109,7 @@ export interface ChangeIdentityPayload {
|
|
|
108
109
|
certIssuer: string;
|
|
109
110
|
serverId: string;
|
|
110
111
|
mountedPort: number | undefined;
|
|
112
|
+
debugEntryPoint: string | undefined;
|
|
111
113
|
}
|
|
112
114
|
class IdentityControllerBase {
|
|
113
115
|
// IMPORTANT! We HAVE to call changeIdentity NOT JUST because we can't use peer certificates in the browser, BUT, also
|
|
@@ -158,7 +160,22 @@ class IdentityControllerBase {
|
|
|
158
160
|
pubKeyShort: getShortNumber(pubKey),
|
|
159
161
|
});
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
let duration = Date.now() - time;
|
|
164
|
+
console.log(`Authenticated identity for ${caller.nodeId} in ${formatTime(duration)}, at ${Date.now()}`, {
|
|
165
|
+
clientId: caller.nodeId,
|
|
166
|
+
reconnectNodeId,
|
|
167
|
+
duration,
|
|
168
|
+
mountedPort: payload.mountedPort,
|
|
169
|
+
debugEntryPoint: payload.debugEntryPoint,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
SocketFunction.onNextDisconnect(caller.nodeId, () => {
|
|
173
|
+
// NOTE: I don't really see any purpose of deleting from caller info. I don't think we're going to run out of memory because of too many callers authenticating.
|
|
174
|
+
// However, logging here is useful as it allows us to complete the life cycle so we know how long a client was connected for.
|
|
175
|
+
console.log(`Disconnected client`, {
|
|
176
|
+
clientId: caller.nodeId,
|
|
177
|
+
});
|
|
178
|
+
});
|
|
162
179
|
}
|
|
163
180
|
}
|
|
164
181
|
|
|
@@ -188,6 +205,7 @@ const changeIdentityOnce = cacheWeak(async function changeIdentityOnce(connectio
|
|
|
188
205
|
cert: threadKeyCert.cert.toString(),
|
|
189
206
|
certIssuer: issuer.cert.toString(),
|
|
190
207
|
mountedPort: getNodeIdLocation(SocketFunction.mountedNodeId)?.port,
|
|
208
|
+
debugEntryPoint: isNode() ? process.argv[1] : "browser",
|
|
191
209
|
};
|
|
192
210
|
let signature = sign(threadKeyCert, payload);
|
|
193
211
|
await timeoutToError(
|
|
@@ -1249,14 +1249,14 @@ class PathWatcher {
|
|
|
1249
1249
|
if (newPathsWatched.size > 0 || newParentsWatched.size > 0) {
|
|
1250
1250
|
if (isOwnNodeId(config.callback)) {
|
|
1251
1251
|
for (let path of newPathsWatched) {
|
|
1252
|
-
auditLog("new local WATCH", { path });
|
|
1252
|
+
auditLog("new local WATCH VALUE", { path });
|
|
1253
1253
|
}
|
|
1254
1254
|
for (let path of newParentsWatched) {
|
|
1255
1255
|
auditLog("new local WATCH PARENT", { path });
|
|
1256
1256
|
}
|
|
1257
1257
|
} else {
|
|
1258
1258
|
for (let path of newPathsWatched) {
|
|
1259
|
-
auditLog("new non-local WATCH", { path, watcher: config.callback });
|
|
1259
|
+
auditLog("new non-local WATCH VALUE", { path, watcher: config.callback });
|
|
1260
1260
|
}
|
|
1261
1261
|
for (let path of newParentsWatched) {
|
|
1262
1262
|
auditLog("new non-local WATCH PARENT", { path, watcher: config.callback });
|
|
@@ -1381,9 +1381,9 @@ class PathWatcher {
|
|
|
1381
1381
|
watchers.watchers.delete(callback);
|
|
1382
1382
|
|
|
1383
1383
|
if (isOwnNodeId(callback)) {
|
|
1384
|
-
auditLog("local UNWATCH", { path });
|
|
1384
|
+
auditLog("local UNWATCH VALUE", { path });
|
|
1385
1385
|
} else {
|
|
1386
|
-
auditLog("non-local UNWATCH", { path, watcher: callback });
|
|
1386
|
+
auditLog("non-local UNWATCH VALUE", { path, watcher: callback });
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
1389
|
if (watchers.watchers.size === 0) {
|
|
@@ -1781,9 +1781,9 @@ class WriteValidStorage {
|
|
|
1781
1781
|
}
|
|
1782
1782
|
public setWriteValidState(write: WriteState, lockless?: boolean): boolean {
|
|
1783
1783
|
if (write.isValid) {
|
|
1784
|
-
auditLog(
|
|
1784
|
+
auditLog("ACCEPTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
|
|
1785
1785
|
} else {
|
|
1786
|
-
auditLog(
|
|
1786
|
+
auditLog("REJECTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
|
|
1787
1787
|
}
|
|
1788
1788
|
this.ensureGarbageCollectOldState();
|
|
1789
1789
|
|
|
@@ -2359,8 +2359,8 @@ export const lockWatchDeduper = new LockWatchDeduper();
|
|
|
2359
2359
|
// can trigger the import of memoryValueAudit for all clients as consistently
|
|
2360
2360
|
// as the core.
|
|
2361
2361
|
setImmediate(() => {
|
|
2362
|
-
logErrors(import("../5-diagnostics/memoryValueAudit").then(x => x.startMemoryAuditLoop()));
|
|
2363
|
-
logErrors(import("../5-diagnostics/diskValueAudit").then(x => x.startDiskAuditLoop()));
|
|
2362
|
+
//logErrors(import("../5-diagnostics/memoryValueAudit").then(x => x.startMemoryAuditLoop()));
|
|
2363
|
+
//logErrors(import("../5-diagnostics/diskValueAudit").then(x => x.startDiskAuditLoop()));
|
|
2364
2364
|
logErrors(import("../5-diagnostics/synchronousLagTracking").then(x => x.trackSynchronousLag()));
|
|
2365
2365
|
});
|
|
2366
2366
|
|
|
@@ -30,13 +30,12 @@ import { readProductionLogsURL, searchTextURL } from "./LogViewerParams";
|
|
|
30
30
|
import { RenderSearchStats } from "./RenderSearchStats";
|
|
31
31
|
import { LifeCyclesController, LifeCycle, LifeCycleEntry } from "../lifeCycleAnalysis/lifeCycles";
|
|
32
32
|
import { getLifecycleMatchesForDatum } from "../lifeCycleAnalysis/lifeCycleMatching";
|
|
33
|
-
import { lifecycleIdURL } from "../lifeCycleAnalysis/LifeCyclePage";
|
|
33
|
+
import { lifecycleIdURL, additionalSearchURL } from "../lifeCycleAnalysis/LifeCyclePage";
|
|
34
34
|
import { ATag } from "../../../library-components/ATag";
|
|
35
35
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
36
36
|
import { managementPageURL } from "../../managementPages";
|
|
37
37
|
import { startTimeParam, endTimeParam } from "../TimeRangeSelector";
|
|
38
38
|
import { formatSearchString } from "./LogViewerParams";
|
|
39
|
-
import { additionalSearchURL } from "../lifeCycleAnalysis/lifeCycleSearch";
|
|
40
39
|
|
|
41
40
|
let excludePendingResults = new URLParam("excludePendingResults", false);
|
|
42
41
|
let limitURL = new URLParam("limit", 100);
|
|
@@ -564,7 +563,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
564
563
|
startTimeParam.getOverride(datum.time - timeInHour),
|
|
565
564
|
endTimeParam.getOverride(datum.time + timeInHour),
|
|
566
565
|
]}>
|
|
567
|
-
{match.lifecycle.title}
|
|
566
|
+
View {JSON.stringify(match.lifecycle.title)}
|
|
568
567
|
</ATag>
|
|
569
568
|
</div>
|
|
570
569
|
))}
|
|
@@ -576,7 +575,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
576
575
|
endTimeParam.getOverride(maxTime + timeInHour),
|
|
577
576
|
]}>
|
|
578
577
|
<Button hue={280} className={css.boldStyle}>
|
|
579
|
-
View All
|
|
578
|
+
View All
|
|
580
579
|
</Button>
|
|
581
580
|
</ATag>
|
|
582
581
|
</div>;
|
|
@@ -9,6 +9,7 @@ const SEARCH_OR_CHAR = "|";
|
|
|
9
9
|
const SEARCH_AND_BYTE = 38;
|
|
10
10
|
const SEARCH_AND_CHAR = "&";
|
|
11
11
|
|
|
12
|
+
// IMPORTANT! While technically THIS structure can support negation, the index structure above us cannot, as it only stores information at the block level (multiple buffers per block). So... if we supported negation, it couldn't, so it would give us a lot of false positives (cases where negation removes the results). So... never use negation, just turn everything into a positive signal.
|
|
12
13
|
export type MatchStructure = {
|
|
13
14
|
type: "or";
|
|
14
15
|
parts: MatchStructure[];
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { deepCloneJSON } from "socket-function/src/misc";
|
|
3
|
+
import { css } from "typesafecss";
|
|
4
|
+
import { t } from "../../../2-proxy/schema2";
|
|
5
|
+
import { qreact } from "../../../4-dom/qreact";
|
|
6
|
+
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
7
|
+
import { Button } from "../../../library-components/Button";
|
|
8
|
+
import { InputLabel } from "../../../library-components/InputLabel";
|
|
9
|
+
import { LogDatum } from "../diskLogger";
|
|
10
|
+
import { LifeCycleEntryReadMode } from "./LifeCycleEntryReadMode";
|
|
11
|
+
import { LifeCycle, LifeCycleEntry, LifeCyclesController } from "./lifeCycles";
|
|
12
|
+
|
|
13
|
+
export class LifeCycleEntryEditor extends qreact.Component<{
|
|
14
|
+
lifeCycle: LifeCycle;
|
|
15
|
+
entry: LifeCycleEntry;
|
|
16
|
+
entryIndex: number;
|
|
17
|
+
defaultEditMode?: boolean;
|
|
18
|
+
datum?: LogDatum;
|
|
19
|
+
}> {
|
|
20
|
+
state = t.state({
|
|
21
|
+
editMode: t.atomic<boolean>(false),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
controller = LifeCyclesController(SocketFunction.browserNodeId());
|
|
25
|
+
|
|
26
|
+
componentDidMount() {
|
|
27
|
+
this.state.editMode = this.props.defaultEditMode || false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
let { lifeCycle, entry, entryIndex } = this.props;
|
|
32
|
+
let isConfigured = entry.groupByKeys && entry.groupByKeys.length > 0 && entry.groupByKeys.every(k => k.ourKey.trim() !== "");
|
|
33
|
+
let bgClass = isConfigured && css.hsl(150, 30, 95) || css.hsl(30, 50, 90);
|
|
34
|
+
|
|
35
|
+
return <div className={
|
|
36
|
+
css.pad2(4).bord2(150, 30, 70)
|
|
37
|
+
.vbox(4)
|
|
38
|
+
.cursor("auto")
|
|
39
|
+
+ bgClass
|
|
40
|
+
+ " LifeCycleEntryEditor"
|
|
41
|
+
}>
|
|
42
|
+
<div className={css.hbox(12)}>
|
|
43
|
+
<span className={css.boldStyle}>{entry.description || entry.matchPattern}</span>
|
|
44
|
+
{entry.description && <span className={css.colorhsl(0, 0, 50)}>({entry.matchPattern})</span>}
|
|
45
|
+
<span className={css.colorhsl(220, 70, 50)}>[{entry.sourceType}]</span>
|
|
46
|
+
{entry.isStart && <span className={css.colorhsl(120, 80, 30).boldStyle}>START</span>}
|
|
47
|
+
{entry.isEnd && <span className={css.colorhsl(0, 80, 30).boldStyle}>END</span>}
|
|
48
|
+
{!isConfigured && !this.state.editMode && <span className={css.colorhsl(30, 80, 40).boldStyle}>(not fully configured)</span>}
|
|
49
|
+
<Button
|
|
50
|
+
hue={150}
|
|
51
|
+
onClick={() => {
|
|
52
|
+
this.state.editMode = !this.state.editMode;
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{this.state.editMode && "Read Mode" || "Edit Mode"}
|
|
56
|
+
</Button>
|
|
57
|
+
<Button
|
|
58
|
+
hue={0}
|
|
59
|
+
onClick={() => {
|
|
60
|
+
if (!confirm("Delete this entry?")) return;
|
|
61
|
+
|
|
62
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
63
|
+
updatedLifeCycle.entries.splice(entryIndex, 1);
|
|
64
|
+
Querysub.onCommitFinished(async () => {
|
|
65
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
66
|
+
});
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
Delete
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{this.state.editMode && (
|
|
74
|
+
<div className={css.vbox(12)}>
|
|
75
|
+
<div className={css.hbox(10)}>
|
|
76
|
+
<InputLabel
|
|
77
|
+
label="Match Pattern"
|
|
78
|
+
value={entry.matchPattern}
|
|
79
|
+
onChangeValue={(value) => {
|
|
80
|
+
let matchPattern = value.trim();
|
|
81
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
82
|
+
updatedLifeCycle.entries[entryIndex].matchPattern = matchPattern;
|
|
83
|
+
Querysub.onCommitFinished(async () => {
|
|
84
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
85
|
+
});
|
|
86
|
+
}}
|
|
87
|
+
className={css.width(500)}
|
|
88
|
+
/>
|
|
89
|
+
<InputLabel
|
|
90
|
+
label="Description"
|
|
91
|
+
value={entry.description || ""}
|
|
92
|
+
onChangeValue={(value) => {
|
|
93
|
+
let description = value.trim() || undefined;
|
|
94
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
95
|
+
updatedLifeCycle.entries[entryIndex].description = description;
|
|
96
|
+
Querysub.onCommitFinished(async () => {
|
|
97
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
98
|
+
});
|
|
99
|
+
}}
|
|
100
|
+
className={css.width(500)}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className={css.hbox(12)}>
|
|
105
|
+
<div className={css.vbox(4)}>
|
|
106
|
+
<span>Source Type</span>
|
|
107
|
+
<select
|
|
108
|
+
value={entry.sourceType}
|
|
109
|
+
onChange={(e) => {
|
|
110
|
+
let sourceType = e.currentTarget.value as "log" | "error" | "info" | "warning";
|
|
111
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
112
|
+
updatedLifeCycle.entries[entryIndex].sourceType = sourceType;
|
|
113
|
+
Querysub.onCommitFinished(async () => {
|
|
114
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
115
|
+
});
|
|
116
|
+
}}
|
|
117
|
+
className={css.pad2(4, 2)}
|
|
118
|
+
>
|
|
119
|
+
<option value="log">log</option>
|
|
120
|
+
<option value="info">info</option>
|
|
121
|
+
<option value="warning">warning</option>
|
|
122
|
+
<option value="error">error</option>
|
|
123
|
+
</select>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div className={css.hbox(12)}>
|
|
128
|
+
<InputLabel
|
|
129
|
+
label="Is Start"
|
|
130
|
+
checkbox
|
|
131
|
+
checked={entry.isStart}
|
|
132
|
+
onChange={(e) => {
|
|
133
|
+
let isStart = e.currentTarget.checked || undefined;
|
|
134
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
135
|
+
updatedLifeCycle.entries[entryIndex].isStart = isStart;
|
|
136
|
+
Querysub.onCommitFinished(async () => {
|
|
137
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
138
|
+
});
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
<InputLabel
|
|
142
|
+
label="Is End"
|
|
143
|
+
checkbox
|
|
144
|
+
checked={entry.isEnd}
|
|
145
|
+
onChange={(e) => {
|
|
146
|
+
let isEnd = e.currentTarget.checked || undefined;
|
|
147
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
148
|
+
updatedLifeCycle.entries[entryIndex].isEnd = isEnd;
|
|
149
|
+
Querysub.onCommitFinished(async () => {
|
|
150
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
151
|
+
});
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className={css.vbox(8)}>
|
|
157
|
+
<div className={!isConfigured && css.colorhsl(30, 80, 40).boldStyle || ""}>
|
|
158
|
+
Group By Keys{!isConfigured && " (at least one required)" || ""}
|
|
159
|
+
</div>
|
|
160
|
+
{Array.isArray(entry.groupByKeys) && entry.groupByKeys.map((keyConfig, keyIdx) => {
|
|
161
|
+
let keyExistsInAllOtherEntries = lifeCycle.entries.every((e, idx) => {
|
|
162
|
+
if (idx === entryIndex) return true;
|
|
163
|
+
return e.groupByKeys && e.groupByKeys.some(k => k.ourKey === keyConfig.ourKey);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return <div key={keyIdx} className={css.hbox(8).pad2(4).bord2(200, 30, 80).hsl(200, 30, 97)}>
|
|
167
|
+
<Button
|
|
168
|
+
hue={160}
|
|
169
|
+
disabled={keyExistsInAllOtherEntries}
|
|
170
|
+
onClick={() => {
|
|
171
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
172
|
+
updatedLifeCycle.entries.forEach((e, idx) => {
|
|
173
|
+
if (idx === entryIndex) return;
|
|
174
|
+
if (!e.groupByKeys.some(k => k.ourKey === keyConfig.ourKey)) {
|
|
175
|
+
e.groupByKeys.push({ ourKey: keyConfig.ourKey });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
Querysub.onCommitFinished(async () => {
|
|
179
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
180
|
+
});
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
Copy to All
|
|
184
|
+
</Button>
|
|
185
|
+
<InputLabel
|
|
186
|
+
label="Key"
|
|
187
|
+
value={keyConfig.ourKey}
|
|
188
|
+
onChangeValue={(value) => {
|
|
189
|
+
let ourKey = value.trim();
|
|
190
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
191
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys[keyIdx].ourKey = ourKey;
|
|
192
|
+
Querysub.onCommitFinished(async () => {
|
|
193
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
194
|
+
});
|
|
195
|
+
}}
|
|
196
|
+
className={css.width(250)}
|
|
197
|
+
/>
|
|
198
|
+
<InputLabel
|
|
199
|
+
label="Different key in start entry (not usually needed)"
|
|
200
|
+
value={keyConfig.startKey || ""}
|
|
201
|
+
onChangeValue={(value) => {
|
|
202
|
+
let startKey = value.trim() || undefined;
|
|
203
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
204
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys[keyIdx].startKey = startKey;
|
|
205
|
+
Querysub.onCommitFinished(async () => {
|
|
206
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
207
|
+
});
|
|
208
|
+
}}
|
|
209
|
+
className={css.width(250)}
|
|
210
|
+
/>
|
|
211
|
+
<InputLabel
|
|
212
|
+
label="Aliases (comma-separated)"
|
|
213
|
+
value={keyConfig.aliases && keyConfig.aliases.join(", ") || ""}
|
|
214
|
+
onChangeValue={(value) => {
|
|
215
|
+
let aliases = value.trim() && value.split(",").map(a => a.trim()).filter(a => a) || undefined;
|
|
216
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
217
|
+
updatedLifeCycle.entries.forEach((e) => {
|
|
218
|
+
let matchingKey = e.groupByKeys.find(k => k.ourKey === keyConfig.ourKey);
|
|
219
|
+
if (matchingKey) {
|
|
220
|
+
matchingKey.aliases = aliases;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
Querysub.onCommitFinished(async () => {
|
|
224
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
225
|
+
});
|
|
226
|
+
}}
|
|
227
|
+
className={css.width(250)}
|
|
228
|
+
/>
|
|
229
|
+
<Button
|
|
230
|
+
hue={0}
|
|
231
|
+
onClick={() => {
|
|
232
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
233
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys.splice(keyIdx, 1);
|
|
234
|
+
Querysub.onCommitFinished(async () => {
|
|
235
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
236
|
+
});
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
Delete
|
|
240
|
+
</Button>
|
|
241
|
+
</div>;
|
|
242
|
+
})}
|
|
243
|
+
<InputLabel
|
|
244
|
+
label="Add new key"
|
|
245
|
+
onChangeValue={(value) => {
|
|
246
|
+
let ourKey = value.trim();
|
|
247
|
+
if (ourKey) {
|
|
248
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
249
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys.push({ ourKey });
|
|
250
|
+
Querysub.onCommitFinished(async () => {
|
|
251
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
className={css.width(250)}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div className={css.vbox(4)}>
|
|
260
|
+
{Object.entries(entry.variables).map(([key, varData]) => {
|
|
261
|
+
let keyExistsInAllOtherEntries = lifeCycle.entries.every((e, idx) => {
|
|
262
|
+
if (idx === entryIndex) return true;
|
|
263
|
+
return key in e.variables;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return <div key={key} className={css.hbox(8).pad2(4).bord2(0, 0, 80).hsl(0, 0, 97)}>
|
|
267
|
+
<Button
|
|
268
|
+
hue={160}
|
|
269
|
+
disabled={keyExistsInAllOtherEntries}
|
|
270
|
+
onClick={() => {
|
|
271
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
272
|
+
updatedLifeCycle.entries.forEach((e, idx) => {
|
|
273
|
+
if (idx === entryIndex) return;
|
|
274
|
+
if (!(key in e.variables)) {
|
|
275
|
+
e.variables[key] = {};
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
Querysub.onCommitFinished(async () => {
|
|
279
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
280
|
+
});
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
Copy to All
|
|
284
|
+
</Button>
|
|
285
|
+
<span className={css.minWidth(150).boldStyle}>{key}</span>
|
|
286
|
+
<InputLabel
|
|
287
|
+
placeholder="Variable title"
|
|
288
|
+
value={varData.title || ""}
|
|
289
|
+
onChangeValue={(value) => {
|
|
290
|
+
let title = value.trim() || undefined;
|
|
291
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
292
|
+
updatedLifeCycle.entries[entryIndex].variables[key].title = title;
|
|
293
|
+
Querysub.onCommitFinished(async () => {
|
|
294
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
295
|
+
});
|
|
296
|
+
}}
|
|
297
|
+
className={css.width(300)}
|
|
298
|
+
/>
|
|
299
|
+
<InputLabel
|
|
300
|
+
placeholder="Aliases (comma-separated)"
|
|
301
|
+
value={varData.aliases && varData.aliases.join(", ") || ""}
|
|
302
|
+
onChangeValue={(value) => {
|
|
303
|
+
let aliases = value.trim() && value.split(",").map(a => a.trim()).filter(a => a) || undefined;
|
|
304
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
305
|
+
updatedLifeCycle.entries.forEach((e) => {
|
|
306
|
+
if (key in e.variables) {
|
|
307
|
+
e.variables[key].aliases = aliases;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
Querysub.onCommitFinished(async () => {
|
|
311
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
312
|
+
});
|
|
313
|
+
}}
|
|
314
|
+
className={css.width(300)}
|
|
315
|
+
/>
|
|
316
|
+
<Button
|
|
317
|
+
hue={0}
|
|
318
|
+
onClick={() => {
|
|
319
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
320
|
+
delete updatedLifeCycle.entries[entryIndex].variables[key];
|
|
321
|
+
Querysub.onCommitFinished(async () => {
|
|
322
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
323
|
+
});
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
Delete
|
|
327
|
+
</Button>
|
|
328
|
+
</div>;
|
|
329
|
+
})}
|
|
330
|
+
|
|
331
|
+
<InputLabel
|
|
332
|
+
label="Add new variable key"
|
|
333
|
+
onChangeValue={(value) => {
|
|
334
|
+
let key = value.trim();
|
|
335
|
+
if (key) {
|
|
336
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
337
|
+
updatedLifeCycle.entries[entryIndex].variables[key] = {};
|
|
338
|
+
Querysub.onCommitFinished(async () => {
|
|
339
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}}
|
|
343
|
+
className={css.width(300)}
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
{!this.state.editMode && <LifeCycleEntryReadMode
|
|
350
|
+
lifeCycle={lifeCycle}
|
|
351
|
+
entry={entry}
|
|
352
|
+
entryIndex={entryIndex}
|
|
353
|
+
datum={this.props.datum}
|
|
354
|
+
/>}
|
|
355
|
+
</div>;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { deepCloneJSON } from "socket-function/src/misc";
|
|
3
|
+
import { css } from "typesafecss";
|
|
4
|
+
import { t } from "../../../2-proxy/schema2";
|
|
5
|
+
import { qreact } from "../../../4-dom/qreact";
|
|
6
|
+
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
7
|
+
import { Button } from "../../../library-components/Button";
|
|
8
|
+
import { niceStringify } from "../../../niceStringify";
|
|
9
|
+
import { LogDatum } from "../diskLogger";
|
|
10
|
+
import { LifeCycle, LifeCycleEntry, LifeCyclesController, getVariables } from "./lifeCycles";
|
|
11
|
+
import { NestedLifeCycleInfo } from "./NestedLifeCycleInfo";
|
|
12
|
+
|
|
13
|
+
export class LifeCycleEntryReadMode extends qreact.Component<{
|
|
14
|
+
lifeCycle: LifeCycle;
|
|
15
|
+
entry: LifeCycleEntry;
|
|
16
|
+
entryIndex: number;
|
|
17
|
+
datum: LogDatum | undefined;
|
|
18
|
+
}> {
|
|
19
|
+
state = t.state({
|
|
20
|
+
showAllVariables: t.atomic<boolean>(false),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
controller = LifeCyclesController(SocketFunction.browserNodeId());
|
|
24
|
+
|
|
25
|
+
render() {
|
|
26
|
+
let { lifeCycle, entry, entryIndex } = this.props;
|
|
27
|
+
const datum = this.props.datum;
|
|
28
|
+
|
|
29
|
+
let variables = getVariables(entry);
|
|
30
|
+
|
|
31
|
+
let renderEditUI = (key: string) => {
|
|
32
|
+
let updateLifeCycle = (fnc: (updatedLifeCycle: LifeCycle) => void) => {
|
|
33
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
34
|
+
fnc(updatedLifeCycle);
|
|
35
|
+
Querysub.onCommitFinished(async () => {
|
|
36
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let isVariable = key in entry.variables;
|
|
41
|
+
let isGroupByKey = Array.isArray(entry.groupByKeys) && entry.groupByKeys.some(k => k.ourKey === key);
|
|
42
|
+
|
|
43
|
+
return <>
|
|
44
|
+
<span
|
|
45
|
+
className={css.button.pad2(4).bord2(0, 0, 60).hsl(220, 50, 60) + (!isGroupByKey && css.opacity(0.4))}
|
|
46
|
+
onClick={() => {
|
|
47
|
+
updateLifeCycle((updated) => {
|
|
48
|
+
if (isGroupByKey) {
|
|
49
|
+
updated.entries.forEach((e) => {
|
|
50
|
+
e.groupByKeys = e.groupByKeys.filter(k => k.ourKey !== key);
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
updated.entries.forEach((e) => {
|
|
54
|
+
if (!e.groupByKeys.some(k => k.ourKey === key)) {
|
|
55
|
+
e.groupByKeys.push({ ourKey: key });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}}
|
|
61
|
+
title={isGroupByKey && "Remove group by key from all entries" || "Add group by key to all entries"}
|
|
62
|
+
>
|
|
63
|
+
🔑
|
|
64
|
+
</span>
|
|
65
|
+
<span
|
|
66
|
+
className={css.button.pad2(4).bord2(0, 0, 60).hsl(120, 50, 60) + (!isVariable && css.opacity(0.4))}
|
|
67
|
+
onClick={() => {
|
|
68
|
+
updateLifeCycle((updated) => {
|
|
69
|
+
if (isVariable) {
|
|
70
|
+
updated.entries.forEach((e) => {
|
|
71
|
+
delete e.variables[key];
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
updated.entries.forEach((e) => {
|
|
75
|
+
if (!(key in e.variables)) {
|
|
76
|
+
e.variables[key] = {};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}}
|
|
82
|
+
title={isVariable && "Remove variable from all entries" || "Add variable to all entries"}
|
|
83
|
+
>
|
|
84
|
+
📌
|
|
85
|
+
</span>
|
|
86
|
+
</>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let renderNestedInfo = (key: string, value: unknown) => {
|
|
90
|
+
if (value === undefined) return null;
|
|
91
|
+
|
|
92
|
+
let isGroupByKey = Array.isArray(entry.groupByKeys) && entry.groupByKeys.some(k => k.ourKey === key);
|
|
93
|
+
let aliases = isGroupByKey && entry.groupByKeys.find(k => k.ourKey === key)?.aliases || entry.variables[key]?.aliases || [];
|
|
94
|
+
|
|
95
|
+
return <NestedLifeCycleInfo
|
|
96
|
+
keyName={key}
|
|
97
|
+
value={value}
|
|
98
|
+
aliases={aliases}
|
|
99
|
+
currentLifeCycleId={lifeCycle.id}
|
|
100
|
+
/>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return <div className={css.vbox(8)}>
|
|
104
|
+
{Array.isArray(entry.groupByKeys) && entry.groupByKeys.some(k => k.startKey) && (
|
|
105
|
+
<div className={css.vbox(2)}>
|
|
106
|
+
{entry.groupByKeys.filter(k => k.startKey).map((k, idx) => (
|
|
107
|
+
<div key={idx}>{k.ourKey} start override: {k.startKey}</div>
|
|
108
|
+
))}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
{datum && <div className={css.vbox(4)}>
|
|
112
|
+
{variables.map(({ key, title }) => {
|
|
113
|
+
let value = datum[key];
|
|
114
|
+
return <div key={key} className={css.hbox(8).wrap}>
|
|
115
|
+
{renderEditUI(key)}
|
|
116
|
+
<span className={css.minWidth(150).colorhsl(0, 0, 50)}>
|
|
117
|
+
{title || key}{title && ` (${key})` || ""}:
|
|
118
|
+
</span>
|
|
119
|
+
<span className={css.whiteSpace("pre-wrap")}>{niceStringify(value)}</span>
|
|
120
|
+
{renderNestedInfo(key, value)}
|
|
121
|
+
</div>;
|
|
122
|
+
})}
|
|
123
|
+
{this.state.showAllVariables && (() => {
|
|
124
|
+
let configuredKeys = new Set(getVariables(entry).map(v => v.key));
|
|
125
|
+
let allKeys = Object.keys(datum).filter(key => !configuredKeys.has(key));
|
|
126
|
+
return allKeys.map((key) => {
|
|
127
|
+
let value = datum[key];
|
|
128
|
+
return <div key={key} className={css.hbox(8).wrap}>
|
|
129
|
+
{renderEditUI(key)}
|
|
130
|
+
<span className={css.minWidth(150).colorhsl(0, 0, 50)}>
|
|
131
|
+
{key}
|
|
132
|
+
</span>
|
|
133
|
+
<span className={css.whiteSpace("pre-wrap")}>{niceStringify(value)}</span>
|
|
134
|
+
{renderNestedInfo(key, value)}
|
|
135
|
+
</div>;
|
|
136
|
+
});
|
|
137
|
+
})()}
|
|
138
|
+
<Button
|
|
139
|
+
hue={220}
|
|
140
|
+
onClick={() => {
|
|
141
|
+
this.state.showAllVariables = !this.state.showAllVariables;
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{this.state.showAllVariables && "Hide Additional Variables" || "Show All Variables"}
|
|
145
|
+
</Button>
|
|
146
|
+
</div>}
|
|
147
|
+
</div>;
|
|
148
|
+
}
|
|
149
|
+
}
|