Topbar: title trim, shorter search, the view selector pinned above the panel (v2.6.3)

- the header title drops its "UICC" prefix — "GSMTAP trace analyser" is enough
- the trace topbar's search input is half its previous width (flex basis
  33.333% -> 16.667%); the session name now ellipsizes instead of wrapping
  the tighter row
- the side-panel view selector moved out of the scrollable side panel into
  the trace topbar: right-aligned over the panel at the panel's width
  (--side-panel-w: 260px, the negative right margin cancelling the topbar's
  16px padding so the edges line up), so it stays visible at all times while
  the panel scrolls
- tests: the topbar/selector position pins (the Files-view wiring test and
  style.test.js), the shared width variable, the halved search basis and the
  title; 527 Python + 61 Node tests
- README/README_RUS: the selector's new place; PWA_VERSION/pyproject/
  version.py 2.6.3, SW cache simtrace-analyser-v77
This commit is contained in:
catarrh committed 2026-10-17 23:30:37 +03:00
1 parent 76c5647dd4
commit 475c136749
9 files changed
+65 -20

No files matched your search

+4 -2
View File
@@ -199,8 +199,10 @@ in the stored wire shape and are skipped with a note in the import report.
### Files view (per-file tracking) ### Files view (per-file tracking)
The Trace tab's side panel has a view selector (*Map* / *Stats* / **Files**; The Trace tab has a view selector (*Map* / *Stats* / **Files**) in its topbar,
a drop-down, since three tab captions no longer fit). The Files view is a right-aligned over the side panel with the panel's width, so it stays visible
while the panel scrolls (a drop-down, since three tab captions no longer
fit). The Files view is a
trace-wide timeline of file operations — every **Read**, **Update** and trace-wide timeline of file operations — every **Read**, **Update** and
**Changed on card** row shows the same file name as the Map view, and clicking **Changed on card** row shows the same file name as the Map view, and clicking
a row jumps straight to the APDU in the trace and leaves it highlighted a row jumps straight to the APDU in the trace and leaves it highlighted
+4 -2
View File
@@ -203,8 +203,10 @@ Application Layer* — два отдельных поля выбора, имен
### Просмотр файлов (сопровождение состояния файлов) ### Просмотр файлов (сопровождение состояния файлов)
В боковой панели вкладки «Трассировка» появился выбор вида (*Map* / *Stats* / В верхней панели вкладки «Трассировка» появился выбор вида (*Map* / *Stats* /
**Files** — выпадающий список, три подписи-вкладки уже не помещались). Вид **Files**) — он выровнен по правому краю над боковой панелью и имеет её
ширину, поэтому виден всё время, пока панель прокручивается (выпадающий
список, три подписи-вкладки уже не помещались). Вид
Files — это таймлайн файловых операций по всей трассе: строки **Read**, Files — это таймлайн файловых операций по всей трассе: строки **Read**,
**Update** и **Changed on card** показывают те же имена файлов, что и вид Map, **Update** и **Changed on card** показывают те же имена файлов, что и вид Map,
а щелчок по строке переходит прямо к APDU в трассе и оставляет его а щелчок по строке переходит прямо к APDU в трассе и оставляет его
+20 -11
View File
@@ -9,6 +9,11 @@
<link rel="icon" type="image/png" href="favicon.png"> <link rel="icon" type="image/png" href="favicon.png">
<meta name="theme-color" content="#0f172a"> <meta name="theme-color" content="#0f172a">
<style> <style>
:root {
/* Layout sizes (theme-independent) */
--side-panel-w: 260px;
}
:root, [data-theme="dark"] { :root, [data-theme="dark"] {
--bg: #0f172a; --bg: #0f172a;
--bg-panel: #1e293b; --bg-panel: #1e293b;
@@ -104,10 +109,11 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
/* Trace view */ /* Trace view */
.trace-topbar { display: flex; gap: 12px; padding: 12px 16px; background: var(--bg-panel); align-items: center; flex-shrink: 0; } .trace-topbar { display: flex; gap: 12px; padding: 12px 16px; background: var(--bg-panel); align-items: center; flex-shrink: 0; }
.trace-topbar input { flex: 0 1 33.333%; padding: 8px 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); } .trace-topbar input { flex: 0 1 16.667%; padding: 8px 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); }
.trace-topbar input::placeholder { color: var(--text-muted); } .trace-topbar input::placeholder { color: var(--text-muted); }
.trace-topbar select { padding: 8px 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); } .trace-topbar select { padding: 8px 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); }
.trace-topbar .filter-label { color: var(--text-muted); font-size: 13px; white-space: nowrap; } .trace-topbar .filter-label { color: var(--text-muted); font-size: 13px; white-space: nowrap; }
.trace-topbar #session-name-display { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trace-topbar button { padding: 8px 16px; background: var(--accent); color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; } .trace-topbar button { padding: 8px 16px; background: var(--accent); color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; }
.trace-topbar button:hover { background: var(--accent-hover); } .trace-topbar button:hover { background: var(--accent-hover); }
.trace-topbar button.stop { background: var(--danger); color: #fff; } .trace-topbar button.stop { background: var(--danger); color: #fff; }
@@ -171,7 +177,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
.detail-body td.name { color: var(--accent); font-weight: 600; } .detail-body td.name { color: var(--accent); font-weight: 600; }
/* Side panel */ /* Side panel */
.side-panel { width: 260px; background: var(--bg-panel); padding: 12px; overflow-y: auto; flex-shrink: 0; border-left: 1px solid var(--bg-input); } .side-panel { width: var(--side-panel-w); background: var(--bg-panel); padding: 12px; overflow-y: auto; flex-shrink: 0; border-left: 1px solid var(--bg-input); }
.side-panel h3 { color: var(--accent); margin-bottom: 8px; font-size: 13px; text-transform: uppercase; letter-spacing: 1px; } .side-panel h3 { color: var(--accent); margin-bottom: 8px; font-size: 13px; text-transform: uppercase; letter-spacing: 1px; }
.side-item { display: flex; justify-content: space-between; padding: 3px 8px; cursor: pointer; border-radius: 4px; font-size: 13px; } .side-item { display: flex; justify-content: space-between; padding: 3px 8px; cursor: pointer; border-radius: 4px; font-size: 13px; }
.side-item:hover { background: var(--bg-input); } .side-item:hover { background: var(--bg-input); }
@@ -179,8 +185,11 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
.side-tabs { display: flex; margin-bottom: 8px; border-bottom: 1px solid var(--bg-input); } .side-tabs { display: flex; margin-bottom: 8px; border-bottom: 1px solid var(--bg-input); }
.side-tab { flex: 1; padding: 6px 0; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; border-bottom: 2px solid transparent; } .side-tab { flex: 1; padding: 6px 0; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; border-bottom: 2px solid transparent; }
.side-tab.active { color: var(--accent); border-bottom-color: var(--accent); } .side-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.side-view-select { width: 100%; margin-bottom: 8px; padding: 6px 8px; background: var(--bg-input); color: var(--text); /* Side-panel view selector: it lives in the trace topbar, right-aligned over
border: 1px solid var(--border); border-radius: 4px; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; cursor: pointer; } the side panel with the panel's width, so it stays visible while the panel
scrolls. The negative right margin cancels the topbar's 16px padding, so
its right edge lines up with the panel's. */
.trace-topbar .side-view-select { width: var(--side-panel-w); flex: 0 0 auto; margin-left: auto; margin-right: -16px; }
.file-op { display: flex; align-items: baseline; gap: 6px; padding: 3px 8px; cursor: pointer; border-radius: 4px; font-size: 13px; word-break: break-word; } .file-op { display: flex; align-items: baseline; gap: 6px; padding: 3px 8px; cursor: pointer; border-radius: 4px; font-size: 13px; word-break: break-word; }
.file-op:hover { background: var(--bg-input); } .file-op:hover { background: var(--bg-input); }
.file-op-read { color: var(--text-muted); } .file-op-read { color: var(--text-muted); }
@@ -330,7 +339,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
<div class="header-tab" data-tab="sessions" data-l10n="Sessions">Sessions</div> <div class="header-tab" data-tab="sessions" data-l10n="Sessions">Sessions</div>
<div class="header-tab" data-tab="settings" data-l10n="Settings">Settings</div> <div class="header-tab" data-tab="settings" data-l10n="Settings">Settings</div>
</div> </div>
<div class="header-title">UICC <a href="https://osmocom.org/projects/baseband/wiki/GSMTAP" target="_blank" rel="noopener">GSMTAP</a> <span data-l10n="trace analyser">trace analyser</span> <span class="pwa-version" id="pwa-version"></span></div> <div class="header-title"><a href="https://osmocom.org/projects/baseband/wiki/GSMTAP" target="_blank" rel="noopener">GSMTAP</a> <span data-l10n="trace analyser">trace analyser</span> <span class="pwa-version" id="pwa-version"></span></div>
<div class="header-right"> <div class="header-right">
<span class="capture-chip" id="capture-chip" style="display:none"></span> <span class="capture-chip" id="capture-chip" style="display:none"></span>
<button id="install-btn" data-l10n="Install PWA">Install PWA</button> <button id="install-btn" data-l10n="Install PWA">Install PWA</button>
@@ -348,6 +357,11 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
<option value="" data-l10n="All commands">All commands</option> <option value="" data-l10n="All commands">All commands</option>
</select> </select>
<span id="session-name-display" style="display:none; color:var(--text-muted); font-size:15px; font-weight:700;"></span> <span id="session-name-display" style="display:none; color:var(--text-muted); font-size:15px; font-weight:700;"></span>
<select class="side-view-select" id="side-view-select" aria-label="Side panel view">
<option value="map" data-l10n="Map">Map</option>
<option value="stats" data-l10n="Stats">Stats</option>
<option value="files" data-l10n="Files">Files</option>
</select>
</div> </div>
<div class="trace-main"> <div class="trace-main">
<div class="trace-left"> <div class="trace-left">
@@ -358,11 +372,6 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
</div> </div>
</div> </div>
<div class="side-panel" id="side-panel"> <div class="side-panel" id="side-panel">
<select class="side-view-select" id="side-view-select" aria-label="Side panel view">
<option value="map" data-l10n="Map">Map</option>
<option value="stats" data-l10n="Stats">Stats</option>
<option value="files" data-l10n="Files">Files</option>
</select>
<div id="type-counts">—</div> <div id="type-counts">—</div>
<div id="type-map" style="display:none">—</div> <div id="type-map" style="display:none">—</div>
<div id="file-view" style="display:none">—</div> <div id="file-view" style="display:none">—</div>
@@ -3216,7 +3225,7 @@ function onFilterChange() {
} }
// ──────────────────── Versioning ──────────────────── // ──────────────────── Versioning ────────────────────
const PWA_VERSION = '2.6.2'; const PWA_VERSION = '2.6.3';
const MIN_SERVER_VERSION = '2.3.0'; const MIN_SERVER_VERSION = '2.3.0';
function parseVersion(v) { function parseVersion(v) {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v76'; const CACHE = 'simtrace-analyser-v77';
const PRECACHE = [ const PRECACHE = [
'index.html', 'index.html',
+7 -1
View File
@@ -324,8 +324,14 @@ test('Files view: the hex image respects the row limit', () => {
assert.equal(view.end, 64); assert.equal(view.end, 64);
}); });
test('Files view: side-panel dropdown and the timeline are wired', () => { test('Files view: the view selector (topbar) and the timeline are wired', () => {
assert.match(src, /<select class="side-view-select" id="side-view-select"[^>]*>/); assert.match(src, /<select class="side-view-select" id="side-view-select"[^>]*>/);
// The selector is a topbar control (before the two-column trace area), so
// it stays visible while the side panel scrolls.
const topbar = src.indexOf('<div class="trace-topbar">');
const sel = src.indexOf('id="side-view-select"');
const main = src.indexOf('<div class="trace-main">');
assert.ok(topbar >= 0 && topbar < sel && sel < main, 'selector in the topbar');
assert.match(src, /<option value="files" data-l10n="Files">/); assert.match(src, /<option value="files" data-l10n="Files">/);
assert.doesNotMatch(src, /class="side-tab/); assert.doesNotMatch(src, /class="side-tab/);
assert.match(src, /id="file-view"/); assert.match(src, /id="file-view"/);
+26
View File
@@ -21,3 +21,29 @@ test('no per-element scrollbar overrides or thin default remain', () => {
assert.doesNotMatch(src, /\.side-panel::-webkit-scrollbar/); assert.doesNotMatch(src, /\.side-panel::-webkit-scrollbar/);
assert.doesNotMatch(src, /::-webkit-scrollbar \{ width: 6px/); assert.doesNotMatch(src, /::-webkit-scrollbar \{ width: 6px/);
}); });
// ── trace topbar layout ────────────────────────────────────────────────
test('the view selector sits over the side panel at the panel width', () => {
// one layout variable keeps the two the same width
assert.match(src, /--side-panel-w: 260px;/);
assert.match(src, /\.side-panel \{ width: var\(--side-panel-w\);/);
assert.match(src, /\.trace-topbar \.side-view-select \{ width: var\(--side-panel-w\); flex: 0 0 auto; margin-left: auto; margin-right: -16px; \}/);
// the selector is a topbar control (before the two-column trace area,
// which keeps it visible while the panel scrolls)
const topbar = src.indexOf('<div class="trace-topbar">');
const sel = src.indexOf('id="side-view-select"');
const main = src.indexOf('<div class="trace-main">');
assert.ok(topbar >= 0 && topbar < sel && sel < main, 'selector in the topbar');
});
test('the search input is half its previous width', () => {
assert.match(src, /\.trace-topbar input \{ flex: 0 1 16\.667%;/);
// a long session name ellipsizes rather than wrapping the now tighter row
assert.match(src, /\.trace-topbar #session-name-display \{ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; \}/);
});
test('the header title carries no UICC prefix', () => {
assert.match(src, /<div class="header-title"><a href="https:\/\/osmocom\.org\/projects\/baseband\/wiki\/GSMTAP"/);
assert.doesNotMatch(src, /class="header-title">UICC/);
});
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
[project] [project]
name = "simtrace-analyser" name = "simtrace-analyser"
version = "2.6.2" version = "2.6.3"
description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)" description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)"
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
@@ -1,3 +1,3 @@
"""Single source of truth for the SIMtrace Analyser version.""" """Single source of truth for the SIMtrace Analyser version."""
__version__ = '2.6.2' __version__ = '2.6.3'
+1 -1
View File
@@ -224,7 +224,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__, '2.6.2') self.assertEqual(__version__, '2.6.3')
def test_status_includes_version(self): def test_status_includes_version(self):
import os import os