querysub 0.454.0 → 0.456.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/-a-archives/archivesBackBlaze.ts +2 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +40 -7
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +2 -0
- package/src/5-diagnostics/Table.tsx +97 -67
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +5 -2
package/package.json
CHANGED
|
@@ -585,8 +585,8 @@ export class ArchivesBackblaze {
|
|
|
585
585
|
console.error(`[${context}] getaddrinfo ENOTFOUND ${hostname}`, { lookupAddresses, resolveAddresses, apiUrl: api.apiUrl, fullError: err.stack });
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
-
//
|
|
589
|
-
|
|
588
|
+
// NOTE: The AI thought case that happens when we run out of retries, that's stupid. This obviously isn't the case. This is the case when it's a normal error, as in the file doesn't exist, we need to throw. We absolutely should not warn here. Warning here wouldn't be anything. It would just be saying, oh, we checked if a file and it didn't, which is normal, which is why we check if a file exists.
|
|
589
|
+
|
|
590
590
|
throw err;
|
|
591
591
|
}
|
|
592
592
|
}
|
|
@@ -418,16 +418,27 @@ async function fastMemorySync() {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
let checkNodes = shuffle(Array.from(aliveNodes), Date.now()).slice(0, API_AUDIT_COUNT);
|
|
421
|
-
let
|
|
422
|
-
checkNodes.map(async
|
|
423
|
-
let nodes = await timeoutToUndefinedSilent(200, NodeDiscoveryController.nodes[
|
|
421
|
+
let perPeerNodes = await Promise.all(
|
|
422
|
+
checkNodes.map(async peerNodeId => {
|
|
423
|
+
let nodes = await timeoutToUndefinedSilent(200, NodeDiscoveryController.nodes[peerNodeId].getAllNodeIds());
|
|
424
424
|
if (!nodes) {
|
|
425
|
-
deadNodes.set(
|
|
425
|
+
deadNodes.set(peerNodeId, Date.now());
|
|
426
426
|
}
|
|
427
|
-
return nodes || [];
|
|
427
|
+
return { peerNodeId, nodes: nodes || [] };
|
|
428
428
|
})
|
|
429
429
|
);
|
|
430
|
-
let
|
|
430
|
+
let reportedBy = new Map<string, string[]>();
|
|
431
|
+
for (let { peerNodeId, nodes } of perPeerNodes) {
|
|
432
|
+
for (let nodeId of nodes) {
|
|
433
|
+
let reporters = reportedBy.get(nodeId);
|
|
434
|
+
if (!reporters) {
|
|
435
|
+
reporters = [];
|
|
436
|
+
reportedBy.set(nodeId, reporters);
|
|
437
|
+
}
|
|
438
|
+
reporters.push(peerNodeId);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
let otherNodes = Array.from(reportedBy.keys());
|
|
431
442
|
// This would log WAY too much, because we poll a lot, because we want to minimize downtime
|
|
432
443
|
//console.log(magenta(`Fast memory sync at ${formatVeryNiceDateTime(Date.now())}, nodes found ${otherNodes.length}`), getDebuggerUrl());
|
|
433
444
|
|
|
@@ -436,8 +447,30 @@ async function fastMemorySync() {
|
|
|
436
447
|
// sync now.
|
|
437
448
|
let missingNodes = otherNodes.filter(nodeId => !allNodeIds2.has(nodeId));
|
|
438
449
|
if (missingNodes.length > 0) {
|
|
439
|
-
|
|
450
|
+
let missingNodeReporters = Object.fromEntries(
|
|
451
|
+
missingNodes.map(nodeId => [nodeId, reportedBy.get(nodeId) ?? []])
|
|
452
|
+
);
|
|
453
|
+
console.log(yellow(`Node list is missing nodes, resyncing node`), { missingNodes, missingNodeReporters, otherNodes });
|
|
440
454
|
await syncArchives();
|
|
455
|
+
|
|
456
|
+
// If after syncing our own archive view those nodes are STILL missing, then they are
|
|
457
|
+
// ghosts in the peer's in-memory allNodeIds2 — push a targeted resync to each peer that
|
|
458
|
+
// told us about a ghost, so they can drop it from their own allNodeIds2.
|
|
459
|
+
let stillMissing = missingNodes.filter(nodeId => !allNodeIds2.has(nodeId));
|
|
460
|
+
if (stillMissing.length > 0) {
|
|
461
|
+
let peersToResync = new Set<string>();
|
|
462
|
+
for (let nodeId of stillMissing) {
|
|
463
|
+
for (let peerNodeId of reportedBy.get(nodeId) ?? []) {
|
|
464
|
+
if (isOwnNodeId(peerNodeId)) continue;
|
|
465
|
+
peersToResync.add(peerNodeId);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
let reason = `ghost-resync: peer reported nodes not in our archive after sync: ${stillMissing.join("|")}`;
|
|
469
|
+
console.log(yellow(`Pushing targeted resync to ${peersToResync.size} peers`), { stillMissing, peers: Array.from(peersToResync) });
|
|
470
|
+
for (let peerNodeId of peersToResync) {
|
|
471
|
+
ignoreErrors(NodeDiscoveryController.nodes[peerNodeId].resyncNodes(reason));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
441
474
|
}
|
|
442
475
|
}
|
|
443
476
|
|
|
@@ -11,6 +11,8 @@ import { decodeCborx, encodeCborx } from "../../misc/cloneHelpers";
|
|
|
11
11
|
import { runInParallel } from "socket-function/src/batching";
|
|
12
12
|
import { formatNumber } from "socket-function/src/formatting/format";
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
// Snapshot is just a newline delimitted list of files. The name contains the metadata and time of it.
|
|
14
16
|
const snapshots = lazy(() => getArchives("snapshots"));
|
|
15
17
|
|
|
16
18
|
export type ArchiveSnapshotOverview = {
|
|
@@ -5,6 +5,7 @@ import preact from "preact";
|
|
|
5
5
|
import { showModal } from "./Modal";
|
|
6
6
|
import { FullscreenModal } from "./FullscreenModal";
|
|
7
7
|
import { canHaveChildren } from "socket-function/src/types";
|
|
8
|
+
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
8
9
|
|
|
9
10
|
// Undefined means we infer the column
|
|
10
11
|
// Null means the column is removed
|
|
@@ -24,6 +25,13 @@ export type TableType<RowT extends RowType = RowType> = {
|
|
|
24
25
|
|
|
25
26
|
module.hotreload = true;
|
|
26
27
|
|
|
28
|
+
const SHALLOW_CACHE_MAX_SIZE = 100_000;
|
|
29
|
+
type ShallowCacheEntry = {
|
|
30
|
+
columns: ColumnsType;
|
|
31
|
+
cells: preact.ComponentChild[];
|
|
32
|
+
};
|
|
33
|
+
const shallowCacheMap = new Map<RowType, ShallowCacheEntry>();
|
|
34
|
+
|
|
27
35
|
export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT> & {
|
|
28
36
|
class?: string;
|
|
29
37
|
cellClass?: string;
|
|
@@ -34,6 +42,10 @@ export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT
|
|
|
34
42
|
characterLimit?: number;
|
|
35
43
|
dark?: boolean;
|
|
36
44
|
|
|
45
|
+
// If true, cache the rendered cells per row (keyed by row reference + columns reference).
|
|
46
|
+
// Resets the cache wholesale once it exceeds SHALLOW_CACHE_MAX_SIZE.
|
|
47
|
+
shallowCache?: boolean;
|
|
48
|
+
|
|
37
49
|
getRowAttributes?: (row: RowT, index: number) => preact.JSX.HTMLAttributes<HTMLTableRowElement>;
|
|
38
50
|
}> {
|
|
39
51
|
state = {
|
|
@@ -60,75 +72,93 @@ export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT
|
|
|
60
72
|
<th class={css.pad2(8, 4) + cellClass}>{column?.title || toSpaceCase(columnName)}</th>
|
|
61
73
|
)}
|
|
62
74
|
</tr>
|
|
63
|
-
{rows.map((row, index) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
{rows.map((row, index) => {
|
|
76
|
+
const renderCells = () => Object.entries(columns).filter(x => x[1] !== null).map(([columnName, column]) => {
|
|
77
|
+
let value = row[columnName];
|
|
78
|
+
let formatter = column?.formatter || "guess";
|
|
79
|
+
let result = measureBlock(() => formatValue(value, formatter, { row, columnName }), `formatValue|${columnName}`);
|
|
80
|
+
let renderedObj = renderTrimmed({
|
|
81
|
+
content: result,
|
|
82
|
+
lineLimit,
|
|
83
|
+
characterLimit,
|
|
84
|
+
});
|
|
85
|
+
let attributes = { ...renderedObj.outerAttributes };
|
|
86
|
+
attributes.class = attributes.class || "";
|
|
87
|
+
attributes.class += " " + css.whiteSpace("pre-wrap").pad2(8, 4);
|
|
88
|
+
attributes.class += cellClass;
|
|
89
|
+
// If the inner content looks like a VNode, take it's attributes and unwrap it,
|
|
90
|
+
// so it can fill the entire cell.
|
|
91
|
+
let innerContent = renderedObj.innerContent;
|
|
92
|
+
if (
|
|
93
|
+
canHaveChildren(innerContent) && "props" in innerContent
|
|
94
|
+
&& canHaveChildren(innerContent.props)
|
|
95
|
+
// Table can't show gap, so don;t collapse if it has gap.
|
|
96
|
+
&& !((innerContent.props?.class as string + " " + innerContent.props?.className as string).includes("gap"))
|
|
97
|
+
// NOTE: I'm not sure why we were only elevating the children if we only had 1 child?
|
|
98
|
+
// Changing this will break things, but it should be better overall.
|
|
99
|
+
// && "children" in innerContent.props
|
|
100
|
+
// && (
|
|
101
|
+
// Array.isArray(innerContent.props.children) && innerContent.props.children.length === 1
|
|
102
|
+
// || !Array.isArray(innerContent.props.children)
|
|
103
|
+
// )
|
|
104
|
+
// AND, it is a div (a tags shouldn't be unwrapped)
|
|
105
|
+
&& (innerContent.type === "div")
|
|
106
|
+
) {
|
|
107
|
+
attributes.class += " " + (innerContent.props.class || "");
|
|
108
|
+
attributes.class += " " + (innerContent.props.className || "");
|
|
109
|
+
let baseOnClick = attributes.onClick;
|
|
110
|
+
let props = innerContent.props;
|
|
111
|
+
attributes.onClick = (e) => {
|
|
112
|
+
if (baseOnClick) baseOnClick(e);
|
|
113
|
+
(props as any).onClick?.(e);
|
|
114
|
+
};
|
|
115
|
+
for (let key in props) {
|
|
116
|
+
if (key === "class") continue;
|
|
117
|
+
if (key === "onClick") continue;
|
|
118
|
+
(attributes as any)[key] = props[key];
|
|
119
|
+
}
|
|
120
|
+
innerContent = props.children as any;
|
|
71
121
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// )
|
|
103
|
-
|
|
104
|
-
&& (innerContent.type === "div")
|
|
105
|
-
) {
|
|
106
|
-
attributes.class += " " + (innerContent.props.class || "");
|
|
107
|
-
attributes.class += " " + (innerContent.props.className || "");
|
|
108
|
-
let baseOnClick = attributes.onClick;
|
|
109
|
-
let props = innerContent.props;
|
|
110
|
-
attributes.onClick = (e) => {
|
|
111
|
-
if (baseOnClick) baseOnClick(e);
|
|
112
|
-
(props as any).onClick?.(e);
|
|
113
|
-
};
|
|
114
|
-
for (let key in props) {
|
|
115
|
-
if (key === "class") continue;
|
|
116
|
-
if (key === "onClick") continue;
|
|
117
|
-
(attributes as any)[key] = props[key];
|
|
118
|
-
}
|
|
119
|
-
innerContent = props.children as any;
|
|
122
|
+
return <td
|
|
123
|
+
{...attributes}
|
|
124
|
+
data-column={columnName}
|
|
125
|
+
// If it ever becomes non-table cell, it breaks the table
|
|
126
|
+
style={{ display: "table-cell" }}
|
|
127
|
+
>
|
|
128
|
+
{innerContent}
|
|
129
|
+
</td>;
|
|
130
|
+
});
|
|
131
|
+
let cells: preact.ComponentChild[];
|
|
132
|
+
if (this.props.shallowCache) {
|
|
133
|
+
const cached = shallowCacheMap.get(row);
|
|
134
|
+
if (cached && cached.columns === columns) {
|
|
135
|
+
cells = cached.cells;
|
|
136
|
+
} else {
|
|
137
|
+
if (shallowCacheMap.size >= SHALLOW_CACHE_MAX_SIZE) {
|
|
138
|
+
shallowCacheMap.clear();
|
|
139
|
+
}
|
|
140
|
+
cells = renderCells();
|
|
141
|
+
shallowCacheMap.set(row, { columns: columns as ColumnsType, cells });
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
cells = renderCells();
|
|
145
|
+
}
|
|
146
|
+
return (
|
|
147
|
+
<tr
|
|
148
|
+
class={
|
|
149
|
+
(index % 2 === 1 && css.hsla(0, 0, 100, 0.25) || "")
|
|
150
|
+
// NOTE: We don't set z-index, so children z-index values can be laid out above our siblings
|
|
151
|
+
// (if we set z-index it will create a new z-index order under us, and our children will never
|
|
152
|
+
// be able to above our siblings, which breaks palettes).
|
|
153
|
+
+ css.relative
|
|
120
154
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</td>;
|
|
129
|
-
})}
|
|
130
|
-
</tr>
|
|
131
|
-
))}
|
|
155
|
+
{...this.props.getRowAttributes?.(row, index)}
|
|
156
|
+
>
|
|
157
|
+
<td class={css.center}>{index + 1}</td>
|
|
158
|
+
{cells}
|
|
159
|
+
</tr>
|
|
160
|
+
);
|
|
161
|
+
})}
|
|
132
162
|
{allRows.length > rows.length && <tr>
|
|
133
163
|
<td
|
|
134
164
|
colSpan={1 + Object.keys(columns).length}
|
|
@@ -47,7 +47,9 @@ let enableErrorsURL = new URLParam("enableErrors", true);
|
|
|
47
47
|
let savedPathsURL = new URLParam("savedPaths", "");
|
|
48
48
|
let selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
|
|
49
49
|
let useRelativeTimeURL = new URLParam("useRelativeTime", true);
|
|
50
|
-
|
|
50
|
+
|
|
51
|
+
// NOTE: Because this doesn't cache properly, it lags a lot, so we shouldn't try to use it.
|
|
52
|
+
let showLifecycleColumnURL = new URLParam("showlifecycle", false);
|
|
51
53
|
|
|
52
54
|
const defaultSelectedFields = {
|
|
53
55
|
param0: true,
|
|
@@ -74,7 +76,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
74
76
|
hasSearched: t.boolean(false),
|
|
75
77
|
forceMoveStartTime: t.atomic<number | undefined>(undefined),
|
|
76
78
|
forceMoveEndTime: t.atomic<number | undefined>(undefined),
|
|
77
|
-
showLifecycleColumn: t.boolean(
|
|
79
|
+
showLifecycleColumn: t.boolean(false),
|
|
78
80
|
});
|
|
79
81
|
|
|
80
82
|
lifecycleController = LifeCyclesController(SocketFunction.browserNodeId());
|
|
@@ -640,6 +642,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
640
642
|
</div>}
|
|
641
643
|
|
|
642
644
|
<Table
|
|
645
|
+
shallowCache
|
|
643
646
|
rows={this.state.results}
|
|
644
647
|
columns={columns}
|
|
645
648
|
lineLimit={4}
|