diff --git a/frontend/index.html b/frontend/index.html
index 7fb5d3e..f25ae6e 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -7293,7 +7293,7 @@ function profilerRenderSnapshotList() {
html += '' + esc(s.name) + '';
if (s.iccid) html += '' + esc(s.iccid) + '';
html += '' + esc(new Date(s.created).toLocaleString()) + '';
- html += '(' + s.files.length + ' ' + esc(t('files')) + ')';
+ html += '(' + esc(profilerSnapshotCountLabel(s)) + ')';
html += '';
html += '';
html += '';
@@ -7623,6 +7623,14 @@ function profilerFormatMs(ms) {
return Math.round(ms) + ' ms';
}
+function profilerSnapshotCountLabel(s) {
+ let label = (s.files ? s.files.length : 0) + ' ' + t('files');
+ if (s.timing && s.timing.total_ms !== undefined) {
+ label += ', ' + t('scanned in') + ' ' + (s.timing.total_ms / 1000).toFixed(2) + ' ' + t('sec');
+ }
+ return label;
+}
+
async function profilerBuildSnapshotFile(path, c, timing) {
let sel;
try {
@@ -8951,6 +8959,8 @@ const LANG_RU = {
'Files': 'Файлов',
'Records': 'Записей',
'Scan time': 'Время сканирования',
+ 'scanned in': 'за',
+ 'sec': 'сек',
'No timing data': 'Нет данных о времени',
'min': 'мин',
'avg': 'сред',
diff --git a/frontend/sw.js b/frontend/sw.js
index cf53041..1ab3f96 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'otaman-v100';
+const CACHE = 'otaman-v101';
const URLS = [
'index.html',
'help.html',
diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js
index 43590ad..c0d7fde 100644
--- a/frontend/tests/profiler.test.js
+++ b/frontend/tests/profiler.test.js
@@ -21,7 +21,7 @@ function extractFunc(src, name, asyncFn) {
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
}
-const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch', 'profilerScanRefreshOptions', 'profilerLiveSource', 'profilerSnapshotSource', 'profilerVisibleResults', 'profilerMaskFidForFile', 'profilerRulesFromSnapshot', 'profilerExtraFileResults', 'profilerScanNameKeydown', 'profilerTimingStats', 'profilerTimingAccumulator', 'profilerFormatMs', 'profilerRenderSnapshotSummary'];
+const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch', 'profilerScanRefreshOptions', 'profilerLiveSource', 'profilerSnapshotSource', 'profilerVisibleResults', 'profilerMaskFidForFile', 'profilerRulesFromSnapshot', 'profilerExtraFileResults', 'profilerScanNameKeydown', 'profilerTimingStats', 'profilerTimingAccumulator', 'profilerFormatMs', 'profilerRenderSnapshotSummary', 'profilerSnapshotCountLabel'];
let code = '';
for (const f of FNS) code += extractFunc(html, f) + '\n';
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
@@ -1216,3 +1216,14 @@ test('profilerRenderSnapshotSummary notes missing timing data', () => {
assert.ok(html.includes('No timing data'), html);
delete global.t;
});
+
+test('profilerSnapshotCountLabel appends the scan time when available', () => {
+ global.t = s => (s === 'scanned in' ? 'scanned in' : s);
+ const withTime = profilerSnapshotCountLabel({ files: new Array(95).fill({}), timing: { total_ms: 21320 } });
+ assert.strictEqual(withTime, '95 files, scanned in 21.32 sec');
+ const noTime = profilerSnapshotCountLabel({ files: new Array(3).fill({}) });
+ assert.strictEqual(noTime, '3 files');
+ const empty = profilerSnapshotCountLabel({ files: [], timing: {} });
+ assert.strictEqual(empty, '0 files');
+ delete global.t;
+});