v1.8.0: proactive log overhaul, Expanded Script rename, proactive view sizing

Server:
- Track all TERMINAL RESPONSEs (chain, TP, pySim auto-handler, menu-respond
  link to paused entry) with shared _build_tr/_record_tr
- Log entry ids + cmd_num; server-side decode of fetched commands and TRs
  (compact PLI port); Result CTLV extracted as tr_result/tr_result_name
- _LoggingApduTracer captures TR SW for pySim auto-handler responses
- _DefaultProactiveHandler answers PLI with editor data (no re-request)
- VERSION 1.8.0

Frontend:
- Expandable proactive log rows (click to expand, state survives re-render)
- Response section: Result name + hex, decoded fields, width-restrained
  readonly hex inputs; no SW display
- Rename Expanded Script tab/pill/button/docs; proactive UICC view font
  sizing increase; card reader pills text-sm
This commit is contained in:
2026-08-19 18:04:08 +03:00
parent a916e8a255
commit 80a860549a
11 changed files with 563 additions and 124 deletions
+1 -1
View File
@@ -87,7 +87,7 @@
<h3 id="usim-rfm" class="text-lg font-medium mb-2">2.2 USIM RFM</h3>
<p class="mb-2">CLA = <code class="font-mono text-sm">00</code> (ETSI TS 102 221). Те же команды, что и SIM, но SELECT использует P1=<code class="font-mono text-sm">09</code>, P2=<code class="font-mono text-sm">0C</code> (выбор по FID из текущего каталога).</p>
<h3 id="ber-tlv" class="text-lg font-medium mb-2">2.3 Expanded BER-TLV</h3>
<h3 id="ber-tlv" class="text-lg font-medium mb-2">2.3 Expanded Script</h3>
<p class="mb-2">Построение формата Expanded Remote Application data по ETSI TS 102 226 &sect;5.2.1.</p>
<h4 id="ber-format" class="font-medium mb-1">Формат</h4>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
+1 -1
View File
@@ -87,7 +87,7 @@
<h3 id="usim-rfm" class="text-lg font-medium mb-2">2.2 USIM RFM</h3>
<p class="mb-2">CLA = <code class="font-mono text-sm">00</code> (ETSI TS 102 221). Same commands as SIM, but SELECT uses P1=<code class="font-mono text-sm">09</code>, P2=<code class="font-mono text-sm">0C</code> (select by FID from the current directory).</p>
<h3 id="ber-tlv" class="text-lg font-medium mb-2">2.3 Expanded BER-TLV</h3>
<h3 id="ber-tlv" class="text-lg font-medium mb-2">2.3 Expanded Script</h3>
<p class="mb-2">Builds the Expanded Remote Application data format per ETSI TS 102 226 &sect;5.2.1.</p>
<h4 id="ber-format" class="font-medium mb-1">Format</h4>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
+84 -59
View File
@@ -17,7 +17,7 @@
<div class="max-w-7xl mx-auto px-6 py-2">
<div class="flex items-center justify-between mb-3">
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v1.7.0</span></h1>
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v1.8.0</span></h1>
<div class="flex items-center gap-4">
<button id="install-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" style="display:none">INSTALL PWA [for offline use]</button>
<a href="https://github.com/anttro/otaman" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a>
@@ -41,7 +41,7 @@
<div class="flex justify-center gap-2 mb-3">
<button class="c-apdu-subtab px-4 py-1.5 text-sm rounded-full bg-blue-600 text-white" data-sub="sim" onclick="cApduSwitchSubtab('sim')">SIM RFM</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="usim" onclick="cApduSwitchSubtab('usim')">USIM RFM</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 BER-TLV</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="ram" onclick="cApduSwitchSubtab('ram')">RAM/GP</button>
</div>
<div id="c-apdu-sub-sim">
@@ -308,7 +308,7 @@
<div id="ber-rows" class="space-y-2"></div>
</div>
<button onclick="genBerTlv()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Generate BER-TLV">Generate BER-TLV</button>
<button onclick="genBerTlv()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Generate Expanded Script APDU">Generate Expanded Script APDU</button>
<textarea id="ber-result" 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 dark:text-slate-300"></textarea>
<button onclick="packToSp('ber-result')" id="ber-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>
@@ -865,12 +865,12 @@
</div>
<div id="pysim-body" class="hidden">
<div class="flex gap-1 mb-2">
<button class="pysim-subtab px-3 py-1 text-xs rounded-full bg-blue-600 text-white" data-pysim-sub="files" data-l10n="File manager">File manager</button>
<button class="pysim-subtab px-3 py-1 text-xs 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-pysim-sub="custom" data-l10n="Custom files">Custom files</button>
<button class="pysim-subtab px-3 py-1 text-xs 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-pysim-sub="cmd" data-l10n="pySim command line">pySim command line</button>
<button class="pysim-subtab px-3 py-1 text-xs 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-pysim-sub="apdu" data-l10n="Raw APDU">Raw APDU</button>
<button class="pysim-subtab px-3 py-1 text-xs 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-pysim-sub="proactive">Proactive UICC</button>
<button id="stk-menu-btn" onclick="stkMenuOpen()" class="pysim-subtab px-3 py-1 text-xs rounded-full bg-emerald-600 text-white hover:bg-emerald-700" style="display:none">STK: <span id="stk-menu-title"></span></button>
<button class="pysim-subtab px-3 py-1 text-sm rounded-full bg-blue-600 text-white" data-pysim-sub="files" data-l10n="File manager">File manager</button>
<button class="pysim-subtab px-3 py-1 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-pysim-sub="custom" data-l10n="Custom files">Custom files</button>
<button class="pysim-subtab px-3 py-1 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-pysim-sub="cmd" data-l10n="pySim command line">pySim command line</button>
<button class="pysim-subtab px-3 py-1 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-pysim-sub="apdu" data-l10n="Raw APDU">Raw APDU</button>
<button class="pysim-subtab px-3 py-1 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-pysim-sub="proactive">Proactive UICC</button>
<button id="stk-menu-btn" onclick="stkMenuOpen()" class="pysim-subtab px-3 py-1 text-sm rounded-full bg-emerald-600 text-white hover:bg-emerald-700" style="display:none">STK: <span id="stk-menu-title"></span></button>
</div>
<div id="pysim-sub-files">
@@ -934,25 +934,25 @@
<div id="pysim-sub-proactive" class="hidden">
<div class="flex gap-6">
<div>
<div class="mb-1 text-xs text-gray-500 dark:text-slate-400" data-l10n="STATUS and Polling">STATUS and Polling</div>
<div class="mb-1 text-sm text-gray-500 dark:text-slate-400" data-l10n="STATUS and Polling">STATUS and Polling</div>
<div class="flex items-center gap-2 mb-3">
<button id="pli-status-btn" onclick="pysimStatusPoll()" class="px-2 py-0.5 text-xs rounded bg-gray-600 text-white hover:bg-gray-700" data-l10n="Send STATUS">Send STATUS</button>
<span class="text-[10px] text-gray-500 dark:text-slate-400">|</span>
<span class="text-[10px] text-gray-500 dark:text-slate-400" data-l10n="Polling">Polling</span>
<button id="pli-pause-btn" onclick="pysimPollToggle()" class="px-2 py-0.5 text-xs rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600">OFF</button>
<span id="pli-poll-stat" class="text-[10px] text-gray-400"></span>
<button id="pli-status-btn" onclick="pysimStatusPoll()" class="px-2.5 py-1 text-sm rounded bg-gray-600 text-white hover:bg-gray-700" data-l10n="Send STATUS">Send STATUS</button>
<span class="text-xs text-gray-500 dark:text-slate-400">|</span>
<span class="text-xs text-gray-500 dark:text-slate-400" data-l10n="Polling">Polling</span>
<button id="pli-pause-btn" onclick="pysimPollToggle()" class="px-2.5 py-1 text-sm rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600">OFF</button>
<span id="pli-poll-stat" class="text-xs text-gray-400"></span>
</div>
<div class="mt-2 mb-2 text-xs text-gray-500 dark:text-slate-400" data-l10n="Events that the card monitors (SET UP EVENT LIST):">Events that the card monitors (SET UP EVENT LIST):</div>
<div id="pysim-event-list" class="text-xs font-mono text-gray-600 dark:text-slate-400"></div>
<div class="mt-4 mb-2 text-xs text-gray-500 dark:text-slate-400" data-l10n="Fetched proactive commands:">Fetched proactive commands:</div>
<div id="pysim-proactive-log" class="text-xs font-mono text-gray-600 dark:text-slate-400"></div>
<div class="mt-2 mb-2 text-sm text-gray-500 dark:text-slate-400" data-l10n="Events that the card monitors (SET UP EVENT LIST):">Events that the card monitors (SET UP EVENT LIST):</div>
<div id="pysim-event-list" class="text-sm font-mono text-gray-600 dark:text-slate-400"></div>
<div class="mt-4 mb-2 text-sm text-gray-500 dark:text-slate-400" data-l10n="Fetched proactive commands:">Fetched proactive commands:</div>
<div id="pysim-proactive-log" class="text-sm font-mono text-gray-600 dark:text-slate-400"></div>
</div>
<div>
<div class="flex justify-between items-center mb-2">
<span class="text-xs text-gray-500 dark:text-slate-400" data-l10n="PROVIDE LOCAL INFORMATION response data">PROVIDE LOCAL INFORMATION response data</span>
<button id="pli-save-btn" onclick="pysimPliSave()" class="px-2 py-0.5 text-xs rounded bg-emerald-600 text-white hover:bg-emerald-700" data-l10n="Save">Save</button>
<span class="text-sm text-gray-500 dark:text-slate-400" data-l10n="PROVIDE LOCAL INFORMATION response data">PROVIDE LOCAL INFORMATION response data</span>
<button id="pli-save-btn" onclick="pysimPliSave()" class="px-2.5 py-1 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700" data-l10n="Save">Save</button>
</div>
<div id="pysim-pli-dict" class="text-xs font-mono text-gray-600 dark:text-slate-400 max-h-[60vh] overflow-auto">
<div id="pysim-pli-dict" class="text-sm font-mono text-gray-600 dark:text-slate-400 max-h-[60vh] overflow-auto">
<span class="text-gray-400" data-l10n="Loading...">Loading...</span>
</div>
</div>
@@ -3909,7 +3909,7 @@ async function pysimEventsRender() {
html += '<div class="flex items-center gap-2 py-0.5"><span class="w-6 text-right text-gray-400">'
+ e.toString(16).padStart(2, '0').toUpperCase()
+ '</span> ' + esc(name)
+ '<button onclick="pysimEventSendForm(' + e + ')" class="ml-auto px-1.5 py-0.5 text-xs rounded bg-blue-600 text-white hover:bg-blue-700">' + t('Send') + '</button></div>';
+ '<button onclick="pysimEventSendForm(' + e + ')" class="ml-auto px-2 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700">' + t('Send') + '</button></div>';
});
el.innerHTML = html;
} catch (e) {
@@ -4148,6 +4148,32 @@ const CMD_QUALIFIER_SHORT = {
0x16:'CAG', 0x17:'RejSlice'},
};
let _pysimLogExpanded = new Set();
function pysimLogToggle(id) {
const panel = document.querySelector('[data-log-panel="' + id + '"]');
const tog = document.querySelector('[data-log-tog="' + id + '"]');
if (_pysimLogExpanded.has(id)) {
_pysimLogExpanded.delete(id);
if (panel) panel.classList.add('hidden');
if (tog) tog.textContent = '▶';
} else {
_pysimLogExpanded.add(id);
if (panel) panel.classList.remove('hidden');
if (tog) tog.textContent = '▼';
}
}
function pysimLogFields(decoded) {
if (!decoded || !decoded.length) return '';
let html = '';
decoded.forEach(f => {
html += '<div class="flex gap-1"><span class="text-gray-500 dark:text-slate-400 shrink-0">'
+ esc(f.label) + ':</span><span class="break-all">' + esc(f.value) + '</span></div>';
});
return html;
}
async function pysimProactiveLogRender() {
const el = document.getElementById('pysim-proactive-log');
el.innerHTML = '<span class="text-gray-400">' + t('Loading...') + '</span>';
@@ -4168,37 +4194,33 @@ async function pysimProactiveLogRender() {
if (qv) qual = ' [' + qv + ']';
}
}
html += '<div>'
+ '<div class="flex items-center gap-2 py-0.5">'
const expanded = _pysimLogExpanded.has(e.id);
html += '<div data-log-id="' + e.id + '" class="border-b border-gray-100 dark:border-slate-700/50">'
+ '<div class="flex items-center gap-2 py-0.5 cursor-pointer select-none" onclick="pysimLogToggle(' + e.id + ')">'
+ '<span class="w-12 text-right text-gray-400">' + e.elapsed + 's</span>'
+ '<span class="font-mono text-gray-400">0x' + e.type_hex.toUpperCase() + '</span>'
+ '<span class="truncate">' + esc(name) + '</span>'
+ (qual ? '<span class="text-[10px] text-gray-500 dark:text-slate-400">' + esc(qual) + '</span>' : '')
+ '</div>';
if (e.tr_hex) {
html += '<div class="text-[10px] pl-[5.5rem] text-gray-400 dark:text-slate-500">Response: ';
if (e.type_hex === '26' && e.qualifier) {
const codec = PLI_CODECS[e.qualifier];
if (codec && codec.decode) {
const vals = codec.decode(e.tr_hex);
if (vals) {
const parts = [];
codec.fields.forEach(f => {
if (vals[f.id] !== undefined) parts.push(f.label + ': ' + vals[f.id]);
});
html += esc(parts.length ? parts.join(', ') : e.tr_hex);
} else {
html += esc(e.tr_hex);
}
} else {
html += esc(e.tr_hex);
}
} else {
html += esc(e.tr_hex);
+ (qual ? '<span class="text-xs text-gray-500 dark:text-slate-400">' + esc(qual) + '</span>' : '')
+ '<span class="ml-auto text-gray-400" data-log-tog="' + e.id + '">' + (expanded ? '▼' : '▶') + '</span>'
+ '</div>'
+ '<div data-log-panel="' + e.id + '" class="' + (expanded ? '' : 'hidden') + ' ml-8 pl-2 border-l border-gray-300 dark:border-slate-600 mb-1 text-xs space-y-0.5">'
+ '<div class="text-gray-500 dark:text-slate-400">' + t('Command') + ':</div>'
+ pysimLogFields(e.cmd_decoded);
if (e.raw) html += '<input type="text" readonly value="' + esc(e.raw) + '" class="w-full max-w-md font-mono text-xs rounded border border-gray-300 dark:border-slate-600 px-2 py-1 bg-gray-100 dark:bg-slate-800 text-gray-600 dark:text-slate-400">';
html += '<div class="text-gray-500 dark:text-slate-400 mt-1">' + t('Response') + ':</div>';
if (e.tr_hex !== undefined) {
if (e.tr_result !== undefined) {
html += '<div class="flex gap-1"><span class="text-gray-500 dark:text-slate-400 shrink-0">' + t('Result') + ':</span>'
+ '<span class="break-all">' + (e.tr_result_name ? esc(e.tr_result_name) + ' (' + esc(e.tr_result) + ')' : esc(e.tr_result)) + '</span></div>';
}
html += '</div>';
if (e.tr_hex) {
html += pysimLogFields(e.tr_decoded);
html += '<input type="text" readonly value="' + esc(e.tr_hex) + '" class="w-full max-w-md font-mono text-xs rounded border border-gray-300 dark:border-slate-600 px-2 py-1 bg-gray-100 dark:bg-slate-800 text-gray-600 dark:text-slate-400">';
}
} else {
html += '<div class="text-gray-400">' + t('No response yet') + '</div>';
}
html += '</div>';
html += '</div></div>';
});
el.innerHTML = html;
} catch (err) {
@@ -4405,12 +4427,12 @@ async function pysimPliRender() {
const hasCodec = !!PLI_CODECS[q.code];
html += '<div data-pli-row="' + q.code + '" class="py-0.5 border-b border-gray-200 dark:border-slate-700">'
+ '<div class="flex items-baseline gap-1.5">'
+ '<span class="w-6 shrink-0 text-right text-gray-400 text-[10px]">' + q.code + '</span>'
+ '<span class="flex-1 min-w-0 text-[11px] leading-snug">' + esc(q.name) + '</span>'
+ '<span class="w-6 shrink-0 text-right text-gray-400 text-xs">' + q.code + '</span>'
+ '<span class="flex-1 min-w-0 text-[13px] leading-snug">' + esc(q.name) + '</span>'
+ '</div>'
+ '<div class="flex items-center gap-1 mt-0.5 ml-8">'
+ '<input type="text" id="pli-v-' + q.code + '" value="' + esc(val) + '" placeholder="hex"'
+ ' class="flex-1 min-w-0 border border-gray-300 dark:border-slate-600 text-[10px] rounded px-1 py-0.5 dark:bg-slate-800 font-mono">';
+ ' class="flex-1 min-w-0 border border-gray-300 dark:border-slate-600 text-xs rounded px-1.5 py-1 dark:bg-slate-800 font-mono">';
if (hasCodec) {
html += '<span onclick="pysimPliDecodeRow(\'' + q.code + '\')" id="pli-tog-' + q.code + '"'
+ ' class="cursor-pointer select-none text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">▶</span>';
@@ -4443,7 +4465,7 @@ function pysimPliDecodeRow(code) {
if (hex) {
values = codec.decode(hex);
if (!values) {
panel.innerHTML = '<span class="text-[10px] text-red-500">Invalid hex</span>';
panel.innerHTML = '<span class="text-xs text-red-500">Invalid hex</span>';
panel.classList.remove('hidden');
const tog = document.getElementById('pli-tog-' + code);
if (tog) tog.textContent = '▼';
@@ -4454,9 +4476,9 @@ function pysimPliDecodeRow(code) {
let html = '<div class="space-y-1 mb-1">';
codec.fields.forEach(f => {
html += '<div class="flex items-center gap-1">'
+ '<span class="text-[10px] w-12 text-gray-500 dark:text-slate-400 truncate" title="' + esc(f.label) + '">' + esc(f.label) + '</span>';
+ '<span class="text-xs w-12 text-gray-500 dark:text-slate-400 truncate" title="' + esc(f.label) + '">' + esc(f.label) + '</span>';
if (f.type === 'select') {
html += '<select id="pli-d-' + code + '-' + f.id + '" class="text-[10px] border border-gray-300 dark:border-slate-600 rounded px-1 py-0.5 dark:bg-slate-800 w-36">';
html += '<select id="pli-d-' + code + '-' + f.id + '" class="text-xs border border-gray-300 dark:border-slate-600 rounded px-1.5 py-1 dark:bg-slate-800 w-40">';
f.opts.forEach(o => html += '<option value="' + o.v + '"' + (String(values[f.id]) === String(o.v) ? ' selected' : '') + '>' + esc(o.l) + '</option>');
html += '</select>';
} else {
@@ -4465,14 +4487,14 @@ function pysimPliDecodeRow(code) {
+ (f.pattern ? ' pattern="' + f.pattern + '"' : '')
+ (f.placeholder ? ' placeholder="' + f.placeholder + '"' : '')
+ (f.min !== undefined ? ' min="' + f.min + '" max="' + f.max + '"' : '')
+ ' class="text-[10px] border border-gray-300 dark:border-slate-600 rounded px-1 py-0.5 dark:bg-slate-800 w-32 font-mono">';
+ ' class="text-xs border border-gray-300 dark:border-slate-600 rounded px-1.5 py-1 dark:bg-slate-800 w-40 font-mono">';
}
html += '</div>';
});
html += '</div>';
html += '<div class="flex gap-1">'
+ '<button onclick="pysimPliApplyRow(\'' + code + '\')" class="text-[10px] px-1.5 py-0.5 rounded bg-emerald-600 text-white hover:bg-emerald-700">Apply</button>'
+ '<button onclick="var p=document.querySelector(\'[data-pli-panel=\\\'' + code + '\\\']\');p.classList.add(\'hidden\');p.innerHTML=\'\';var t=document.getElementById(\'pli-tog-' + code + '\');if(t)t.textContent=\'▶\'" class="text-[10px] px-1.5 py-0.5 rounded border border-gray-300 dark:border-slate-600">Cancel</button>'
+ '<button onclick="pysimPliApplyRow(\'' + code + '\')" class="text-xs px-2 py-1 rounded bg-emerald-600 text-white hover:bg-emerald-700">Apply</button>'
+ '<button onclick="var p=document.querySelector(\'[data-pli-panel=\\\'' + code + '\\\']\');p.classList.add(\'hidden\');p.innerHTML=\'\';var t=document.getElementById(\'pli-tog-' + code + '\');if(t)t.textContent=\'▶\'" class="text-xs px-2 py-1 rounded border border-gray-300 dark:border-slate-600">Cancel</button>'
+ '</div>';
panel.innerHTML = html;
panel.classList.remove('hidden');
@@ -4630,10 +4652,13 @@ const LANG_RU = {
'Pack into Secured packet': 'Упаковать в Secured packet',
'Convert': 'Конвертировать',
'Decode': 'Декодировать',
'Generate BER-TLV': 'Сгенерировать BER-TLV',
'Generate Expanded Script APDU': 'Сгенерировать Expanded Script APDU',
'Generate Secure Packet': 'Сгенерировать Secure Packet',
'Conversion': 'Конвертация',
'Command': 'Команда',
'Response': 'Ответ',
'Result': 'Результат',
'No response yet': 'Ответа пока нет',
'Start with SELECT': 'Начать с SELECT',
'SELECT method': 'Метод SELECT',
'Path (hex)': 'Путь (hex)',
+2 -14
View File
@@ -764,14 +764,6 @@ video {
width: 6rem;
}
.w-32 {
width: 8rem;
}
.w-36 {
width: 9rem;
}
.w-40 {
width: 10rem;
}
@@ -1248,12 +1240,8 @@ video {
line-height: 2rem;
}
.text-\[10px\] {
font-size: 10px;
}
.text-\[11px\] {
font-size: 11px;
.text-\[13px\] {
font-size: 13px;
}
.text-lg {