diff --git a/frontend/index.html b/frontend/index.html
index 50d15eb..7061f8f 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -211,6 +211,16 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
.session-download:hover { border-color: var(--accent); color: var(--accent); }
.session-delete { padding: 4px 12px; background: none; border: 1px solid var(--accent); color: var(--accent); border-radius: 4px; cursor: pointer; font-size: 13px; }
.session-delete:hover { background: var(--accent); color: #fff; }
+.session-view-toggle { display: flex; gap: 2px; }
+.session-view-toggle button { padding: 4px 10px; background: var(--bg-input); border: 1px solid var(--border); color: var(--text-muted); cursor: pointer; font-size: 13px; }
+.session-view-toggle button.active { background: var(--accent); color: #fff; border-color: var(--accent); }
+.sessions-list { padding: 0 16px 16px; overflow-y: auto; }
+.sessions-list table { width: 100%; border-collapse: collapse; }
+.sessions-list th { text-align: left; padding: 8px 12px; color: var(--text-muted); font-size: 12px; border-bottom: 1px solid var(--border); }
+.sessions-list td { padding: 8px 12px; border-bottom: 1px solid var(--bg-input); font-size: 13px; }
+.sessions-list tr[data-id] { cursor: pointer; }
+.sessions-list tr[data-id]:hover { background: var(--bg-panel); }
+.sessions-list td .session-actions { display: flex; gap: 8px; align-items: center; margin-top: 0; }
/* Settings */
.settings-view { padding: 24px; max-width: 600px; }
@@ -321,6 +331,10 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
+
+ const emptyHTML = `
${t('No saved sessions')}
`;
+ grid.innerHTML = emptyHTML;
+ list.innerHTML = emptyHTML;
return;
}
- grid.innerHTML = data.sessions.map(s => {
- const name = s.name || t('Session #') + s.id;
- const dStart = s.started ? s.started.replace('T', ' ') : '?';
- const duration = s.ended ? formatDuration(s.started, s.ended) : t('active');
- return `
-
-
-
-
- ${dStart}
- ${duration}
- ${s.message_count} ${t('msgs')}
- ${s.mode}
-
-
-
`;
- }).join('');
- grid.querySelectorAll('.session-card').forEach(card => {
- card.addEventListener('click', e => {
- if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.tagName === 'A') return;
- loadSession(parseInt(card.dataset.id));
- });
- });
- grid.querySelectorAll('.session-name input').forEach(input => {
- const originalName = input.value;
- input.addEventListener('blur', async () => {
- const sid = parseInt(input.dataset.sid);
- const name = input.value.trim();
- try {
- await API.renameSession(sid, name);
- } catch (e) {
- input.value = originalName;
- }
- });
- input.addEventListener('keydown', e => {
- if (e.key === 'Enter') input.blur();
- });
- });
- grid.querySelectorAll('.session-delete').forEach(btn => {
- btn.addEventListener('click', e => {
- e.stopPropagation();
- const sid = parseInt(btn.dataset.sid);
- showDeleteModal(sid);
- });
- });
- grid.querySelectorAll('.session-copy').forEach(btn => {
- btn.addEventListener('click', e => {
- e.stopPropagation();
- copySessionLink(parseInt(btn.dataset.sid));
- });
- });
+ if (SESSION_VIEW === 'grid') {
+ grid.innerHTML = buildGridHTML(data.sessions);
+ } else {
+ list.querySelector('tbody').innerHTML = buildListHTML(data.sessions);
+ }
+
+ attachSessionListeners(SESSION_VIEW === 'grid' ? grid : list);
} catch (e) {}
}
@@ -2526,7 +2607,7 @@ function onFilterChange() {
}
// ──────────────────── Versioning ────────────────────
-const PWA_VERSION = '1.22.5';
+const PWA_VERSION = '1.22.6';
const MIN_SERVER_VERSION = '1.0.0';
function parseVersion(v) {
@@ -2584,6 +2665,14 @@ const LANG_RU = {
'Start a capture from the Trace tab to create sessions.': 'Начните захват на вкладке «Трассировка», чтобы создать сессии.',
'Import PCAP': 'Импорт PCAP',
'New capture': 'Новый захват',
+ 'Tiles': 'Плитки',
+ 'List': 'Список',
+ 'Name': 'Имя',
+ 'Date': 'Дата',
+ 'Duration': 'Длительность',
+ 'Messages': 'Сообщения',
+ 'Mode': 'Режим',
+ 'Actions': 'Действия',
'Server': 'Сервер',
'device disconnected — reconnecting…': 'устройство отключено — переподключение…',
'device disconnected — messages may be missing': 'устройство отключалось — сообщения могли быть потеряны',
@@ -2788,6 +2877,9 @@ function init() {
el('import-pcap-file').value = '';
});
+ $on('view-grid-btn', 'click', () => toggleSessionView('grid'));
+ $on('view-list-btn', 'click', () => toggleSessionView('list'));
+
$on('search-input', 'input', onSearchChange);
$on('filter-select', 'change', onFilterChange);
diff --git a/frontend/sw.js b/frontend/sw.js
index 6f0b0fa..4e98098 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simtrace-analyser-v37';
+const CACHE = 'simtrace-analyser-v38';
const PRECACHE = [
'index.html',
diff --git a/pyproject.toml b/pyproject.toml
index 5c40c9f..4b2cf0b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "simtrace2-pysniff"
-version = "1.22.5"
+version = "1.22.6"
description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)"
readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"}
diff --git a/simtrace2_pysniff/__init__.py b/simtrace2_pysniff/__init__.py
index b0b4375..9e0ccc6 100644
--- a/simtrace2_pysniff/__init__.py
+++ b/simtrace2_pysniff/__init__.py
@@ -6,7 +6,7 @@ from .gsmtap import GsmtapSender, GsmtapReceiver
from .pcap import PcapWriter
from .dump import FileDumper, format_message
-__version__ = '1.22.5'
+__version__ = '1.22.6'
__all__ = [
'SniffSession',
'DeviceDisconnected',
diff --git a/tests/test_pcap_import.py b/tests/test_pcap_import.py
index 01b77b5..56b46c6 100644
--- a/tests/test_pcap_import.py
+++ b/tests/test_pcap_import.py
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase):
def test_package_version(self):
from simtrace2_pysniff import __version__
- self.assertEqual(__version__, '1.22.5')
+ self.assertEqual(__version__, '1.22.6')
def test_status_includes_version(self):
import os