v3.29.1: the per-row keysets in the outfile preview

The import applied one global default keyset to every TAR entry of every
imported card.  The preview's TAR table (TAR + MSL, the three role rows +
"+ TAR") now has a keyset select per row, mirroring the preset editor: an
empty "— none —" plus the keysets found in the file with their KIc/KID,
the first preselected, and the editor's amber warning when a non-zero MSL
has no keyset.  Each TAR's packets then use exactly the chosen keyset.

- PWA: the global "Default keyset for the TARs" select (and the request's
  tar_keyset) is gone; cardsOutfileTarsFromDom/FromForm carry each row's
  keyset; cardsOutfileKeysets() became cardsOutfileKeysetOptions() (the
  union with the KIc/KID bytes for the labels).
- Server: a `tars` entry's own `keyset` (1-15; an unusable value is a 400)
  wins, then the optional global `tar_keyset` (the API shorthand), then the
  card's first keyset - parse_outfile no longer clobbers per-entry keysets
  with the card-level fallback.  presets.normalise_keyset_ref() is
  module-level (one rule for the store and the parser).
- Docs: help EN/RU, api.md (the tars entry keyset + the global fallback),
  the repo AGENTS.  Tests: the per-entry/global/card fallbacks, the
  bad-keyset 400, the options helper, the row select and round-trip, the
  import body.  925 frontend + 944 Python; sw cache simple-v448.
This commit is contained in:
2026-10-15 00:40:16 +03:00
parent cf7ee77a9e
commit 8a66ea5a0a
12 changed files with 168 additions and 95 deletions
+20 -20
View File
@@ -218,6 +218,25 @@ def tar_keyset(preset, tar_hex):
return (tar_entry(preset, tar_hex) or {}).get('keyset') or None
def normalise_keyset_ref(v):
"""A TAR's default keyset number (int 1-15) or None. Accepts an int and
the decimal ('1'..'15') or hex-ish ('01'..'0F') string forms; an unusable
value becomes None - the validation reports a missing keyset where one is
mandatory. Shared by the store and the outfile import (v3.29.1)."""
if v is None or v == '':
return None
s = str(v).strip().upper()
if re.fullmatch(r'[0-9]{1,2}', s):
n = int(s, 10)
if 1 <= n <= KVN_MAX:
return n
if re.fullmatch(r'[0-9A-F]{1,2}', s):
n = int(s, 16)
if 1 <= n <= KVN_MAX:
return n
return None
class PresetError(Exception):
"""A refused mutation (validation, duplicate ICCID, ...)."""
@@ -360,7 +379,7 @@ class PresetStore:
'tar': re.sub(r'\s', '', str(t.get('tar') or '')).upper(),
'msl': re.sub(r'\s', '', str(t.get('msl') or '')).upper(),
'desc': str(t.get('desc') or '').strip(),
'keyset': self._normalise_keyset_ref(t.get('keyset')),
'keyset': normalise_keyset_ref(t.get('keyset')),
})
else:
msl = re.sub(r'\s', '', str(fields.get('spi1') or '')).upper()
@@ -392,25 +411,6 @@ class PresetStore:
'keyset': e.get('keyset')})
return out
@staticmethod
def _normalise_keyset_ref(v):
"""A TAR's default keyset number (int 1-15) or None. Accepts an int
and the decimal ('1'..'15') or hex-ish ('01'..'0F') string forms; an
unusable value becomes None - the validation reports a missing keyset
where one is mandatory (v3.26.0)."""
if v is None or v == '':
return None
s = str(v).strip().upper()
if re.fullmatch(r'[0-9]{1,2}', s):
n = int(s, 10)
if 1 <= n <= KVN_MAX:
return n
if re.fullmatch(r'[0-9A-F]{1,2}', s):
n = int(s, 16)
if 1 <= n <= KVN_MAX:
return n
return None
def _normalise_keyset(self, ks):
"""One keyset, cleaned. The counter is kept as typed here and
validated + padded in _validate_keyset() - a bad value must be