From cee1b6f7c3339bc2168a5e075b028712fe32b8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 20 Aug 2026 23:13:03 +0300 Subject: [PATCH] Add shareable session deep links (#session=N) --- frontend/index.html | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/frontend/index.html b/frontend/index.html index ff06218..daf637f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -380,6 +380,7 @@ const STATE = { captureMode: '', deviceConnected: null, mapExpanded: new Set(), + deepLinkHandled: false, }; const API_BASE = () => STATE.serverUrl; @@ -1068,6 +1069,24 @@ function command_cat(ins) { return null; } +function session_link(id) { + return location.href.split('#')[0] + '#session=' + id; +} + +function is_loopback() { + return ['localhost', '127.0.0.1', '::1', '[::1]', '0.0.0.0'].includes(location.hostname); +} + +function copySessionLink(id) { + const url = session_link(id); + const prompt_ = () => prompt(t('Share link'), url); + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url).catch(prompt_); + } else { + prompt_(); + } +} + function buildTraceMap() { const msgs = STATE.messages; const groups = []; @@ -1503,6 +1522,7 @@ async function loadSessions() {
${t('Download PCAP')} + ${is_loopback() ? '' : ``}
`; @@ -1536,6 +1556,12 @@ async function loadSessions() { showDeleteModal(sid); }); }); + grid.querySelectorAll('.session-copy').forEach(btn => { + btn.addEventListener('click', e => { + e.stopPropagation(); + copySessionLink(parseInt(btn.dataset.sid)); + }); + }); } catch (e) {} } @@ -1576,6 +1602,7 @@ async function loadSession(id) { renderMessages(); el('session-name-display').style.display = 'inline'; el('session-name-display').textContent = t('Session: ') + (data.session.name || '#' + id); + history.replaceState(null, '', '#session=' + id); } catch (e) { alert(t('Failed to load session')); } @@ -1757,6 +1784,11 @@ async function checkConnection() { } updateCaptureButton(); renderTraceEmptyState(); + if (!STATE.deepLinkHandled) { + STATE.deepLinkHandled = true; + const m = location.hash.match(/^#session=(\d+)$/); + if (m) loadSession(parseInt(m[1])); + } } catch (e) { STATE.serverAvailable = false; STATE.captureMode = ''; @@ -1900,6 +1932,8 @@ const LANG_RU = { 'msgs': 'сообщ.', 'active': 'активна', 'Download PCAP': 'Скачать PCAP', + 'Copy link': 'Копировать ссылку', + 'Share link': 'Ссылка для общего доступа', '{n} messages': '{n} сообщений', '{n} session(s) · {size}': '{n} сессий · {size}', '{n} session · {size}': '{n} сессия · {size}', @@ -2049,6 +2083,7 @@ function init() { STATE.sessionViewId = null; STATE.currentSessionName = null; updateCaptureButton(); + history.replaceState(null, '', location.pathname); } startCapture(); }); @@ -2063,6 +2098,7 @@ function init() { STATE.sessionViewId = null; STATE.currentSessionName = null; updateCaptureButton(); + history.replaceState(null, '', location.pathname); switchTab('trace'); startCapture(); });