Style command + GET RESPONSE pairs in the APDU list; bump to 1.15.0

- Pair members (a command ending '61 XX' and its GET RESPONSE, via the
  server-side response_for link) get a subtle slate row tint
  (--pair-row, themed: rgba(148,163,184,.12) dark / rgba(100,116,139,.15)
  light); hover/selected still take precedence.
- Paired GET RESPONSE rows are prefixed with '↳' marking them as a
  continuation of the previous command; orphaned GRs stay plain.
- GET RESPONSE label uses cat-gr class: muted color, weight 600 like
  other command names.
- Map view untouched (it skips GRs); detail view already shows
  "Response for".

Version 1.15.0, SW cache simtrace-analyser-v20.
This commit is contained in:
catarrh committed 2026-08-23 14:54:18 +03:00
1 parent 0e99e73fda
commit f61ca9685c
5 files changed
+19 -7

No files matched your search

+15 -3
View File
@@ -38,6 +38,7 @@
--tr: #22d3ee; --tr: #22d3ee;
--envelope: #fbbf24; --envelope: #fbbf24;
--auth: #f87171; --auth: #f87171;
--pair-row: rgba(148, 163, 184, 0.12);
} }
[data-theme="light"] { [data-theme="light"] {
--bg: #fafafa; --bg: #fafafa;
@@ -68,6 +69,7 @@
--tr: #0e7490; --tr: #0e7490;
--envelope: #b45309; --envelope: #b45309;
--auth: #dc2626; --auth: #dc2626;
--pair-row: rgba(100, 116, 139, 0.15);
} }
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -114,6 +116,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
/* APDU list */ /* APDU list */
.apdu-list { flex: 1; overflow-y: auto; padding: 8px; } .apdu-list { flex: 1; overflow-y: auto; padding: 8px; }
.apdu-entry { display: flex; gap: 12px; padding: 3px 12px; border-bottom: 1px solid var(--bg-panel); cursor: pointer; align-items: baseline; } .apdu-entry { display: flex; gap: 12px; padding: 3px 12px; border-bottom: 1px solid var(--bg-panel); cursor: pointer; align-items: baseline; }
.apdu-entry.apdu-pair { background: var(--pair-row); }
.apdu-entry:hover { background: var(--bg-panel); } .apdu-entry:hover { background: var(--bg-panel); }
.apdu-entry.selected { background: var(--bg-input); } .apdu-entry.selected { background: var(--bg-input); }
[data-theme="light"] .apdu-entry.selected { background: #dbeafe; } [data-theme="light"] .apdu-entry.selected { background: #dbeafe; }
@@ -125,6 +128,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
.apdu-type.cat-tr { font-weight: 700; color: var(--tr); } .apdu-type.cat-tr { font-weight: 700; color: var(--tr); }
.apdu-type.cat-envelope { color: var(--envelope); } .apdu-type.cat-envelope { color: var(--envelope); }
.apdu-type.cat-auth { color: var(--auth); } .apdu-type.cat-auth { color: var(--auth); }
.apdu-type.cat-gr { color: var(--text-muted); font-weight: 600; }
.apdu-data { font-family: monospace; color: var(--text-dim); word-break: break-all; flex: 1; } .apdu-data { font-family: monospace; color: var(--text-dim); word-break: break-all; flex: 1; }
.apdu-sw { font-family: monospace; color: var(--text-muted); white-space: nowrap; min-width: 52px; } .apdu-sw { font-family: monospace; color: var(--text-muted); white-space: nowrap; min-width: 52px; }
.apdu-sw.sw-ok { color: var(--green); } .apdu-sw.sw-ok { color: var(--green); }
@@ -1084,6 +1088,7 @@ function command_cat(ins) {
if (ins === '14' || ins === '10' || ins === 'aa') return 'tr'; // TERMINAL RESPONSE / PROFILE / CAPABILITY if (ins === '14' || ins === '10' || ins === 'aa') return 'tr'; // TERMINAL RESPONSE / PROFILE / CAPABILITY
if (ins === 'c2') return 'envelope'; // ENVELOPE if (ins === 'c2') return 'envelope'; // ENVELOPE
if (ins === '88' || ins === '89') return 'auth'; // AUTHENTICATE if (ins === '88' || ins === '89') return 'auth'; // AUTHENTICATE
if (ins === 'c0') return 'gr'; // GET RESPONSE
return null; return null;
} }
@@ -1349,9 +1354,16 @@ function renderMessages(skipScroll) {
(cat ? ' cat-' + cat : ''); (cat ? ' cat-' + cat : '');
const errs = error_names(m); const errs = error_names(m);
const errBadge = errs ? `<span class="apdu-err" title="${errs.join(', ')}">⚠</span>` : ''; const errBadge = errs ? `<span class="apdu-err" title="${errs.join(', ')}">⚠</span>` : '';
return `<div class="apdu-entry" data-idx="${idx}"> // Command + GET RESPONSE pairs: subtle tint on both members,
// ↳ prefix marks the response as a continuation of the previous
// command (server sets response_for when prior SW1 was '61').
const isGR = m.type === 'tpdu' && m.decoded &&
m.decoded.ins_name === 'GET RESPONSE' && m.decoded.response_for;
const pairCls = (isGR || isOriginalCommand(idx)) ? ' apdu-pair' : '';
const grLabel = isGR ? '↳ ' + label : label;
return `<div class="apdu-entry${pairCls}" data-idx="${idx}">
<span class="apdu-time">${fmt_elapsed(m.elapsed)}</span> <span class="apdu-time">${fmt_elapsed(m.elapsed)}</span>
<span class="apdu-type ${typeCls}">${label}</span> <span class="apdu-type ${typeCls}">${grLabel}</span>
<span class="apdu-sw ${sw_class(m)}">${sw_text(m)}</span> <span class="apdu-sw ${sw_class(m)}">${sw_text(m)}</span>
<span class="apdu-data">${cmd_description(m)}</span> <span class="apdu-data">${cmd_description(m)}</span>
${errBadge} ${errBadge}
@@ -1869,7 +1881,7 @@ function onFilterChange() {
} }
// ──────────────────── Versioning ──────────────────── // ──────────────────── Versioning ────────────────────
const PWA_VERSION = '1.14.0'; const PWA_VERSION = '1.15.0';
const MIN_SERVER_VERSION = '1.0.0'; const MIN_SERVER_VERSION = '1.0.0';
function parseVersion(v) { function parseVersion(v) {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v17'; const CACHE = 'simtrace-analyser-v20';
const PRECACHE = [ const PRECACHE = [
'index.html', 'index.html',
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
[project] [project]
name = "simtrace2-pysniff" name = "simtrace2-pysniff"
version = "1.14.0" version = "1.15.0"
description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)" description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)"
readme = {file = "README.md", content-type = "text/markdown"} readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"} license = {text = "GPL-2.0-or-later"}
+1 -1
View File
@@ -6,7 +6,7 @@ from .gsmtap import GsmtapSender, GsmtapReceiver
from .pcap import PcapWriter from .pcap import PcapWriter
from .dump import FileDumper, format_message from .dump import FileDumper, format_message
__version__ = '1.14.0' __version__ = '1.15.0'
__all__ = [ __all__ = [
'SniffSession', 'SniffSession',
'DeviceDisconnected', 'DeviceDisconnected',
+1 -1
View File
@@ -197,7 +197,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase): class TestVersion(unittest.TestCase):
def test_package_version(self): def test_package_version(self):
from simtrace2_pysniff import __version__ from simtrace2_pysniff import __version__
self.assertEqual(__version__, '1.14.0') self.assertEqual(__version__, '1.15.0')
def test_status_includes_version(self): def test_status_includes_version(self):
import os import os