diff --git a/frontend/index.html b/frontend/index.html index 2829984..77e83d0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2320,8 +2320,12 @@ function renderFileDiff(image) { meta.push(`${t('Record')} ${image.record}` + (image.n_records ? ` / ${image.n_records}` : '')); } - if (image.index !== null && image.index !== undefined) meta.push(`#${image.index}`); - if (image.elapsed !== null && image.elapsed !== undefined) meta.push(fmt_elapsed(image.elapsed)); + if (image.index !== null && image.index !== undefined) { + meta.push(`${t('APDU')} #${image.index}`); + } + if (image.elapsed !== null && image.elapsed !== undefined) { + meta.push(`${fmt_elapsed(image.elapsed)} ${t('sec')}`); + } el('file-diff-sub').textContent = `${image.path || image.name || ''} — ${meta.join(' · ')}`; const before = hexImageHtml(image, 'before'); const after = hexImageHtml(image, 'after'); @@ -2337,9 +2341,12 @@ function renderFileDiff(image) { used.fresh ? `aa ${t('newly observed')}` : '', used.unknown ? `-- ${t('never observed')}` : '', ].join(''); + // Mention only what happened: "0 bytes newly observed" is noise. + const counts = []; + if (changed) counts.push(t('{c} bytes changed').replace('{c}', changed)); + if (fresh) counts.push(t('{n} bytes newly observed').replace('{n}', fresh)); el('file-diff-body').innerHTML = - `
${t('{c} bytes changed, {n} bytes newly observed') - .replace('{c}', changed).replace('{n}', fresh)}
+ `
${counts.length ? counts.join(' · ') : t('No changes')}

${t('Before')}

${before.html}

${t('After')}

${after.html}
@@ -3373,7 +3380,7 @@ function onSearchChange() { } // ──────────────────── Versioning ──────────────────── -const PWA_VERSION = '2.7.3'; +const PWA_VERSION = '2.7.4'; const MIN_SERVER_VERSION = '2.3.0'; function parseVersion(v) { @@ -3430,7 +3437,11 @@ const LANG_RU = { 'changed': 'изменено', 'newly observed': 'впервые наблюдается', 'never observed': 'не наблюдалось', - '{c} bytes changed, {n} bytes newly observed': 'Изменено байт: {c}; впервые наблюдается байт: {n}', + '{c} bytes changed': 'Изменено байт: {c}', + '{n} bytes newly observed': 'Впервые наблюдается байт: {n}', + 'No changes': 'Изменений нет', + 'APDU': 'APDU', + 'sec': 'с', 'Showing the first {n} bytes of the image': 'Показаны первые {n} байт образа', 'The image is frozen (size cap reached) — later changes are not tracked.': 'Образ заморожен (достигнут предел размера) — последующие изменения не отслеживаются.', 'Close': 'Закрыть', diff --git a/frontend/sw.js b/frontend/sw.js index 1e685ad..c8285eb 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simtrace-analyser-v83'; +const CACHE = 'simtrace-analyser-v84'; const PRECACHE = [ 'index.html', diff --git a/frontend/tests/comprion.test.js b/frontend/tests/comprion.test.js index 0d877e2..7541c0c 100644 --- a/frontend/tests/comprion.test.js +++ b/frontend/tests/comprion.test.js @@ -379,7 +379,7 @@ test('Files view: strings carry Russian translations', () => { for (const s of ['Files', 'Read', 'Update', 'Changed on card!', 'Change details', 'Show diff', 'Close', 'Offset', 'Record', 'Before', 'After', 'Show the change', 'changed', 'newly observed', 'never observed', - '{c} bytes changed, {n} bytes newly observed', + '{c} bytes changed', '{n} bytes newly observed', 'No changes', 'sec', 'Showing the first {n} bytes of the image', 'The image is frozen (size cap reached) — later changes are not tracked.', 'No file operations recorded', 'Failed to load files', 'Loading…']) { @@ -508,9 +508,10 @@ test('Change details modal: header, summary, legend and both views', () => { }); const sub = el('file-diff-sub').textContent; assert.match(sub, /MF\/EF_ICCID \(2FE2\) — READ BINARY/); - assert.match(sub, /#12/); + assert.match(sub, /APDU #12/); + assert.match(sub, /0003\.250 sec/); const body = el('file-diff-body').innerHTML; - assert.match(body, /2 bytes changed, 4 bytes newly observed/); + assert.match(body, /2 bytes changed · 4 bytes newly observed/); assert.match(body, /hex-cell hex-changed/); // legend + the changed byte assert.match(body, /hex-cell hex-new/); assert.match(body, /hex-cell hex-unknown/); @@ -542,6 +543,8 @@ test('Change details: a short file shows no filler, no offset, no stray legend', const body = el('file-diff-body').innerHTML; assert.doesNotMatch(body, /hex-unknown/); // no '--' cells at all assert.doesNotMatch(body, /hex-addr/); // a single row: no offset column + // The summary mentions only what happened. + assert.match(body, /^
1 bytes changed<\/div>/); assert.match(body, /hex-cell hex-changed/); // The legend lists only what the views show: 'changed' (the summary line // above still counts the zero "newly observed" bytes). @@ -551,6 +554,17 @@ test('Change details: a short file shows no filler, no offset, no stray legend', assert.doesNotMatch(legend, /newly observed|never observed/); }); +test('Change details: an empty diff says so instead of counting zeros', () => { + renderFileDiff({ + name: 'EF_ICCID', path: 'MF/EF_ICCID (2FE2)', op: 'READ BINARY', + record: null, size: 2, index: 1, elapsed: 0.5, ranges: [], + observed_ranges: [], before: [{offset: 0, hex: 'aaaa'}], + after: [{offset: 0, hex: 'aaaa'}], image_truncated: false, + }); + assert.match(el('file-diff-body').innerHTML, + /^
No changes<\/div>/); +}); + test('Change details: a short record keeps its never-observed tail', () => { // A 9-byte record with 4 bytes observed: the row ends at 9 (no filler // past it) and '--' marks the bytes that were never observed. diff --git a/pyproject.toml b/pyproject.toml index 85956f0..fadff98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend" [project] name = "simtrace-analyser" -version = "2.7.3" +version = "2.7.4" description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)" readme = {file = "README.md", content-type = "text/markdown"} license = {text = "GPL-2.0-or-later"} diff --git a/simtrace_analyser/version.py b/simtrace_analyser/version.py index abfb2fc..77c9610 100644 --- a/simtrace_analyser/version.py +++ b/simtrace_analyser/version.py @@ -1,3 +1,3 @@ """Single source of truth for the SIMtrace Analyser version.""" -__version__ = '2.7.3' +__version__ = '2.7.4' diff --git a/tests/test_pcap_import.py b/tests/test_pcap_import.py index 3b2a183..91f0156 100644 --- a/tests/test_pcap_import.py +++ b/tests/test_pcap_import.py @@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase): class TestVersion(unittest.TestCase): def test_package_version(self): from simtrace2_pysniff import __version__ - self.assertEqual(__version__, '2.7.3') + self.assertEqual(__version__, '2.7.4') def test_status_includes_version(self): import os