From 6f63b5172c753494abe8065a6ba8f20825319eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 12 Sep 2026 15:00:58 +0300 Subject: [PATCH] ui: show total scan time in the card snapshots list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each snapshot row now renders '(N files, scanned in X.XX sec)' when the snapshot has timing data (profilerSnapshotCountLabel); old/imported snapshots without timing keep the plain '(N files)'. RU wording 'за X.XX сек'. SW cache v100 -> v101. --- frontend/index.html | 12 +++++++++++- frontend/sw.js | 2 +- frontend/tests/profiler.test.js | 13 ++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) 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; +});