querysub 0.356.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 +9 -0
- package/bin/movelogs.js +4 -0
- package/package.json +13 -6
- package/scripts/postinstall.js +23 -0
- package/src/-a-archives/archiveCache.ts +10 -12
- package/src/-a-archives/archives.ts +29 -0
- package/src/-a-archives/archivesBackBlaze.ts +60 -12
- package/src/-a-archives/archivesDisk.ts +39 -13
- package/src/-a-archives/archivesLimitedCache.ts +21 -0
- package/src/-a-archives/archivesMemoryCache.ts +374 -0
- package/src/-a-archives/archivesPrivateFileSystem.ts +22 -0
- package/src/-g-core-values/NodeCapabilities.ts +3 -0
- package/src/0-path-value-core/auditLogs.ts +5 -1
- package/src/0-path-value-core/pathValueCore.ts +7 -7
- package/src/4-dom/qreact.tsx +1 -0
- package/src/4-querysub/Querysub.ts +1 -5
- package/src/config.ts +5 -0
- 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/MachineThreadInfo.tsx +235 -0
- package/src/diagnostics/NodeViewer.tsx +5 -3
- package/src/diagnostics/logs/FastArchiveAppendable.ts +79 -42
- package/src/diagnostics/logs/FastArchiveController.ts +102 -63
- package/src/diagnostics/logs/FastArchiveViewer.tsx +36 -8
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +462 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.cpp +327 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.d.ts +18 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.js +1 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +222 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants.ts +22 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat +1145 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat.d.ts +178 -0
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +208 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +716 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +146 -0
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +569 -0
- package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +685 -0
- package/src/diagnostics/logs/IndexedLogs/LogStreamer.ts +47 -0
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +901 -0
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +236 -0
- package/src/diagnostics/logs/IndexedLogs/binding.gyp +23 -0
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +251 -0
- package/src/diagnostics/logs/IndexedLogs/moveLogsEntry.ts +10 -0
- package/src/diagnostics/logs/LogViewer2.tsx +120 -55
- package/src/diagnostics/logs/TimeRangeSelector.tsx +5 -2
- package/src/diagnostics/logs/diskLogger.ts +32 -48
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +3 -2
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +1 -0
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +150 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +150 -15
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat +106 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat.d.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/testHoist.ts +5 -0
- package/src/diagnostics/logs/logViewerExtractField.ts +2 -3
- package/src/diagnostics/managementPages.tsx +10 -0
- package/src/diagnostics/trackResources.ts +1 -1
- package/src/functional/limitProcessing.ts +39 -0
- package/src/misc/lz4_wasm_nodejs.d.ts +34 -0
- package/src/misc/lz4_wasm_nodejs.js +178 -0
- package/src/misc/lz4_wasm_nodejs_bg.js +94 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm +0 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm.d.ts +15 -0
- package/src/storage/CompressedStream.ts +13 -0
- package/src/storage/LZ4.ts +32 -0
- package/src/storage/ZSTD.ts +10 -0
- package/src/wat/watCompiler.ts +1716 -0
- package/src/wat/watGrammar.pegjs +93 -0
- package/src/wat/watHandler.ts +179 -0
- package/src/wat/watInstructions.txt +707 -0
- package/src/zip.ts +3 -89
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +0 -125
|
@@ -26,7 +26,7 @@ import { getPathIndex, getPathStr2 } from "../../path";
|
|
|
26
26
|
import { onNextPaint } from "../../functional/onNextPaint";
|
|
27
27
|
import { getArchivesBackblazePrivateImmutable, getArchivesBackblazePublicImmutable } from "../../-a-archives/archivesBackBlaze";
|
|
28
28
|
import { httpsRequest } from "socket-function/src/https";
|
|
29
|
-
import { getDomain } from "../../config";
|
|
29
|
+
import { getDomain, isPublic } from "../../config";
|
|
30
30
|
import { getIPDomain } from "../../-e-certs/EdgeCertController";
|
|
31
31
|
import { getArchivesPrivateFileSystem } from "../../-a-archives/archivesPrivateFileSystem";
|
|
32
32
|
import { createArchivesLimitedCache } from "../../-a-archives/archivesLimitedCache";
|
|
@@ -40,6 +40,7 @@ import { Querysub } from "../../4-querysub/QuerysubController";
|
|
|
40
40
|
|
|
41
41
|
export type FileMetadata = {
|
|
42
42
|
nodeId?: string;
|
|
43
|
+
machineId?: string;
|
|
43
44
|
path: string;
|
|
44
45
|
url: string;
|
|
45
46
|
size: number;
|
|
@@ -67,16 +68,8 @@ type SynchronizeInfo = {
|
|
|
67
68
|
|
|
68
69
|
// Excluding authorize tokens, then hashed
|
|
69
70
|
export function getFileMetadataHash(file: FileMetadata): string {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
urlObj.search = "";
|
|
73
|
-
} else {
|
|
74
|
-
let args = JSON.parse(urlObj.searchParams.get("args") || "");
|
|
75
|
-
// The syncId shouldn't be part of the hash though
|
|
76
|
-
args[0] = "";
|
|
77
|
-
urlObj.searchParams.set("args", JSON.stringify(args));
|
|
78
|
-
}
|
|
79
|
-
return sha256(urlObj.toString());
|
|
71
|
+
// NOTE: This size will be at least equal or older to the size when we read it. And so for the file to have new data, it means the size will have to be larger. And because this size is always equal older to when we read it and never newer, it means it won't include the newer writes. And so even for local files which are constantly changing, this will properly invalidate when we write to them again.
|
|
72
|
+
return sha256(JSON.stringify({ path: file.path, size: file.size, machineId: file.machineId }));
|
|
80
73
|
}
|
|
81
74
|
|
|
82
75
|
export class FastArchiveAppendableControllerBase {
|
|
@@ -131,13 +124,21 @@ export class FastArchiveAppendableControllerBase {
|
|
|
131
124
|
/** Download a local file from another server (coordinator forwards request).
|
|
132
125
|
* NOTE: This doesn't support streaming, which isn't great. However, eventually most logs will make their way to backblaze. So not supporting streaming here doesn't mean we don't support streaming in general. We also compress the file. So for the compressed file to be so big that you can't send it all at once would mean your uncompressed data would just be ridiculously large.
|
|
133
126
|
*/
|
|
134
|
-
public async downloadLocalFile(syncId: string, targetNodeId: string, path: string): Promise<Buffer> {
|
|
127
|
+
public async downloadLocalFile(syncId: string, targetNodeId: string, path: string, fromLocalArchives?: boolean): Promise<Buffer> {
|
|
135
128
|
const caller = SocketFunction.getCaller();
|
|
136
129
|
let syncInfo = FastArchiveAppendableControllerBase.activeSynchronizes.get(syncId);
|
|
137
130
|
if (!syncInfo) {
|
|
138
131
|
throw new Error(`Invalid sync ID: ${syncId}`);
|
|
139
132
|
}
|
|
140
133
|
|
|
134
|
+
if (fromLocalArchives) {
|
|
135
|
+
let archives = new FastArchiveAppendable(syncInfo.config.rootPath).getArchives(false);
|
|
136
|
+
let file = await archives.get(path);
|
|
137
|
+
if (!file) {
|
|
138
|
+
throw new Error(`File not found in local archives: ${path}`);
|
|
139
|
+
}
|
|
140
|
+
return file;
|
|
141
|
+
}
|
|
141
142
|
try {
|
|
142
143
|
// Forward the request to the actual server that has the file
|
|
143
144
|
let targetController = FastArchiveAppendableController.nodes[targetNodeId];
|
|
@@ -205,6 +206,7 @@ export class FastArchiveAppendableControllerBase {
|
|
|
205
206
|
};
|
|
206
207
|
rootPath: string;
|
|
207
208
|
noLocalFiles?: boolean;
|
|
209
|
+
forceGetPublic?: boolean;
|
|
208
210
|
}): Promise<{
|
|
209
211
|
files: FileMetadata[];
|
|
210
212
|
}> {
|
|
@@ -234,6 +236,7 @@ export class FastArchiveAppendableControllerBase {
|
|
|
234
236
|
range: config.range,
|
|
235
237
|
rootPath: config.rootPath,
|
|
236
238
|
noLocalFiles: config.noLocalFiles,
|
|
239
|
+
forceGetPublic: config.forceGetPublic,
|
|
237
240
|
});
|
|
238
241
|
}
|
|
239
242
|
|
|
@@ -244,6 +247,7 @@ export class FastArchiveAppendableControllerBase {
|
|
|
244
247
|
};
|
|
245
248
|
rootPath: string;
|
|
246
249
|
noLocalFiles?: boolean;
|
|
250
|
+
forceGetPublic?: boolean;
|
|
247
251
|
}): Promise<{
|
|
248
252
|
files: FileMetadata[];
|
|
249
253
|
}> {
|
|
@@ -262,6 +266,7 @@ export class FastArchiveAppendableControllerBase {
|
|
|
262
266
|
range: config.range,
|
|
263
267
|
rootPath: config.rootPath,
|
|
264
268
|
noLocalFiles: config.noLocalFiles,
|
|
269
|
+
forceGetPublic: config.forceGetPublic,
|
|
265
270
|
});
|
|
266
271
|
}
|
|
267
272
|
|
|
@@ -273,6 +278,7 @@ export class FastArchiveAppendableControllerBase {
|
|
|
273
278
|
};
|
|
274
279
|
rootPath: string;
|
|
275
280
|
noLocalFiles?: boolean;
|
|
281
|
+
forceGetPublic?: boolean;
|
|
276
282
|
}): Promise<{
|
|
277
283
|
files: FileMetadata[];
|
|
278
284
|
}> {
|
|
@@ -281,15 +287,60 @@ export class FastArchiveAppendableControllerBase {
|
|
|
281
287
|
}
|
|
282
288
|
let syncId = config.syncId ?? "";
|
|
283
289
|
|
|
290
|
+
let isPublicValue = isPublic() || config.forceGetPublic;
|
|
291
|
+
|
|
292
|
+
const getLocalFileMetadata = async (config: {
|
|
293
|
+
nodeId: string;
|
|
294
|
+
path: string;
|
|
295
|
+
size: number;
|
|
296
|
+
fromLocalArchives?: boolean;
|
|
297
|
+
}): Promise<FileMetadata> => {
|
|
298
|
+
let { nodeId, path, size, fromLocalArchives } = config;
|
|
299
|
+
// Create download URL that points to the coordinator (this server)
|
|
300
|
+
// The coordinator will forward the request to the actual file server
|
|
301
|
+
let coordinatorNodeId = getOwnNodeId();
|
|
302
|
+
let coordinatorController = FastArchiveAppendableController.nodes[coordinatorNodeId];
|
|
303
|
+
let downloadCall = coordinatorController.downloadLocalFile[getCallObj](
|
|
304
|
+
syncId,
|
|
305
|
+
nodeId, // Target server that has the file
|
|
306
|
+
path,
|
|
307
|
+
fromLocalArchives,
|
|
308
|
+
);
|
|
309
|
+
let url = SocketFunction.getHTTPCallLink(downloadCall);
|
|
310
|
+
// Have to use the IP domain, as it's externally available. That, plus the port, should uniquely identify us.
|
|
311
|
+
let ipDomain = await getIPDomain();
|
|
312
|
+
let urlObj = new URL(url);
|
|
313
|
+
urlObj.hostname = ipDomain;
|
|
314
|
+
url = urlObj.toString();
|
|
315
|
+
|
|
316
|
+
let timeStamp = getFileTimeStamp(path);
|
|
317
|
+
let startTime = timeStamp.startTime;
|
|
318
|
+
let endTime = timeStamp.endTime;
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
nodeId: nodeId,
|
|
322
|
+
machineId: getMachineId(nodeId),
|
|
323
|
+
path: path,
|
|
324
|
+
url: url,
|
|
325
|
+
size: size,
|
|
326
|
+
startTime: startTime,
|
|
327
|
+
endTime: endTime,
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
284
331
|
// Define inline functions for parallel execution
|
|
285
332
|
const searchBackblazeFiles = async (): Promise<FileMetadata[]> => {
|
|
286
|
-
let archives = new FastArchiveAppendable(config.rootPath).getArchives();
|
|
333
|
+
let archives = new FastArchiveAppendable(config.rootPath).getArchives(config.forceGetPublic ?? false);
|
|
287
334
|
let backblazeFiles: FileMetadata[] = [];
|
|
288
|
-
if (!archives.getDownloadAuthorization
|
|
289
|
-
|
|
335
|
+
if (!archives.getDownloadAuthorization && isPublicValue) {
|
|
336
|
+
throw new Error(`archives.getDownloadAuthorization is `);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!archives.getURL && isPublicValue) throw new Error(`archives.getURL is missing?`);
|
|
340
|
+
let authorization = await archives.getDownloadAuthorization?.({
|
|
290
341
|
validDurationInSeconds: timeInDay * 6 / 1000,
|
|
291
342
|
});
|
|
292
|
-
let authToken = authorization
|
|
343
|
+
let authToken = authorization?.authorizationToken;
|
|
293
344
|
|
|
294
345
|
const folderProgress = this.updateProgress(syncId, "Backblaze folder search", 0);
|
|
295
346
|
let folderMax = 0;
|
|
@@ -346,21 +397,28 @@ export class FastArchiveAppendableControllerBase {
|
|
|
346
397
|
let filePaths = await archives.findInfo(folder + "/", { shallow: true, type: "files" });
|
|
347
398
|
for (let info of filePaths) {
|
|
348
399
|
if (!info.path.endsWith(".log")) continue;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
400
|
+
if (archives.getURL && authToken !== undefined) {
|
|
401
|
+
let url = await archives.getURL(info.path);
|
|
402
|
+
let urlObj = new URL(url);
|
|
403
|
+
// IMPORTANT! This is CASE SENSITIVE! Ugh...
|
|
404
|
+
urlObj.searchParams.set("Authorization", authToken);
|
|
405
|
+
url = urlObj.toString();
|
|
406
|
+
|
|
407
|
+
backblazeFiles.push({
|
|
408
|
+
path: info.path,
|
|
409
|
+
url: url,
|
|
410
|
+
size: info.size,
|
|
411
|
+
startTime: hourStart,
|
|
412
|
+
endTime: hourEnd,
|
|
413
|
+
});
|
|
414
|
+
} else {
|
|
415
|
+
backblazeFiles.push(await getLocalFileMetadata({
|
|
416
|
+
nodeId: getOwnNodeId(),
|
|
417
|
+
path: info.path,
|
|
418
|
+
size: info.size,
|
|
419
|
+
fromLocalArchives: true,
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
364
422
|
}
|
|
365
423
|
}
|
|
366
424
|
}));
|
|
@@ -371,12 +429,20 @@ export class FastArchiveAppendableControllerBase {
|
|
|
371
429
|
return backblazeFiles;
|
|
372
430
|
};
|
|
373
431
|
|
|
374
|
-
|
|
432
|
+
// NOTE: Disk files are on our machine, and on every other server (as we want to search live logs as well).
|
|
433
|
+
const getDiskFiles = async (): Promise<FileMetadata[]> => {
|
|
375
434
|
const getControllerProgress = this.updateProgress(syncId, "Discovering remote machines", 0);
|
|
376
435
|
|
|
377
436
|
let localFiles: FileMetadata[] = [];
|
|
378
437
|
|
|
379
438
|
let nodeIds = await getControllerNodeIdList(FastArchiveAppendableController);
|
|
439
|
+
// This filtering is somewhat of a hack, as machines can have mixed public status, and change it easliy. However in practice... they don't... We COULD have two sets of pending logs, for public and non-public (like we have backblaze and local archives for non-pending logs). But... at the moment... I don't think it's needed.
|
|
440
|
+
let ownMachineId = getOwnMachineId();
|
|
441
|
+
if (!isPublicValue) {
|
|
442
|
+
nodeIds = nodeIds.filter(x => getMachineId(x.nodeId) === ownMachineId);
|
|
443
|
+
} else if (isPublicValue && !isPublic()) {
|
|
444
|
+
nodeIds = nodeIds.filter(x => getMachineId(x.nodeId) !== ownMachineId);
|
|
445
|
+
}
|
|
380
446
|
let byMachineId = keyByArray(nodeIds, x => getMachineId(x.nodeId));
|
|
381
447
|
getControllerProgress(byMachineId.size, byMachineId.size);
|
|
382
448
|
|
|
@@ -402,7 +468,8 @@ export class FastArchiveAppendableControllerBase {
|
|
|
402
468
|
let controller = FastArchiveAppendableController.nodes[aliveNodeId];
|
|
403
469
|
|
|
404
470
|
let pendingFiles = await errorToUndefined(
|
|
405
|
-
controller.getPendingFiles(config.rootPath, config.range)
|
|
471
|
+
controller.getPendingFiles(config.rootPath, config.range)
|
|
472
|
+
);
|
|
406
473
|
console.log(blue(`Found ${pendingFiles?.length} pending files on node ${aliveNodeId}`));
|
|
407
474
|
|
|
408
475
|
remoteValue++;
|
|
@@ -410,44 +477,16 @@ export class FastArchiveAppendableControllerBase {
|
|
|
410
477
|
|
|
411
478
|
if (!pendingFiles) return;
|
|
412
479
|
for (let file of pendingFiles) {
|
|
413
|
-
|
|
414
|
-
// The coordinator will forward the request to the actual file server
|
|
415
|
-
let coordinatorNodeId = getOwnNodeId();
|
|
416
|
-
let coordinatorController = FastArchiveAppendableController.nodes[coordinatorNodeId];
|
|
417
|
-
let downloadCall = coordinatorController.downloadLocalFile[getCallObj](
|
|
418
|
-
syncId,
|
|
419
|
-
aliveNodeId, // Target server that has the file
|
|
420
|
-
file.path
|
|
421
|
-
);
|
|
422
|
-
let url = SocketFunction.getHTTPCallLink(downloadCall);
|
|
423
|
-
// Have to use the IP domain, as it's externally available. That, plus the port, should uniquely identify us.
|
|
424
|
-
let ipDomain = await getIPDomain();
|
|
425
|
-
let urlObj = new URL(url);
|
|
426
|
-
urlObj.hostname = ipDomain;
|
|
427
|
-
url = urlObj.toString();
|
|
428
|
-
|
|
429
|
-
let timeStamp = getFileTimeStamp(file.path);
|
|
430
|
-
let startTime = timeStamp.startTime;
|
|
431
|
-
let endTime = timeStamp.endTime;
|
|
432
|
-
|
|
433
|
-
localFiles.push({
|
|
434
|
-
nodeId: aliveNodeId,
|
|
435
|
-
path: file.path,
|
|
436
|
-
url: url,
|
|
437
|
-
size: file.size,
|
|
438
|
-
startTime: startTime,
|
|
439
|
-
endTime: endTime,
|
|
440
|
-
});
|
|
480
|
+
localFiles.push(await getLocalFileMetadata({ nodeId: aliveNodeId, path: file.path, size: file.size }));
|
|
441
481
|
}
|
|
442
482
|
}));
|
|
443
483
|
|
|
444
484
|
return localFiles;
|
|
445
485
|
};
|
|
446
|
-
|
|
447
486
|
// Execute both operations in parallel
|
|
448
487
|
let filePromises: Promise<FileMetadata[]>[] = [];
|
|
449
488
|
filePromises.push(searchBackblazeFiles());
|
|
450
|
-
if (!config.noLocalFiles) filePromises.push(
|
|
489
|
+
if (!config.noLocalFiles) filePromises.push(getDiskFiles());
|
|
451
490
|
|
|
452
491
|
let allFilesList = await Promise.all(filePromises);
|
|
453
492
|
let allFiles = allFilesList.flat();
|
|
@@ -32,11 +32,11 @@ export const filterParam2 = new URLParam("filter2", "");
|
|
|
32
32
|
export const cacheBustParam = new URLParam("cacheBust", 0);
|
|
33
33
|
const caseInsensitiveParam = new URLParam("caseInsensitive", false);
|
|
34
34
|
const hideAllDataParam = new URLParam("hideAllData", false);
|
|
35
|
-
|
|
36
35
|
export type ScanFnc = (posStart: number, posEnd: number, data: Buffer) => boolean;
|
|
37
36
|
|
|
38
37
|
export class FastArchiveViewer<T> extends qreact.Component<{
|
|
39
|
-
fastArchives: FastArchiveAppendable<T>[];
|
|
38
|
+
fastArchives: () => FastArchiveAppendable<T>[];
|
|
39
|
+
forceGetPublic?: boolean;
|
|
40
40
|
runOnLoad?: boolean;
|
|
41
41
|
onStart: () => MaybePromise<void>;
|
|
42
42
|
getScanFnc?: () => ScanFnc;
|
|
@@ -83,6 +83,8 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
83
83
|
private limitedScanCount = 0;
|
|
84
84
|
private limitedMatchCount = 0;
|
|
85
85
|
|
|
86
|
+
private fileErrors: Array<{ file: FileMetadata; error: Error }> = [];
|
|
87
|
+
|
|
86
88
|
private progressBars: Record<string, {
|
|
87
89
|
section: string,
|
|
88
90
|
value: number,
|
|
@@ -105,7 +107,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
cancelAllSynchronizes() {
|
|
108
|
-
for (let fastArchive of this.props.fastArchives) {
|
|
110
|
+
for (let fastArchive of this.props.fastArchives()) {
|
|
109
111
|
fastArchive.cancelAllSynchronizes();
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -161,7 +163,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
161
163
|
filterString,
|
|
162
164
|
startTime: timeRange.startTime,
|
|
163
165
|
endTime: timeRange.endTime,
|
|
164
|
-
fastArchivePaths: fastArchives.map(archive => archive.rootPath),
|
|
166
|
+
fastArchivePaths: fastArchives().map(archive => archive.rootPath),
|
|
165
167
|
};
|
|
166
168
|
});
|
|
167
169
|
|
|
@@ -174,6 +176,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
174
176
|
delete this.progressBars[key];
|
|
175
177
|
}
|
|
176
178
|
this.state.fileMetadata = [];
|
|
179
|
+
this.fileErrors = [];
|
|
177
180
|
this.limitedScanCount = 0;
|
|
178
181
|
this.limitedMatchCount = 0;
|
|
179
182
|
this.allSize = 0;
|
|
@@ -331,7 +334,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
331
334
|
const limitedBuffer = Buffer.from(LOG_LIMIT_FLAG);
|
|
332
335
|
|
|
333
336
|
|
|
334
|
-
await Promise.all(fastArchives.map(async (fastArchive, index) => {
|
|
337
|
+
await Promise.all(fastArchives().map(async (fastArchive, index) => {
|
|
335
338
|
const result = await fastArchive.synchronizeData({
|
|
336
339
|
range: timeRange,
|
|
337
340
|
cacheBust: Querysub.fastRead(() => cacheBustParam.value),
|
|
@@ -407,6 +410,10 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
407
410
|
}
|
|
408
411
|
}
|
|
409
412
|
}),
|
|
413
|
+
onError: (error, file) => {
|
|
414
|
+
if (!isLatestSync()) return;
|
|
415
|
+
this.fileErrors.push({ file, error });
|
|
416
|
+
},
|
|
410
417
|
onProgress,
|
|
411
418
|
onFinish: () => {
|
|
412
419
|
ifLatest(() => {
|
|
@@ -615,7 +622,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
615
622
|
private getOutdatedInfo(): string[] {
|
|
616
623
|
const currentTimeRange = getTimeRange();
|
|
617
624
|
const currentFilterString = filterParam.value;
|
|
618
|
-
const currentArchivePaths = this.props.fastArchives.map(archive => archive.rootPath);
|
|
625
|
+
const currentArchivePaths = this.props.fastArchives().map(archive => archive.rootPath);
|
|
619
626
|
|
|
620
627
|
// Get stored sync parameters
|
|
621
628
|
const storedParams = this.state.currentSyncParams;
|
|
@@ -735,6 +742,19 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
735
742
|
<div className={css.vbox(10)}>
|
|
736
743
|
{this.state.runCount > 0 && (() => {
|
|
737
744
|
const outdatedWarnings = this.getOutdatedInfo();
|
|
745
|
+
const errorCount = this.fileErrors.length;
|
|
746
|
+
|
|
747
|
+
// Group errors by file path
|
|
748
|
+
const errorsByFile = new Map<string, number>();
|
|
749
|
+
for (let errorInfo of this.fileErrors) {
|
|
750
|
+
const path = errorInfo.file.path;
|
|
751
|
+
errorsByFile.set(path, (errorsByFile.get(path) || 0) + 1);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const errorTooltip = Array.from(errorsByFile.entries())
|
|
755
|
+
.map(([path, count]) => `${path}: ${count} errors`)
|
|
756
|
+
.join("\n");
|
|
757
|
+
|
|
738
758
|
return (
|
|
739
759
|
<div className={css.hbox(10).wrap}>
|
|
740
760
|
<div
|
|
@@ -743,8 +763,16 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
743
763
|
`${x.path} (${formatNumber(x.size)})`
|
|
744
764
|
).join("\n")}
|
|
745
765
|
>
|
|
746
|
-
File count: {formatNumber(totalFileCount)}, Backblaze size: {formatNumber(totalBackblazeByteCount)}B (compressed), Disk size: {formatNumber(totalLocalByteCount)}B (uncompressed)
|
|
766
|
+
File count: {formatNumber(totalFileCount)}{errorCount > 0 && ` (${errorCount} erred)`}, Backblaze size: {formatNumber(totalBackblazeByteCount)}B (compressed), Disk size: {formatNumber(totalLocalByteCount)}B (uncompressed)
|
|
747
767
|
</div>
|
|
768
|
+
{errorCount > 0 && (
|
|
769
|
+
<div
|
|
770
|
+
className={infoDisplay(45)}
|
|
771
|
+
title={errorTooltip}
|
|
772
|
+
>
|
|
773
|
+
{errorCount} file{errorCount > 1 ? "s" : ""} failed to load
|
|
774
|
+
</div>
|
|
775
|
+
)}
|
|
748
776
|
{outdatedWarnings.length > 0 && (
|
|
749
777
|
<div
|
|
750
778
|
className={infoDisplay(30).button}
|
|
@@ -772,9 +800,9 @@ export class FastArchiveViewer<T> extends qreact.Component<{
|
|
|
772
800
|
</div>
|
|
773
801
|
)}
|
|
774
802
|
{this.state.finished && (() => {
|
|
775
|
-
if (readLocalTimes.length === 0) return null;
|
|
776
803
|
|
|
777
804
|
const timeRange = getTimeRange();
|
|
805
|
+
if (timeRange.endTime && readLocalTimes.length === 0) return null;
|
|
778
806
|
const earliestTime = Math.min(...readLocalTimes);
|
|
779
807
|
|
|
780
808
|
if (earliestTime >= timeRange.endTime) return null;
|