diff --git a/frontend/index.html b/frontend/index.html index de02668..83aac4f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7225,6 +7225,16 @@ function profilerScanSetTarget(target) { document.getElementById('profiler-scan-options').classList.toggle('hidden', target === 'snapshot'); } +// Re-translate the dynamically built mask labels in the open scan modal, +// preserving the checkbox state (ignore-list labels are plain file names). +function profilerScanRefreshOptions() { + if (_scanTarget === 'snapshot') return; + document.querySelectorAll('#profiler-scan-mask input[data-mask-fid]').forEach(cb => { + const span = cb.parentElement ? cb.parentElement.querySelector('span') : null; + if (span) span.textContent = t('Match first 4 bytes for') + ' ' + PROFILER_MASK_PREFIX4_FIDS[cb.getAttribute('data-mask-fid')]; + }); +} + function profilerFromCard() { profilerScanSetTarget('profile'); document.getElementById('profiler-scan-name').value = ''; @@ -7594,6 +7604,14 @@ function snapshotOpen(i) { snapshotViewId = s.id; profilerSetView('snapshot'); document.getElementById('snapshot-name').value = s.name; + profilerRenderSnapshotData(); +} + +// Re-render the immutable part of the snapshot view (ICCID + file cards) +// without touching the (editable) name input. +function profilerRenderSnapshotData() { + const s = snapshots.find(x => x.id === snapshotViewId); + if (!s) return; document.getElementById('snapshot-iccid').textContent = s.iccid || ''; document.getElementById('snapshot-files').innerHTML = profilerRenderSnapshotFiles(s); } @@ -7863,26 +7881,31 @@ async function profilerCheck(i) { profilerSetView('results'); document.getElementById('profiler-results-title').textContent = p.name; const prog = document.getElementById('profiler-progress'); - const summary = document.getElementById('profiler-summary'); - const report = document.getElementById('profiler-report'); - report.innerHTML = ''; - summary.innerHTML = ''; + document.getElementById('profiler-summary').innerHTML = ''; + document.getElementById('profiler-report').innerHTML = ''; const results = []; - let passed = 0, failed = 0, errors = 0; for (let ri = 0; ri < p.rules.length; ri++) { const r = p.rules[ri]; prog.textContent = t('Checking') + ' ' + (ri + 1) + '/' + p.rules.length + ' — ' + r.path; - const res = await profilerRunRule(r); - results.push(res); + results.push(await profilerRunRule(r)); + } + prog.textContent = ''; + profilerResults = results; + profilerRenderResultsView(); +} + +function profilerRenderResultsView() { + if (!profilerResults) return; + let passed = 0, failed = 0, errors = 0; + for (const res of profilerResults) { if (res.status === 'pass') passed++; else if (res.status === 'fail') failed++; else errors++; } - prog.textContent = ''; - summary.innerHTML = '' + passed + ' ' + t('passed') + '' + + document.getElementById('profiler-summary').innerHTML = '' + passed + ' ' + t('passed') + '' + ' · ' + failed + ' ' + t('failed') + '' + ' · ' + errors + ' ' + t('errors') + ''; - report.innerHTML = profilerRenderReport(results); + document.getElementById('profiler-report').innerHTML = profilerRenderReport(profilerResults); } async function profilerRunRule(r) { @@ -8660,9 +8683,9 @@ function t(text) { function toggleLang() { currentLang = currentLang === 'en' ? 'ru' : 'en'; localStorage.setItem('lang', currentLang); -document.getElementById('lang-btn').textContent = currentLang.toUpperCase(); -translatePage(); + document.getElementById('lang-btn').textContent = currentLang.toUpperCase(); translatePage(); + refreshDynamicI18n(); updateHelpLink(); } @@ -8688,6 +8711,29 @@ function translatePage() { if (slogan) slogan.textContent = t('SIM OTA with a Human Face'); } +function isViewVisible(id) { + const el = document.getElementById(id); + return !!el && !el.classList.contains('hidden'); +} + +// Dynamic views build their labels with t() at render time; re-render the +// visible ones after a language switch so they don't stay in the old language. +function refreshDynamicI18n() { + if (isViewVisible('pysim-sub-profiler')) { + if (profilerView === 'list') { profilerRenderList(); profilerRenderSnapshotList(); } + else if (profilerView === 'editor') profilerRenderEditor(); + else if (profilerView === 'results') profilerRenderResultsView(); + else if (profilerView === 'snapshot') profilerRenderSnapshotData(); + } + if (isViewVisible('profiler-scan-modal')) profilerScanRefreshOptions(); + if (isViewVisible('scp80-sub-cards')) cardsRender(); + if (isViewVisible('pysim-sub-proactive')) { + pysimEventsRender(); + pysimProactiveLogRender(); + pysimPliRender(); + } +} + // Init language const userLang = (navigator.language || '').split('-')[0]; currentLang = localStorage.getItem('lang') || (userLang === 'ru' ? 'ru' : 'en'); diff --git a/frontend/sw.js b/frontend/sw.js index ee94e50..c1225d2 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'otaman-v83'; +const CACHE = 'otaman-v84'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js index 28e324c..11859f6 100644 --- a/frontend/tests/profiler.test.js +++ b/frontend/tests/profiler.test.js @@ -21,13 +21,14 @@ 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']; +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']; let code = ''; for (const f of FNS) code += extractFunc(html, f) + '\n'; code += extractFunc(html, 'profilerBuildFileRule', true) + '\n'; code += extractFunc(html, 'profilerRunRule', true) + '\n'; code += extractFunc(html, 'profilerScanCard', true) + '\n'; code += extractFunc(html, 'profilerBuildSnapshotFile', true) + '\n'; +code += "var _scanTarget = 'profile';\n"; code += html.match(/const PROFILER_MASK_PREFIX4_FIDS = \{[\s\S]*?\n\};/)[0] + '\n'; eval(code); @@ -942,3 +943,21 @@ test('profilerListSwitch toggles the profiles/snapshots tabs', () => { delete global.document; }); + +test('profilerScanRefreshOptions re-translates mask labels without touching checkbox state', () => { + global.t = s => (s === 'Match first 4 bytes for' ? 'FIRST4' : s); + const span = { textContent: '' }; + const cb = { + checked: true, + parentElement: { querySelector: sel => (sel === 'span' ? span : null) }, + getAttribute: () => '6F07', + }; + global.document = { + querySelectorAll: sel => (sel === '#profiler-scan-mask input[data-mask-fid]' ? [cb] : []), + }; + profilerScanRefreshOptions(); + assert.strictEqual(span.textContent, 'FIRST4 EF.IMSI'); + assert.strictEqual(cb.checked, true); + delete global.document; + delete global.t; +});