ui: Parser pill — C-APDU / R-APDU parsers as sub-pills; R-APDU rename (v3.5.0)

Remote APDU now has six top-level pills (SIM RFM, USIM RFM, RAM/GP,
Expanded Script, Push commands, Parser); the C-APDU and R-APDU parsers are
nested panels behind the Parser pill, switched by parserSwitchSubtab().

- "Response parser" renamed to "R-APDU parser" (RU: «Разбор R-APDU»), the
  umbrella is "Parser" / «Разбор»; the C-APDU panel keeps its id and label.
- cApduSwitchSubtab() routes nested names through the Parser pill first, so
  pysimSendOta()'s post-send jump keeps working; the help deep-link anchor
  follows the selected parser (c-apdu-parser / response-parser) and the Push
  commands pill now links to the renamed #push-commands section.
- The small C-APDU panel moved next to the R-APDU panel so both live in the
  #c-apdu-sub-parser container; html.test.js checks the nesting and sub-pills.
- Pill lists updated in help EN/RU (2.6 note, 2.8 heading), READMEs, AGENTS;
  sw cache -> simple-v248.
This commit is contained in:
2026-09-23 09:55:40 +03:00
parent c011c02f8e
commit 731cb53105
7 changed files with 77 additions and 33 deletions
+48 -14
View File
@@ -55,8 +55,7 @@
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-sub="ram" onclick="cApduSwitchSubtab('ram')">RAM/GP</button>
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-sub="ber" onclick="cApduSwitchSubtab('ber')">Expanded Script</button>
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-sub="httpota" onclick="cApduSwitchSubtab('httpota')" data-l10n="Push commands">Push commands</button>
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-sub="parse" onclick="cApduSwitchSubtab('parse')" data-l10n="C-APDU Parser">C-APDU Parser</button>
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-sub="response" onclick="cApduSwitchSubtab('response')" data-l10n="Response parser">Response parser</button>
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-sub="parser" onclick="cApduSwitchSubtab('parser')" data-l10n="Parser">Parser</button>
</div>
<div id="c-apdu-sub-sim">
<div id="chain-sim-rows"></div>
@@ -172,15 +171,6 @@
<textarea id="chain-ram-expanded" rows="3" readonly class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 bg-gray-100 dark:bg-slate-800 mb-2" placeholder="Expanded script forms (AA / AE80)"></textarea>
<button onclick="packToSp('chain-ram-preview')" id="chain-ram-pack-btn" disabled class="mb-3 px-5 py-2.5 bg-emerald-600 text-white text-sm font-medium rounded hover:bg-emerald-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Pack into Secured packet">Pack into Secured packet</button>
</div>
<div id="c-apdu-sub-parse" class="hidden">
<div class="mb-3">
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="APDU hex">APDU hex</label>
<textarea id="parse-input" rows="4" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="80 E6 0C 00 ..."></textarea>
</div>
<button onclick="parseHex()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Decode">Decode</button>
<div id="parse-output"></div>
</div>
<div id="c-apdu-sub-httpota" class="hidden">
<div class="flex flex-wrap gap-2 mb-3">
<button id="hota-form-btn-trigger" onclick="hotaSetForm('trigger')" class="px-4 py-1.5 text-sm rounded-full bg-blue-600 text-white" data-l10n="Trigger (Push SMS)">Trigger (Push SMS)</button>
@@ -371,6 +361,20 @@
</div>
</div>
<div id="c-apdu-sub-parser" class="hidden">
<div class="flex flex-wrap gap-2 mb-3">
<button id="parser-btn-parse" onclick="parserSwitchSubtab('parse')" class="px-4 py-1.5 text-sm rounded-full bg-blue-600 text-white" data-l10n="C-APDU Parser">C-APDU Parser</button>
<button id="parser-btn-response" onclick="parserSwitchSubtab('response')" class="px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-l10n="R-APDU parser">R-APDU parser</button>
</div>
<div id="c-apdu-sub-parse" class="hidden">
<div class="mb-3">
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="APDU hex">APDU hex</label>
<textarea id="parse-input" rows="4" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="80 E6 0C 00 ..."></textarea>
</div>
<button onclick="parseHex()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Decode">Decode</button>
<div id="parse-output"></div>
</div>
<div id="c-apdu-sub-response" class="hidden">
<div class="mb-3">
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Command">Command</label>
@@ -441,6 +445,7 @@
<div id="resp-output" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 bg-gray-100 dark:bg-slate-800 whitespace-pre-wrap" style="min-height:100px"></div>
</div>
</div>
</div>
<div class="w-1/4">
<div class="border border-gray-300 dark:border-slate-600 rounded p-3 bg-gray-50 dark:bg-slate-800">
<select id="conv-type" onchange="updateConvFields()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 mb-2">
@@ -1569,7 +1574,34 @@ function scp80SwitchSubtab(name) {
setHelpAnchor(scp80HelpAnchor);
}
// Top-level Remote APDU subtabs; the C-APDU and R-APDU parsers are nested
// panels behind the Parser pill (parserSwitchSubtab).
const C_APDU_SUBTABS = ['sim', 'usim', 'ram', 'ber', 'httpota', 'parser'];
const PARSER_HELP_ANCHORS = { parse: 'c-apdu-parser', response: 'response-parser' };
let _parserSub = 'parse';
function parserSwitchSubtab(name) {
_parserSub = (name === 'response') ? 'response' : 'parse';
[['parse', 'parser-btn-parse'], ['response', 'parser-btn-response']].forEach(pair => {
const btn = document.getElementById(pair[1]);
if (!btn) return;
btn.className = 'px-4 py-1.5 text-sm rounded-full ' + (pair[0] === _parserSub
? 'bg-blue-600 text-white'
: 'bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600');
});
document.getElementById('c-apdu-sub-parse').classList.toggle('hidden', _parserSub !== 'parse');
document.getElementById('c-apdu-sub-response').classList.toggle('hidden', _parserSub !== 'response');
setHelpAnchor(PARSER_HELP_ANCHORS[_parserSub]);
}
function cApduSwitchSubtab(name) {
// Nested names select the Parser pill first, so callers can keep
// addressing the panels directly (e.g. after a successful send).
if (name === 'parse' || name === 'response') {
cApduSwitchSubtab('parser');
parserSwitchSubtab(name);
return;
}
document.querySelectorAll('.c-apdu-subtab').forEach(btn => {
const active = btn.dataset.sub === name;
btn.classList.toggle('bg-blue-600', active);
@@ -1579,11 +1611,12 @@ function cApduSwitchSubtab(name) {
btn.classList.toggle('text-gray-700', !active);
btn.classList.toggle('dark:text-slate-300', !active);
});
['sim', 'usim', 'ram', 'ber', 'parse', 'httpota', 'response'].forEach(s => {
C_APDU_SUBTABS.forEach(s => {
const el = document.getElementById('c-apdu-sub-' + s);
if (el) el.classList.toggle('hidden', s !== name);
});
setHelpAnchor({sim:'sim-rfm', usim:'usim-rfm', ber:'ber-tlv', ram:'ram-gp', parse:'c-apdu-parser', httpota:'http-ota', response:'response-parser'}[name] || 'c-apdu');
setHelpAnchor({sim:'sim-rfm', usim:'usim-rfm', ber:'ber-tlv', ram:'ram-gp', httpota:'push-commands'}[name] || 'c-apdu');
if (name === 'parser') parserSwitchSubtab(_parserSub);
}
function phoneSwitchSubtab(name) {
@@ -14481,7 +14514,8 @@ const LANG_RU = {
'KID key error: ': 'Ошибка KID key: ',
'Error: ciphering requires KIc key': 'Ошибка: ciphering requires KIc key',
'Error: MAC requires KID key': 'Ошибка: MAC requires KID key',
'Response parser': 'Парсер ответов',
'Parser': 'Разбор',
'R-APDU parser': 'Разбор R-APDU',
'C-APDU Parser': 'Разбор C-APDU',
'Cards': 'Карты',
'Card reader': 'Картридер',