querysub 0.357.0 → 0.358.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/.cursorrules +1 -0
- package/package.json +2 -1
- package/src/-a-archives/archivesDisk.ts +13 -6
- package/src/-a-archives/archivesMemoryCache.ts +41 -17
- package/src/deployManager/components/MachineDetailPage.tsx +43 -2
- package/src/deployManager/components/MachinesListPage.tsx +10 -2
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineSchema.ts +39 -0
- package/src/diagnostics/NodeViewer.tsx +2 -1
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +124 -123
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +83 -1
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +2 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +21 -24
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +186 -25
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +231 -144
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +307 -108
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +37 -7
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +51 -33
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -180
- package/src/functional/limitProcessing.ts +39 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { formatDateTime, formatNumber } from "socket-function/src/formatting/format";
|
|
1
|
+
import { formatDate, formatDateTime, formatNumber } from "socket-function/src/formatting/format";
|
|
2
2
|
import { css } from "typesafecss";
|
|
3
3
|
import { t } from "../../../2-proxy/schema2";
|
|
4
4
|
import { qreact } from "../../../4-dom/qreact";
|
|
5
5
|
import { Button } from "../../../library-components/Button";
|
|
6
6
|
import { TimeFilePathWithSize } from "./IndexedLogs";
|
|
7
|
-
import { keyByArray } from "socket-function/src/misc";
|
|
7
|
+
import { keyBy, keyByArray } from "socket-function/src/misc";
|
|
8
8
|
import { MachineThreadInfo } from "../../MachineThreadInfo";
|
|
9
9
|
|
|
10
10
|
export type FilePathsByThread = {
|
|
@@ -12,6 +12,8 @@ export type FilePathsByThread = {
|
|
|
12
12
|
files: TimeFilePathWithSize[];
|
|
13
13
|
totalSize: number;
|
|
14
14
|
totalLogCount: number;
|
|
15
|
+
pendingCount: number;
|
|
16
|
+
errorCount: number;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export type FilePathsByMachine = {
|
|
@@ -19,12 +21,15 @@ export type FilePathsByMachine = {
|
|
|
19
21
|
threads: FilePathsByThread[];
|
|
20
22
|
totalSize: number;
|
|
21
23
|
totalLogCount: number;
|
|
24
|
+
pendingCount: number;
|
|
25
|
+
errorCount: number;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
export class FilePathSelector extends qreact.Component<{
|
|
26
30
|
paths: TimeFilePathWithSize[];
|
|
27
31
|
onChange: (paths: TimeFilePathWithSize[]) => void;
|
|
32
|
+
fileErrors?: { error: string; path: string; }[];
|
|
28
33
|
}> {
|
|
29
34
|
state = t.state({
|
|
30
35
|
showFullScreenModal: t.boolean,
|
|
@@ -32,6 +37,7 @@ export class FilePathSelector extends qreact.Component<{
|
|
|
32
37
|
|
|
33
38
|
getGroupedByMachine(): FilePathsByMachine[] {
|
|
34
39
|
let byMachine = keyByArray(this.props.paths, (path) => path.machineId || "unknown");
|
|
40
|
+
let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
|
|
35
41
|
|
|
36
42
|
let result: FilePathsByMachine[] = [];
|
|
37
43
|
for (let [machineId, files] of byMachine) {
|
|
@@ -40,27 +46,39 @@ export class FilePathSelector extends qreact.Component<{
|
|
|
40
46
|
let threads: FilePathsByThread[] = [];
|
|
41
47
|
let totalSize = 0;
|
|
42
48
|
let totalLogCount = 0;
|
|
49
|
+
let machinePendingCount = 0;
|
|
50
|
+
let machineErrorCount = 0;
|
|
43
51
|
|
|
44
52
|
for (let [threadId, threadFiles] of byThread) {
|
|
45
53
|
let threadSize = 0;
|
|
46
54
|
let threadLogCount = 0;
|
|
55
|
+
let threadPendingCount = 0;
|
|
56
|
+
let threadErrorCount = 0;
|
|
47
57
|
for (let file of threadFiles) {
|
|
48
58
|
threadSize += file.size;
|
|
49
59
|
threadLogCount += file.logCount || 0;
|
|
60
|
+
if (file.logCount === undefined) {
|
|
61
|
+
threadPendingCount++;
|
|
62
|
+
}
|
|
63
|
+
if (errorsByPath.get(file.fullPath)) {
|
|
64
|
+
threadErrorCount++;
|
|
65
|
+
}
|
|
50
66
|
}
|
|
51
|
-
threads.push({ threadId, files: threadFiles, totalSize: threadSize, totalLogCount: threadLogCount });
|
|
67
|
+
threads.push({ threadId, files: threadFiles, totalSize: threadSize, totalLogCount: threadLogCount, pendingCount: threadPendingCount, errorCount: threadErrorCount });
|
|
52
68
|
totalSize += threadSize;
|
|
53
69
|
totalLogCount += threadLogCount;
|
|
70
|
+
machinePendingCount += threadPendingCount;
|
|
71
|
+
machineErrorCount += threadErrorCount;
|
|
54
72
|
}
|
|
55
73
|
|
|
56
|
-
result.push({ machineId, threads, totalSize, totalLogCount });
|
|
74
|
+
result.push({ machineId, threads, totalSize, totalLogCount, pendingCount: machinePendingCount, errorCount: machineErrorCount });
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
return result;
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
getPendingCount(): number {
|
|
63
|
-
return this.props.paths.filter(p => p.
|
|
81
|
+
return this.props.paths.filter(p => p.logCount === undefined).length;
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
getUniqueThreadCount(): number {
|
|
@@ -76,11 +94,28 @@ export class FilePathSelector extends qreact.Component<{
|
|
|
76
94
|
getTotals() {
|
|
77
95
|
let totalSize = 0;
|
|
78
96
|
let totalLogCount = 0;
|
|
97
|
+
let minTime = Infinity;
|
|
98
|
+
let maxTime = -Infinity;
|
|
79
99
|
for (let path of this.props.paths) {
|
|
80
100
|
totalSize += path.size;
|
|
81
101
|
totalLogCount += path.logCount || 0;
|
|
102
|
+
if (path.startTime < minTime) {
|
|
103
|
+
minTime = path.startTime;
|
|
104
|
+
}
|
|
105
|
+
if (path.startTime > maxTime) {
|
|
106
|
+
maxTime = path.startTime;
|
|
107
|
+
}
|
|
82
108
|
}
|
|
83
|
-
|
|
109
|
+
const errorCount = this.props.fileErrors?.length || 0;
|
|
110
|
+
return {
|
|
111
|
+
totalSize,
|
|
112
|
+
totalLogCount,
|
|
113
|
+
totalPending: this.getPendingCount(),
|
|
114
|
+
uniqueThreads: this.getUniqueThreadCount(),
|
|
115
|
+
minTime: minTime === Infinity ? undefined : minTime,
|
|
116
|
+
maxTime: maxTime === -Infinity ? undefined : maxTime,
|
|
117
|
+
errorCount
|
|
118
|
+
};
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
formatBytes(bytes: number): string {
|
|
@@ -94,15 +129,34 @@ export class FilePathSelector extends qreact.Component<{
|
|
|
94
129
|
return (
|
|
95
130
|
<div className={css.vbox(10)}>
|
|
96
131
|
<div
|
|
97
|
-
className={css.hbox(10).pad2(10).bord(1, { h: 0, s: 0, l: 80 }).hsl(240, 20, 95).hslhover(240, 30, 90).cursor("pointer")}
|
|
132
|
+
className={css.hbox(10).pad2(10).bord(1, { h: 0, s: 0, l: 80 }).hsl(240, 20, 95).hslhover(240, 30, 90).cursor("pointer").wrap}
|
|
98
133
|
onClick={() => this.state.showFullScreenModal = true}
|
|
99
134
|
>
|
|
100
|
-
<
|
|
101
|
-
<div
|
|
102
|
-
<div>
|
|
103
|
-
<div
|
|
104
|
-
<div>
|
|
135
|
+
<Button hue={220}>Edit</Button>
|
|
136
|
+
<div>|</div>
|
|
137
|
+
<div>Machines: {formatNumber(grouped.length)}</div>
|
|
138
|
+
<div>|</div>
|
|
139
|
+
<div>Threads: {formatNumber(totals.uniqueThreads)}</div>
|
|
140
|
+
<div>|</div>
|
|
141
|
+
<div>Files: {formatNumber(this.props.paths.length)}</div>
|
|
142
|
+
<div>|</div>
|
|
143
|
+
<div>Pending: {formatNumber(totals.totalPending)}</div>
|
|
144
|
+
<div>|</div>
|
|
145
|
+
<div>Size: {this.formatBytes(totals.totalSize)}</div>
|
|
146
|
+
<div>|</div>
|
|
105
147
|
<div>Log Count: {formatNumber(totals.totalLogCount)}</div>
|
|
148
|
+
{totals.errorCount > 0 && (
|
|
149
|
+
<>
|
|
150
|
+
<div>|</div>
|
|
151
|
+
<div className={css.colorhsl(0, 80, 40)}>Errors: {formatNumber(totals.errorCount)}</div>
|
|
152
|
+
</>
|
|
153
|
+
)}
|
|
154
|
+
{totals.minTime !== undefined && totals.maxTime !== undefined && (
|
|
155
|
+
<>
|
|
156
|
+
<div>|</div>
|
|
157
|
+
<div>{formatDateTime(totals.minTime)} - {formatDateTime(totals.maxTime)}</div>
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
106
160
|
</div>
|
|
107
161
|
|
|
108
162
|
{this.state.showFullScreenModal && (
|
|
@@ -119,6 +173,7 @@ export class FilePathSelector extends qreact.Component<{
|
|
|
119
173
|
}}
|
|
120
174
|
onCancel={() => this.state.showFullScreenModal = false}
|
|
121
175
|
formatBytes={(bytes) => this.formatBytes(bytes)}
|
|
176
|
+
fileErrors={this.props.fileErrors}
|
|
122
177
|
/>
|
|
123
178
|
</div>
|
|
124
179
|
)}
|
|
@@ -134,6 +189,7 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
134
189
|
onSave: (selectedPaths: TimeFilePathWithSize[]) => void;
|
|
135
190
|
onCancel: () => void;
|
|
136
191
|
formatBytes: (bytes: number) => string;
|
|
192
|
+
fileErrors?: { error: string; path: string; }[];
|
|
137
193
|
}> {
|
|
138
194
|
state = t.state({
|
|
139
195
|
expandedMachines: t.lookup({
|
|
@@ -223,22 +279,50 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
223
279
|
this.props.onSave(selectedPaths);
|
|
224
280
|
}
|
|
225
281
|
|
|
282
|
+
expandAll() {
|
|
283
|
+
for (let machine of this.props.grouped) {
|
|
284
|
+
this.state.expandedMachines[machine.machineId] = { expanded: true };
|
|
285
|
+
for (let thread of machine.threads) {
|
|
286
|
+
let threadKey = `${machine.machineId}:${thread.threadId}`;
|
|
287
|
+
this.state.expandedThreads[threadKey] = { expanded: true };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
collapseAll() {
|
|
293
|
+
for (let machineId in this.state.expandedMachines) {
|
|
294
|
+
delete this.state.expandedMachines[machineId];
|
|
295
|
+
}
|
|
296
|
+
for (let threadKey in this.state.expandedThreads) {
|
|
297
|
+
delete this.state.expandedThreads[threadKey];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
226
301
|
getSelectedSummary() {
|
|
227
302
|
let selectedFiles = this.props.allPaths.filter(p => this.state.selectedPaths.has(p.fullPath));
|
|
303
|
+
let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
|
|
228
304
|
let totalSize = 0;
|
|
229
305
|
let totalLogCount = 0;
|
|
306
|
+
let pendingCount = 0;
|
|
307
|
+
let errorCount = 0;
|
|
230
308
|
let uniqueMachines = new Set<string>();
|
|
231
309
|
let uniqueThreads = new Set<string>();
|
|
232
310
|
|
|
233
311
|
for (let file of selectedFiles) {
|
|
234
312
|
totalSize += file.size;
|
|
235
313
|
totalLogCount += file.logCount || 0;
|
|
314
|
+
if (file.logCount === undefined) {
|
|
315
|
+
pendingCount++;
|
|
316
|
+
}
|
|
236
317
|
if (file.machineId) {
|
|
237
318
|
uniqueMachines.add(file.machineId);
|
|
238
319
|
}
|
|
239
320
|
if (file.threadId) {
|
|
240
321
|
uniqueThreads.add(file.threadId);
|
|
241
322
|
}
|
|
323
|
+
if (errorsByPath.get(file.fullPath)) {
|
|
324
|
+
errorCount++;
|
|
325
|
+
}
|
|
242
326
|
}
|
|
243
327
|
|
|
244
328
|
return {
|
|
@@ -246,14 +330,44 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
246
330
|
machineCount: uniqueMachines.size,
|
|
247
331
|
threadCount: uniqueThreads.size,
|
|
248
332
|
totalSize,
|
|
249
|
-
totalLogCount
|
|
333
|
+
totalLogCount,
|
|
334
|
+
pendingCount,
|
|
335
|
+
errorCount
|
|
250
336
|
};
|
|
251
337
|
}
|
|
252
338
|
|
|
253
|
-
|
|
339
|
+
getSelectedSummaryBySource(): Map<string, { fileCount: number; size: number; logCount: number; pendingCount: number; errorCount: number; }> {
|
|
340
|
+
let selectedFiles = this.props.allPaths.filter(p => this.state.selectedPaths.has(p.fullPath));
|
|
341
|
+
let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
|
|
342
|
+
let bySource = new Map<string, { fileCount: number; size: number; logCount: number; pendingCount: number; errorCount: number; }>();
|
|
343
|
+
for (let file of selectedFiles) {
|
|
344
|
+
let sourceName = file.sourceName || "unknown";
|
|
345
|
+
let existing = bySource.get(sourceName);
|
|
346
|
+
if (!existing) {
|
|
347
|
+
existing = { fileCount: 0, size: 0, logCount: 0, pendingCount: 0, errorCount: 0 };
|
|
348
|
+
bySource.set(sourceName, existing);
|
|
349
|
+
}
|
|
350
|
+
existing.fileCount++;
|
|
351
|
+
existing.size += file.size;
|
|
352
|
+
existing.logCount += file.logCount || 0;
|
|
353
|
+
if (file.logCount === undefined) {
|
|
354
|
+
existing.pendingCount++;
|
|
355
|
+
}
|
|
356
|
+
if (errorsByPath.get(file.fullPath)) {
|
|
357
|
+
existing.errorCount++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return bySource;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
renderFile(file: TimeFilePathWithSize, machineId: string, errorsByPath: Map<string, { error: string; path: string; }>) {
|
|
254
364
|
let isSelected = this.state.selectedPaths.has(file.fullPath);
|
|
365
|
+
let isPending = file.logCount === undefined;
|
|
366
|
+
const errorObj = errorsByPath.get(file.fullPath);
|
|
367
|
+
const error = errorObj?.error;
|
|
368
|
+
let rowClassName = error && css.hsl(0, 30, 95) || isPending && css.hsl(40, 30, 95) || undefined;
|
|
255
369
|
return (
|
|
256
|
-
<tr key={file.fullPath}>
|
|
370
|
+
<tr key={file.fullPath} className={rowClassName}>
|
|
257
371
|
<td className={css.pad2(2)}>
|
|
258
372
|
<Button
|
|
259
373
|
onClick={() => this.toggleFile(file.fullPath)}
|
|
@@ -264,35 +378,74 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
264
378
|
</Button>
|
|
265
379
|
</td>
|
|
266
380
|
<td className={css.pad2(2)}>{this.props.formatBytes(file.size)}</td>
|
|
267
|
-
<td className={css.pad2(2)}>{file.logCount !== undefined ? formatNumber(file.logCount) :
|
|
381
|
+
<td className={css.pad2(2)}>{file.logCount !== undefined ? formatNumber(file.logCount) : <span className={css.colorhsl(40, 60, 40)}>pending</span>}</td>
|
|
382
|
+
<td className={css.pad2(2)}>{file.sourceName}</td>
|
|
268
383
|
<td className={css.pad2(2)}>{file.dedupe}</td>
|
|
269
384
|
<td className={css.pad2(2)}>{formatDateTime(file.startTime)}</td>
|
|
385
|
+
<td className={css.pad2(2)}>
|
|
386
|
+
{error && (
|
|
387
|
+
<div className={css.colorhsl(0, 80, 40).fontWeight(600)} title={error}>ERROR</div>
|
|
388
|
+
)}
|
|
389
|
+
</td>
|
|
270
390
|
</tr>
|
|
271
391
|
);
|
|
272
392
|
}
|
|
273
393
|
|
|
274
394
|
render() {
|
|
275
395
|
let summary = this.getSelectedSummary();
|
|
396
|
+
let bySource = this.getSelectedSummaryBySource();
|
|
397
|
+
let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
|
|
276
398
|
|
|
277
399
|
return (
|
|
278
400
|
<div
|
|
279
401
|
className={css.vbox(10).pad2(10).hsl(0, 0, 100).overflowAuto.width("75vw").height("75vh").marginAuto}
|
|
280
402
|
onClick={(e) => e.stopPropagation()}
|
|
281
403
|
>
|
|
282
|
-
<div className={css.hbox(10)}>
|
|
404
|
+
<div className={css.hbox(10).alignItems("start")}>
|
|
283
405
|
<Button onClick={() => this.state.selectedPaths = new Set()}>
|
|
284
406
|
Unselect All
|
|
285
407
|
</Button>
|
|
286
408
|
<Button onClick={() => this.state.selectedPaths = new Set(this.props.allPaths.map(p => p.fullPath))}>
|
|
287
409
|
Select All
|
|
288
410
|
</Button>
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
<
|
|
293
|
-
|
|
411
|
+
<Button onClick={() => this.expandAll()}>
|
|
412
|
+
Expand All
|
|
413
|
+
</Button>
|
|
414
|
+
<Button onClick={() => this.collapseAll()}>
|
|
415
|
+
Collapse All
|
|
416
|
+
</Button>
|
|
417
|
+
|
|
418
|
+
<div className={css.vbox(5)}>
|
|
419
|
+
<div className={css.hbox(10)}>
|
|
420
|
+
<div className={css.minWidth(120)}>Machines: {formatNumber(summary.machineCount)}</div>
|
|
421
|
+
<div className={css.minWidth(110)}>Threads: {formatNumber(summary.threadCount)}</div>
|
|
422
|
+
<div className={css.minWidth(140)}>Files: {formatNumber(summary.fileCount)} ({formatNumber(summary.pendingCount)} pending)</div>
|
|
423
|
+
<div className={css.minWidth(120)}>Size: {this.props.formatBytes(summary.totalSize)}</div>
|
|
424
|
+
<div className={css.minWidth(100)}>Logs: {formatNumber(summary.totalLogCount)}</div>
|
|
425
|
+
{summary.errorCount > 0 && (
|
|
426
|
+
<div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(summary.errorCount)}</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
{bySource.size > 0 && (
|
|
431
|
+
<div className={css.vbox(5)}>
|
|
432
|
+
{Array.from(bySource.entries()).map(([sourceName, stats]) => (
|
|
433
|
+
<div key={sourceName} className={css.hbox(10)}>
|
|
434
|
+
<div className={css.minWidth(240)}>{sourceName}:</div>
|
|
435
|
+
<div className={css.minWidth(140)}>Files: {formatNumber(stats.fileCount)} ({formatNumber(stats.pendingCount)} pending)</div>
|
|
436
|
+
<div className={css.minWidth(120)}>Size: {this.props.formatBytes(stats.size)}</div>
|
|
437
|
+
<div className={css.minWidth(100)}>Logs: {formatNumber(stats.logCount)}</div>
|
|
438
|
+
{stats.errorCount > 0 && (
|
|
439
|
+
<div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(stats.errorCount)}</div>
|
|
440
|
+
)}
|
|
441
|
+
</div>
|
|
442
|
+
))}
|
|
443
|
+
</div>
|
|
444
|
+
)}
|
|
445
|
+
</div>
|
|
294
446
|
</div>
|
|
295
447
|
|
|
448
|
+
|
|
296
449
|
{this.props.grouped.map((machine) => {
|
|
297
450
|
let isMachineExpanded = machine.machineId in this.state.expandedMachines;
|
|
298
451
|
let allFiles = machine.threads.flatMap(t => t.files);
|
|
@@ -328,9 +481,12 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
328
481
|
</Button>
|
|
329
482
|
<MachineThreadInfo machineId={machine.machineId} />
|
|
330
483
|
<div>Threads: {formatNumber(machine.threads.length)}</div>
|
|
331
|
-
<div className={css.minWidth(
|
|
484
|
+
<div className={css.minWidth(120)}>Files: {formatNumber(totalFileCount)} ({formatNumber(machine.pendingCount)} pending)</div>
|
|
332
485
|
<div className={css.minWidth(100)}>Size: {this.props.formatBytes(machine.totalSize)}</div>
|
|
333
486
|
<div className={css.minWidth(100)}>Logs: {formatNumber(machine.totalLogCount)}</div>
|
|
487
|
+
{machine.errorCount > 0 && (
|
|
488
|
+
<div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(machine.errorCount)}</div>
|
|
489
|
+
)}
|
|
334
490
|
</div>
|
|
335
491
|
|
|
336
492
|
{isMachineExpanded && machine.threads.map((thread) => {
|
|
@@ -365,9 +521,12 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
365
521
|
>
|
|
366
522
|
{allThreadSelected ? "Selected" : someThreadSelected ? "Partial" : "Not Selected"}
|
|
367
523
|
</Button>
|
|
368
|
-
<div className={css.minWidth(
|
|
524
|
+
<div className={css.minWidth(120)}>Files: {formatNumber(thread.files.length)} ({formatNumber(thread.pendingCount)} pending)</div>
|
|
369
525
|
<div className={css.minWidth(100)}>Size: {this.props.formatBytes(thread.totalSize)}</div>
|
|
370
526
|
<div className={css.minWidth(100)}>Logs: {formatNumber(thread.totalLogCount)}</div>
|
|
527
|
+
{thread.errorCount > 0 && (
|
|
528
|
+
<div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(thread.errorCount)}</div>
|
|
529
|
+
)}
|
|
371
530
|
<MachineThreadInfo machineId={machine.machineId} threadId={thread.threadId} onlyShowThread />
|
|
372
531
|
</div>
|
|
373
532
|
|
|
@@ -378,12 +537,14 @@ export class FilePathSelectorModal extends qreact.Component<{
|
|
|
378
537
|
<th className={css.pad2(2).textAlign("left")}>Selection</th>
|
|
379
538
|
<th className={css.pad2(2).textAlign("left")}>Size</th>
|
|
380
539
|
<th className={css.pad2(2).textAlign("left")}>Logs</th>
|
|
540
|
+
<th className={css.pad2(2).textAlign("left")}>Source</th>
|
|
381
541
|
<th className={css.pad2(2).textAlign("left")}>Dedupe</th>
|
|
382
542
|
<th className={css.pad2(2).textAlign("left")}>Start Time</th>
|
|
543
|
+
<th className={css.pad2(2).textAlign("left")}>Status</th>
|
|
383
544
|
</tr>
|
|
384
545
|
</thead>
|
|
385
546
|
<tbody>
|
|
386
|
-
{thread.files.map((file) => this.renderFile(file, machine.machineId))}
|
|
547
|
+
{thread.files.map((file) => this.renderFile(file, machine.machineId, errorsByPath))}
|
|
387
548
|
</tbody>
|
|
388
549
|
</table>
|
|
389
550
|
)}
|