diff --git a/frontend/index.html b/frontend/index.html index ed69607..8ca9abd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1413,7 +1413,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // by the server version check in pysimConnect(). -const SIMPLE_VERSION = '2.7.10'; +const SIMPLE_VERSION = '2.7.11'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -7164,7 +7164,7 @@ function pysimFsInfoHtml(sel) { let html = ''; if (attrs.length) html += '
' + attrs.join(' · ') + '
'; if (sel.fci_hex) { - html += '
' + profilerFciPreviewItems(sel.fci_hex) + '
'; + html += '
' + profilerFciPreviewItems(sel.fci_hex, { hideFileSize: num(sel.file_size) !== null }) + '
'; } return html; } @@ -11696,13 +11696,28 @@ function fcpDiffHtml(expectedHex, actualHex, labels) { return html; } -function profilerFciPreviewItems(hex) { +// Decoded FCI rows. The short file identifier belongs to the file +// identifier, so it is merged into that row. `opts.hideFileSize` drops the +// File size DO where the caller already shows the size (file manager header). +function profilerFciPreviewItems(hex, opts) { const d = fcpDecode(hex); if (!d.items.length && !d.error) return ''; - let html = ''; + opts = opts || {}; + const rows = []; for (const it of d.items) { if (it.decoded === null) continue; - html += '
' + esc(it.name) + ': ' + esc(it.decoded || it.value) + '
'; + if (opts.hideFileSize && it.key === '80') continue; + rows.push({ key: it.key, name: it.name, text: it.decoded || it.value }); + } + const fi = rows.find(r => r.key === '83'); + const sfi = rows.find(r => r.key === '88'); + if (fi && sfi) { + fi.text += ' · ' + sfi.name + ': ' + sfi.text; + rows.splice(rows.indexOf(sfi), 1); + } + let html = ''; + for (const r of rows) { + html += '
' + esc(r.name) + ': ' + esc(r.text) + '
'; } if (d.error) { html += '
' + esc(t('Decode failed')) + ': ' + esc(d.error) + '
'; diff --git a/frontend/sw.js b/frontend/sw.js index ad1863f..4c31bdc 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v213'; +const CACHE = 'simple-v214'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js index 716801b..1013176 100644 --- a/frontend/tests/profiler.test.js +++ b/frontend/tests/profiler.test.js @@ -770,6 +770,21 @@ test('pysimFsInfoHtml shows FID, type, size and the decoded FCI', () => { assert.ok(out.includes('Record count: 5'), out); assert.ok(!out.includes('Decoded FCI'), out); assert.ok(out.includes('File descriptor'), out); + // the header already shows the size -> the FCI File size row is dropped + assert.ok(!out.includes('File size:'), out); + assert.ok(!out.includes('75 bytes'), out); + // the short file identifier is merged into the file identifier row + assert.ok(out.includes('6F4F · Short file identifier: 22'), out); + assert.ok(!out.includes('
Short file identifier:'), out); + delete global.t; +}); + +test('pysimFsInfoHtml keeps the FCI File size when the header has none', () => { + global.t = s => s; + const hex = '621A82054221000F0583026F4F8A01058B036F06098002004B8801B0'; + const out = pysimFsInfoHtml({ fid: '6f4f', file_type: 'linear_fixed', file_size: null, record_len: 15, num_of_rec: 5, fci_hex: hex }); + assert.ok(!out.includes('Size:'), out); + assert.ok(out.includes('File size:'), out); assert.ok(out.includes('75 bytes'), out); delete global.t; }); @@ -788,7 +803,13 @@ test('pysimFsInfoHtml omits the FCI block without fci_hex and skips null fields' test('profilerFciPreviewItems renders decoded items and degrades gracefully', () => { global.t = s => s; - assert.ok(profilerFciPreviewItems(FCP_TRANSPARENT).includes('File size: ')); + const full = profilerFciPreviewItems(FCP_TRANSPARENT); + assert.ok(full.includes('File size: ')); + assert.ok(full.includes('6F07 · Short file identifier: 2')); + assert.ok(!full.includes('
Short file identifier:'), 'SFI is merged, not a separate row'); + const noSize = profilerFciPreviewItems(FCP_TRANSPARENT, { hideFileSize: true }); + assert.ok(!noSize.includes('File size: ')); + assert.ok(noSize.includes('6F07 · Short file identifier: 2')); assert.ok(profilerFciPreviewItems('not hex').includes('Decode failed')); assert.strictEqual(profilerFciPreviewItems(''), ''); delete global.t; diff --git a/pyproject.toml b/pyproject.toml index b867394..9c8a882 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "2.7.10" +version = "2.7.11" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 098a099..4a159f9 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -26,7 +26,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '2.7.10' +VERSION = '2.7.11' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE