v3.11.0: Cards tab redesign (list + editor), preset PIN/PUK codes
The Cards tab now shows the preset list by default; "Add preset" / "Edit" open a separate editor view (Save stores and returns to the list, Cancel discards; ramGoToPreset opens it too). The editor keeps the name/ICCID header (with From card) and groups its fields into bordered blocks: - PIN/ADM: the new optional card codes PIN1/PUK1/PIN2/PUK2 (plain text, 4-8 decimal digits) plus ADM, moved out of the top row; nothing consumes the PIN/PUK codes yet. - TAR: one uniform table (TAR, MSL, description); the first three rows are the fixed role entries with the immutable English descriptions "ISD compact format" / "UICC shared FS RFM compact format" / "ADF FS RFM compact format"; further rows are deletable with an optional description. - SCP80 (keyset editor) and SCP81 (PSK pair) as before. Store: PRESET_FIELDS gains pin1/puk1/pin2/puk2 (optional; whitespace stripped; 4-8 decimal digits validated on save and import), SCHEMA_VERSION 4, docs/help/AGENTS updated. TAR rows use unified ids (cards-tr-N-*), the editor switches via cardsShowList/cardsShowEditor. Tests: 665 Python + 783 frontend green; the whole inline PWA script syntax-checked. sw cache simple-v361.
This commit is contained in:
+194
-92
@@ -1593,49 +1593,95 @@
|
||||
|
||||
|
||||
<div id="tab-cards" class="tab-content hidden">
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mb-3" data-l10n="Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries several keysets, each with its own number and counter, and a TAR table — every TAR with its MSL (Minimum SPI1).">Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries several keysets, each with its own number and counter, and a TAR table — every TAR with its MSL (Minimum SPI1).</p>
|
||||
<!-- ===== preset list (default view) ===== -->
|
||||
<div id="cards-list-view">
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mb-3" data-l10n="Card presets are stored on the server. A preset carries the card codes (PIN/PUK/ADM), a TAR table (every TAR with its MSL) and several keysets, each with its own number and counter.">Card presets are stored on the server. A preset carries the card codes (PIN/PUK/ADM), a TAR table (every TAR with its MSL) and several keysets, each with its own number and counter.</p>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<button id="cards-new-btn" onclick="cardsNew()" class="px-4 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 whitespace-nowrap" data-l10n="Add preset">Add preset</button>
|
||||
<button onclick="cardsExportFile()" class="px-3 py-2 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300" data-l10n="Export to file">Export to file</button>
|
||||
<button onclick="document.getElementById('cards-file').click()" class="px-3 py-2 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300" data-l10n="Import from file">Import from file</button>
|
||||
</div>
|
||||
<div id="cards-list" class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Name">Name</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="ICCID">ICCID</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Keysets (number: KIc/KID — counter)">Keysets (number: KIc/KID \u2014 counter)</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="TARs (MSL)">TARs (MSL)</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="SCP81">SCP81</th>
|
||||
<th class="border-b border-gray-200 dark:border-slate-700"></th>
|
||||
</tr></thead>
|
||||
<tbody id="cards-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<input type="file" id="cards-file" accept=".json,application/json" class="hidden" onchange="cardsImportFile(this)">
|
||||
<div id="cards-io" class="hidden mt-2 text-xs text-gray-500 dark:text-slate-400"></div>
|
||||
</div>
|
||||
|
||||
<!-- ===== preset editor ===== -->
|
||||
<div id="cards-editor-view" class="hidden">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 id="cards-editor-title" class="text-lg font-semibold text-gray-800 dark:text-slate-200"></h3>
|
||||
<button onclick="cardsCancelEdit()" class="px-3 py-1.5 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300" data-l10n="Cancel">Cancel</button>
|
||||
</div>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<input id="cards-name" class="flex-1 border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="Name (e.g. Foobar SIM)">
|
||||
<input id="cards-iccid" class="flex-1 border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800 font-mono" placeholder="ICCID (optional)">
|
||||
<button id="cards-iccid-from-card" data-needs="card-iccid" onclick="cardsIccidFromCard()" class="px-3 py-1.5 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300 whitespace-nowrap disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="From card">From card</button>
|
||||
<input id="cards-adm" class="w-40 border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800 font-mono" placeholder="ADM (optional)">
|
||||
<button id="cards-add-btn" onclick="cardsAdd()" class="px-4 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 whitespace-nowrap" data-l10n="Add">Add</button>
|
||||
<button id="cards-cancel-btn" onclick="cardsCancelEdit()" class="hidden px-4 py-2.5 bg-gray-500 text-white text-sm font-medium rounded hover:bg-gray-600 whitespace-nowrap" data-l10n="Cancel">Cancel</button>
|
||||
<div class="flex-1">
|
||||
<label for="cards-name" class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Name">Name</label>
|
||||
<input id="cards-name" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="Name (e.g. Foobar SIM)">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label for="cards-iccid" class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="ICCID">ICCID</label>
|
||||
<div class="flex gap-1">
|
||||
<input id="cards-iccid" class="flex-1 border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800 font-mono" placeholder="ICCID (optional)">
|
||||
<button id="cards-iccid-from-card" data-needs="card-iccid" onclick="cardsIccidFromCard()" class="px-3 py-1.5 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300 whitespace-nowrap disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="From card">From card</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="border border-gray-200 dark:border-slate-700 rounded p-3 mb-3">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="SCP80 (GSM 03.48, ETSI TS 102 225)">SCP80 (GSM 03.48, ETSI TS 102 225)</legend>
|
||||
<div class="flex gap-3 mb-2 flex-wrap items-end">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="PIN/ADM">PIN/ADM</legend>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 mb-2">
|
||||
<div>
|
||||
<label for="cards-tar" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400" data-l10n="ISD TAR">ISD TAR</label>
|
||||
<input id="cards-tar" class="w-20 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono" maxlength="6" value="000000">
|
||||
<label for="cards-pin1" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400">PIN1</label>
|
||||
<input id="cards-pin1" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="8" inputmode="numeric" placeholder="4-8 digits">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cards-tar-msl" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400" data-l10n="MSL">MSL</label>
|
||||
<input id="cards-tar-msl" class="w-12 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="2" value="16" data-l10n-title="MSL (Minimum SPI1, one hex byte)">
|
||||
<label for="cards-puk1" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400">PUK1</label>
|
||||
<input id="cards-puk1" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="8" inputmode="numeric" placeholder="4-8 digits">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cards-uicc-tar" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400" data-l10n="UICC RFM TAR">UICC RFM TAR</label>
|
||||
<input id="cards-uicc-tar" class="w-20 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono" maxlength="6" value="B00000">
|
||||
<label for="cards-pin2" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400">PIN2</label>
|
||||
<input id="cards-pin2" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="8" inputmode="numeric" placeholder="4-8 digits">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cards-uicc-tar-msl" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400" data-l10n="MSL">MSL</label>
|
||||
<input id="cards-uicc-tar-msl" class="w-12 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="2" value="16" data-l10n-title="MSL (Minimum SPI1, one hex byte)">
|
||||
<label for="cards-puk2" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400">PUK2</label>
|
||||
<input id="cards-puk2" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="8" inputmode="numeric" placeholder="4-8 digits">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cards-usim-tar" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400" data-l10n="ADF RFM TAR">ADF RFM TAR</label>
|
||||
<input id="cards-usim-tar" class="w-20 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono" maxlength="6" value="B00001">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cards-usim-tar-msl" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400" data-l10n="MSL">MSL</label>
|
||||
<input id="cards-usim-tar-msl" class="w-12 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="2" value="16" data-l10n-title="MSL (Minimum SPI1, one hex byte)">
|
||||
<label for="cards-adm" class="block mb-0.5 text-xs text-gray-600 dark:text-slate-400">ADM</label>
|
||||
<input id="cards-adm" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono text-center" maxlength="8" placeholder="ADM (optional)">
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mb-2" data-l10n="Each TAR carries its MSL (Minimum SPI1) — the card checks it before the security processing (TS 102 226 8.2.1.3.2.4); the packet SPI1 is derived from it.">Each TAR carries its MSL (Minimum SPI1) — the card checks it before the security processing (TS 102 226 8.2.1.3.2.4); the packet SPI1 is derived from it.</p>
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400" data-l10n="More TARs — each with its MSL and an optional description">More TARs — each with its MSL and an optional description</span>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400" data-l10n="All codes are optional: PIN/PUK are 4-8 decimal digits (stored for later use by the card operations — not consumed yet); ADM is the administrator key.">All codes are optional: PIN/PUK are 4-8 decimal digits (stored for later use by the card operations — not consumed yet); ADM is the administrator key.</p>
|
||||
</fieldset>
|
||||
<fieldset class="border border-gray-200 dark:border-slate-700 rounded p-3 mb-3">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="TAR">TAR</legend>
|
||||
<table class="w-full text-sm mb-2">
|
||||
<thead><tr>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="TAR">TAR</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="MSL">MSL</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Description">Description</th>
|
||||
<th class="w-8 border-b border-gray-200 dark:border-slate-700"></th>
|
||||
</tr></thead>
|
||||
<tbody id="cards-tars"></tbody>
|
||||
</table>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400" data-l10n="The first three rows are the fixed role TARs; add further TARs as needed.">The first three rows are the fixed role TARs; add further TARs as needed.</span>
|
||||
<button type="button" onclick="cardsTarAdd()" class="px-2 py-0.5 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" data-l10n="Add TAR">Add TAR</button>
|
||||
</div>
|
||||
<div id="cards-tars"></div>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400" data-l10n="Each TAR carries its MSL (Minimum SPI1) — the card checks it before the security processing (TS 102 226 8.2.1.3.2.4); the packet SPI1 is derived from it.">Each TAR carries its MSL (Minimum SPI1) — the card checks it before the security processing (TS 102 226 8.2.1.3.2.4); the packet SPI1 is derived from it.</p>
|
||||
</fieldset>
|
||||
<fieldset class="border border-gray-200 dark:border-slate-700 rounded p-3 mb-3">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="SCP80 (GSM 03.48, ETSI TS 102 225)">SCP80 (GSM 03.48, ETSI TS 102 225)</legend>
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400" data-l10n="OTA keysets — each with its own number and counter">OTA keysets — each with its own number and counter</span>
|
||||
<button type="button" onclick="cardsKeysetAdd()" class="px-2 py-0.5 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" data-l10n="Add keyset">Add keyset</button>
|
||||
@@ -1651,25 +1697,11 @@
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400" data-l10n="SCP81 HTTP OTA: the listener picks the key by the PSK identity the card sends in the TLS handshake. Cards without a PSK are ignored by the listener.">SCP81 HTTP OTA: the listener picks the key by the PSK identity the card sends in the TLS handshake. Cards without a PSK are ignored by the listener.</p>
|
||||
</fieldset>
|
||||
<div id="cards-list" class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Name">Name</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="ICCID">ICCID</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Keysets (number: KIc/KID — counter)">Keysets (number: KIc/KID \u2014 counter)</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="TARs (MSL)">TARs (MSL)</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="SCP81">SCP81</th>
|
||||
<th class="border-b border-gray-200 dark:border-slate-700"></th>
|
||||
</tr></thead>
|
||||
<tbody id="cards-tbody"></tbody>
|
||||
</table>
|
||||
<div class="flex gap-2">
|
||||
<button id="cards-add-btn" onclick="cardsAdd()" class="px-4 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Save">Save</button>
|
||||
<button onclick="cardsCancelEdit()" class="px-4 py-2.5 bg-gray-500 text-white text-sm font-medium rounded hover:bg-gray-600" data-l10n="Cancel">Cancel</button>
|
||||
</div>
|
||||
<input type="file" id="cards-file" accept=".json,application/json" class="hidden" onchange="cardsImportFile(this)">
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
<button onclick="cardsExportFile()" class="px-2 py-1 text-xs rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Export to file">Export to file</button>
|
||||
<button onclick="document.getElementById('cards-file').click()" class="px-2 py-1 text-xs rounded bg-gray-600 text-white hover:bg-gray-700" data-l10n="Import from file">Import from file</button>
|
||||
</div>
|
||||
<div id="cards-io" class="hidden mt-2 text-xs text-gray-500 dark:text-slate-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1816,7 +1848,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 = '3.10.0';
|
||||
const SIMPLE_VERSION = '3.11.0';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -11855,46 +11887,72 @@ function cardsKeysetsFromForm() {
|
||||
return out;
|
||||
}
|
||||
|
||||
// ===== TAR table editor (Cards form) =====
|
||||
// The three role entries have fixed inputs (their labels are the immutable
|
||||
// descriptions); further TARs are free rows with an optional description.
|
||||
// Every entry carries the TAR and its MSL (Minimum SPI1, one hex byte).
|
||||
// ===== TAR table editor (Cards editor) =====
|
||||
// One uniform table: the first three rows are the fixed role entries - their
|
||||
// descriptions are the immutable role labels (English fixed text) and they
|
||||
// carry no remove button; further rows are free entries with an optional
|
||||
// description. Every row carries the TAR and its MSL (Minimum SPI1, one hex
|
||||
// byte).
|
||||
let _cardsTarCount = 0;
|
||||
|
||||
// The fixed role descriptions (spec role names; not translated).
|
||||
const CARDS_TAR_ROLE_DESC = {
|
||||
isd: 'ISD compact format',
|
||||
uiccRfm: 'UICC shared FS RFM compact format',
|
||||
usimRfm: 'ADF FS RFM compact format',
|
||||
};
|
||||
|
||||
function cardsTarRowHtml(e, i) {
|
||||
const cls = 'border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono';
|
||||
return '<div class="flex gap-2 items-center mb-1">' +
|
||||
'<input id="cards-ft-' + i + '-tar" class="w-20 ' + cls + ' text-center" maxlength="6" placeholder="TAR" value="' + esc(e.tar || '') + '">' +
|
||||
'<input id="cards-ft-' + i + '-msl" class="w-12 ' + cls + ' text-center" maxlength="2" placeholder="MSL" value="' + esc(e.msl || '') + '" title="' + esc(t('MSL (Minimum SPI1, one hex byte)')) + '">' +
|
||||
'<input id="cards-ft-' + i + '-desc" class="flex-1 ' + cls + '" placeholder="' + esc(t('Description (optional)')) + '" value="' + esc(e.desc || '') + '">' +
|
||||
'<button type="button" onclick="cardsTarRemove(' + i + ')" class="px-2 py-1 text-xs rounded bg-red-600 text-white hover:bg-red-700" title="' + esc(t('Remove TAR')) + '">\u2715</button>' +
|
||||
'</div>';
|
||||
const role = CARDS_TAR_ROLES[i] || '';
|
||||
let cells = '<td class="py-1 px-1"><input id="cards-tr-' + i + '-tar" class="w-24 ' + cls + ' text-center" maxlength="6" placeholder="TAR" value="' + esc(e.tar || '') + '"></td>' +
|
||||
'<td class="py-1 px-1"><input id="cards-tr-' + i + '-msl" class="w-14 ' + cls + ' text-center" maxlength="2" placeholder="MSL" value="' + esc(e.msl || '') + '" title="' + esc(t('MSL (Minimum SPI1, one hex byte)')) + '"></td>';
|
||||
if (role) {
|
||||
cells += '<td class="py-1 px-2 text-xs text-gray-600 dark:text-slate-400">' + esc(CARDS_TAR_ROLE_DESC[role] || '') + '</td>' +
|
||||
'<td class="py-1 px-1"></td>';
|
||||
} else {
|
||||
cells += '<td class="py-1 px-1"><input id="cards-tr-' + i + '-desc" class="w-full ' + cls + '" placeholder="' + esc(t('Description (optional)')) + '" value="' + esc(e.desc || '') + '"></td>' +
|
||||
'<td class="py-1 px-1"><button type="button" onclick="cardsTarRemove(' + i + ')" class="px-2 py-1 text-xs rounded bg-red-600 text-white hover:bg-red-700" title="' + esc(t('Remove TAR')) + '">\u2715</button></td>';
|
||||
}
|
||||
return '<tr>' + cells + '</tr>';
|
||||
}
|
||||
|
||||
// Render the whole table from a preset's TAR list (roles first, then free
|
||||
// rows); the role rows are always shown, with the pre-settled defaults when
|
||||
// the list carries none.
|
||||
function cardsTarRowsRender(entries) {
|
||||
const box = document.getElementById('cards-tars');
|
||||
if (!box) return;
|
||||
const list = entries || [];
|
||||
_cardsTarCount = list.length;
|
||||
box.innerHTML = list.map((e, i) => cardsTarRowHtml(e, i)).join('');
|
||||
const roles = CARDS_TAR_ROLES.map(role => {
|
||||
const e = list.find(x => x.role === role) || {};
|
||||
return { tar: e.tar || cardsRoleDefault(role), msl: e.msl || CARDS_MSL_DEFAULT };
|
||||
});
|
||||
const free = list.filter(e => !e.role);
|
||||
_cardsTarCount = free.length;
|
||||
box.innerHTML = roles.map((e, i) => cardsTarRowHtml(e, i)).join('') +
|
||||
free.map((e, i) => cardsTarRowHtml(e, CARDS_TAR_ROLES.length + i)).join('');
|
||||
}
|
||||
|
||||
function cardsTarAdd() {
|
||||
cardsTarRowsRender(cardsFreeTarsFromForm().concat([{ tar: '', msl: '', desc: '' }]));
|
||||
cardsTarRowsRender(cardsTarsFromForm().concat([{ tar: '', msl: '', desc: '' }]));
|
||||
}
|
||||
|
||||
// `i` is the table row index; the role rows are fixed and never removed.
|
||||
function cardsTarRemove(i) {
|
||||
const rows = cardsFreeTarsFromForm();
|
||||
if (i < CARDS_TAR_ROLES.length) return;
|
||||
const rows = cardsTarsFromForm();
|
||||
rows.splice(i, 1);
|
||||
cardsTarRowsRender(rows);
|
||||
}
|
||||
|
||||
// The free editor rows (fully empty rows are dropped).
|
||||
// The free table rows (fully empty rows are dropped).
|
||||
function cardsFreeTarsFromForm() {
|
||||
const out = [];
|
||||
for (let i = 0; i < _cardsTarCount; i++) {
|
||||
const idx = CARDS_TAR_ROLES.length + i;
|
||||
const get = id => {
|
||||
const el = document.getElementById('cards-ft-' + i + '-' + id);
|
||||
const el = document.getElementById('cards-tr-' + idx + '-' + id);
|
||||
return el ? String(el.value).trim() : '';
|
||||
};
|
||||
const e = { tar: get('tar').replace(/\s/g, '').toUpperCase(),
|
||||
@@ -11905,32 +11963,25 @@ function cardsFreeTarsFromForm() {
|
||||
return out;
|
||||
}
|
||||
|
||||
// The TAR table as preset entries: the three role entries from their fixed
|
||||
// inputs (the labels are the role descriptions) plus the free rows.
|
||||
// The TAR table as preset entries: the three role entries from the first three
|
||||
// rows (an emptied field keeps the pre-settled default) plus the free rows.
|
||||
function cardsTarsFromForm() {
|
||||
const roleInputs = { isd: 'cards-tar', uiccRfm: 'cards-uicc-tar', usimRfm: 'cards-usim-tar' };
|
||||
const out = CARDS_TAR_ROLES.map(role => {
|
||||
const tarEl = document.getElementById(roleInputs[role]);
|
||||
const mslEl = document.getElementById(roleInputs[role] + '-msl');
|
||||
const tar = (tarEl ? String(tarEl.value).trim() : '').replace(/\s/g, '').toUpperCase();
|
||||
const msl = (mslEl ? String(mslEl.value).trim() : '').replace(/\s/g, '').toUpperCase();
|
||||
// An emptied role field keeps the pre-settled default: the three role
|
||||
// entries are mandatory with a non-empty TAR and MSL.
|
||||
const out = CARDS_TAR_ROLES.map((role, i) => {
|
||||
const get = id => {
|
||||
const el = document.getElementById('cards-tr-' + i + '-' + id);
|
||||
return el ? String(el.value).trim() : '';
|
||||
};
|
||||
const tar = get('tar').replace(/\s/g, '').toUpperCase();
|
||||
const msl = get('msl').replace(/\s/g, '').toUpperCase();
|
||||
return { role: role, tar: tar || cardsRoleDefault(role),
|
||||
msl: msl || CARDS_MSL_DEFAULT, desc: '' };
|
||||
});
|
||||
return out.concat(cardsFreeTarsFromForm());
|
||||
}
|
||||
|
||||
// The role inputs back to their pre-settled defaults (TAR + MSL 16).
|
||||
// The role rows back to their pre-settled defaults (TAR + MSL 16) and no free
|
||||
// rows.
|
||||
function cardsTarsReset() {
|
||||
const roleInputs = { isd: 'cards-tar', uiccRfm: 'cards-uicc-tar', usimRfm: 'cards-usim-tar' };
|
||||
CARDS_TAR_ROLES.forEach(role => {
|
||||
const tarEl = document.getElementById(roleInputs[role]);
|
||||
if (tarEl) tarEl.value = cardsRoleDefault(role);
|
||||
const mslEl = document.getElementById(roleInputs[role] + '-msl');
|
||||
if (mslEl) mslEl.value = CARDS_MSL_DEFAULT;
|
||||
});
|
||||
cardsTarRowsRender([]);
|
||||
}
|
||||
|
||||
@@ -12091,6 +12142,10 @@ function cardsFormValues() {
|
||||
return {
|
||||
name: val('name'),
|
||||
iccid: val('iccid'),
|
||||
pin1: val('pin1').replace(/\s/g, ''),
|
||||
puk1: val('puk1').replace(/\s/g, ''),
|
||||
pin2: val('pin2').replace(/\s/g, ''),
|
||||
puk2: val('puk2').replace(/\s/g, ''),
|
||||
adm: val('adm').replace(/\s/g, '').toUpperCase(),
|
||||
tars: cardsTarsFromForm(),
|
||||
pskIdentity: val('psk-id'),
|
||||
@@ -12099,19 +12154,49 @@ function cardsFormValues() {
|
||||
};
|
||||
}
|
||||
|
||||
// Show the preset list (the Cards tab's default view).
|
||||
function cardsShowList() {
|
||||
const list = document.getElementById('cards-list-view');
|
||||
const editor = document.getElementById('cards-editor-view');
|
||||
if (list) list.classList.remove('hidden');
|
||||
if (editor) editor.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Show the preset editor.
|
||||
function cardsShowEditor() {
|
||||
const list = document.getElementById('cards-list-view');
|
||||
const editor = document.getElementById('cards-editor-view');
|
||||
if (list) list.classList.add('hidden');
|
||||
if (editor) editor.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function cardsSetFormMode() {
|
||||
const title = document.getElementById('cards-editor-title');
|
||||
if (title) {
|
||||
title.textContent = _cardsEditIdx === null
|
||||
? t('Add preset')
|
||||
: t('Edit preset') + ': ' + ((cards[_cardsEditIdx] || {}).name || '');
|
||||
}
|
||||
const btn = document.getElementById('cards-add-btn');
|
||||
if (btn) btn.textContent = _cardsEditIdx === null ? t('Add') : t('Save');
|
||||
const cancel = document.getElementById('cards-cancel-btn');
|
||||
if (cancel) cancel.classList.toggle('hidden', _cardsEditIdx === null);
|
||||
}
|
||||
|
||||
// "Add preset" in the list view: reset the form and open the editor.
|
||||
function cardsNew() {
|
||||
_cardsEditIdx = null;
|
||||
cardsClearForm();
|
||||
cardsSetFormMode();
|
||||
cardsShowEditor();
|
||||
const el = document.getElementById('cards-name');
|
||||
if (el) el.focus();
|
||||
}
|
||||
|
||||
function cardsClearForm() {
|
||||
['name','iccid','adm','psk-id','psk-key'].forEach(id => {
|
||||
['name','iccid','pin1','puk1','pin2','puk2','adm','psk-id','psk-key'].forEach(id => {
|
||||
const el = document.getElementById('cards-' + id);
|
||||
if (el) el.value = '';
|
||||
});
|
||||
// The three role entries keep their pre-settled defaults (TAR + MSL 16)
|
||||
// The three role rows keep their pre-settled defaults (TAR + MSL 16)
|
||||
cardsTarsReset();
|
||||
cardsKeysetRowsRender([]);
|
||||
}
|
||||
@@ -12119,6 +12204,14 @@ function cardsClearForm() {
|
||||
async function cardsAdd() {
|
||||
const v = cardsFormValues();
|
||||
if (!v.name) { alert(t('Name is required')); return; }
|
||||
// The optional card codes: PIN/PUK are 4-8 decimal digits; ADM stays
|
||||
// free-form (hex or ASCII digits)
|
||||
for (const k of ['pin1', 'puk1', 'pin2', 'puk2']) {
|
||||
if (v[k] && !/^[0-9]{4,8}$/.test(v[k])) {
|
||||
alert(t('PIN/PUK codes must be 4-8 decimal digits') + ': ' + k.toUpperCase());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!v.keysets.length) {
|
||||
alert(t('Define at least one keyset (KIc/KID and their keys)'));
|
||||
return;
|
||||
@@ -12191,24 +12284,24 @@ function cardsEdit(i) {
|
||||
const el = document.getElementById('cards-' + id);
|
||||
if (el) el.value = value == null ? '' : value;
|
||||
};
|
||||
set('name', c.name); set('iccid', c.iccid); set('adm', c.adm);
|
||||
set('tar', cardsRoleTar(c, 'isd'));
|
||||
set('tar-msl', cardsRoleMsl(c, 'isd'));
|
||||
set('uicc-tar', cardsRoleTar(c, 'uiccRfm'));
|
||||
set('uicc-tar-msl', cardsRoleMsl(c, 'uiccRfm'));
|
||||
set('usim-tar', cardsRoleTar(c, 'usimRfm'));
|
||||
set('usim-tar-msl', cardsRoleMsl(c, 'usimRfm'));
|
||||
set('name', c.name); set('iccid', c.iccid);
|
||||
set('pin1', c.pin1); set('puk1', c.puk1);
|
||||
set('pin2', c.pin2); set('puk2', c.puk2);
|
||||
set('adm', c.adm);
|
||||
set('psk-id', c.pskIdentity); set('psk-key', c.pskKey);
|
||||
cardsKeysetRowsRender(spKeysetList(c));
|
||||
cardsTarRowsRender(cardsTarList(c).filter(e => !e.role));
|
||||
cardsTarRowsRender(cardsTarList(c));
|
||||
cardsSetFormMode();
|
||||
document.getElementById('cards-name').focus();
|
||||
cardsShowEditor();
|
||||
const el = document.getElementById('cards-name');
|
||||
if (el) el.focus();
|
||||
}
|
||||
|
||||
function cardsCancelEdit() {
|
||||
_cardsEditIdx = null;
|
||||
cardsSetFormMode();
|
||||
cardsClearForm();
|
||||
cardsShowList();
|
||||
}
|
||||
|
||||
function ramCardIdxAfterRemove(idx, removedIdx) {
|
||||
@@ -20810,6 +20903,15 @@ const LANG_RU = {
|
||||
'Duplicate TAR in the preset': 'Повторяющийся TAR в пресете',
|
||||
'SPI1 is below the MSL of this TAR': 'SPI1 ниже MSL этого TAR',
|
||||
'(empty)': '(пусто)',
|
||||
'Add preset': 'Добавить пресет',
|
||||
'Edit preset': 'Редактирование пресета',
|
||||
'PIN/ADM': 'PIN/ADM',
|
||||
'TAR': 'TAR',
|
||||
'Description': 'Описание',
|
||||
'Card presets are stored on the server. A preset carries the card codes (PIN/PUK/ADM), a TAR table (every TAR with its MSL) and several keysets, each with its own number and counter.': 'Пресеты карт хранятся на сервере. Пресет содержит коды карты (PIN/PUK/ADM), таблицу TAR (у каждого TAR свой MSL) и несколько keysets — у каждого свой номер и счётчик.',
|
||||
'All codes are optional: PIN/PUK are 4-8 decimal digits (stored for later use by the card operations — not consumed yet); ADM is the administrator key.': 'Все коды необязательны: PIN/PUK — 4–8 десятичных цифр (сохраняются для будущего использования операциями карты — пока не используются); ADM — администраторский ключ.',
|
||||
'The first three rows are the fixed role TARs; add further TARs as needed.': 'Первые три строки — фиксированные роли TAR; дополнительные TAR добавляются по необходимости.',
|
||||
'PIN/PUK codes must be 4-8 decimal digits': 'Коды PIN/PUK должны состоять из 4–8 десятичных цифр',
|
||||
'Keysets (number: KIc/KID — counter)': 'Keysets (номер: KIc/KID — счётчик)',
|
||||
'must be one hex byte': 'должен быть одним hex-байтом',
|
||||
'KIc and KID must carry the same keyset number (TS 102 225 A.2)': 'KIc и KID должны содержать один и тот же номер keyset (TS 102 225 A.2)',
|
||||
|
||||
Reference in New Issue
Block a user