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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.395.0",
3
+ "version": "0.396.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -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
- console.log(`Authenticated identity for ${caller.nodeId} to ${reconnectNodeId || caller.nodeId} in ${formatTime(Date.now() - time)}, at ${Date.now()}`);
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(write.reason || "ACCEPTING VALUE", { path: write.path, time: write.time.time });
1784
+ auditLog("ACCEPTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
1785
1785
  } else {
1786
- auditLog(write.reason || "REJECTING VALUE", { path: write.path, time: write.time.time });
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 {lifecycleTitle}
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
+ }