diff --git a/static/css/modes/meshcore.css b/static/css/modes/meshcore.css index cce677b..0fcc009 100644 --- a/static/css/modes/meshcore.css +++ b/static/css/modes/meshcore.css @@ -1,58 +1,95 @@ /* Meshcore mode — scoped styles */ -/* Override the shared mesh-visuals-container column layout */ +/* ── Container overrides ── */ #meshcoreVisuals { - flex-direction: row; + flex-direction: column; padding: 0; gap: 0; } #meshcoreMode { display: flex; - flex-direction: row; + flex-direction: column; flex: 1; min-height: 0; overflow: hidden; gap: 0; } -/* ── Sidebar ── */ -.meshcore-sidebar { - width: 220px; - min-width: 220px; - background: var(--bg-card); - border-right: 1px solid var(--border-color); +/* ── Connection strip ── */ +.meshcore-strip { display: flex; - flex-direction: column; - overflow-y: auto; - flex-shrink: 0; -} - -.meshcore-sidebar-section { - padding: 10px; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: var(--bg-card); border-bottom: 1px solid var(--border-color); + flex-shrink: 0; + flex-wrap: wrap; } -.meshcore-sidebar-section h4 { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.08em; +.meshcore-strip-group { + display: flex; + align-items: center; + gap: 6px; +} + +.meshcore-strip-divider { + width: 1px; + height: 20px; + background: var(--border-color); + margin: 0 4px; +} + +.meshcore-strip-status-text { + font-size: 12px; color: var(--text-muted); - margin: 0 0 8px; } -/* ── Connection panel ── */ +.meshcore-strip-select { + background: var(--bg-input); + border: 1px solid var(--border-color); + color: var(--text-primary); + padding: 4px 6px; + font-size: 12px; + border-radius: 3px; +} + +.meshcore-strip-input { + background: var(--bg-input); + border: 1px solid var(--border-color); + color: var(--text-primary); + padding: 4px 6px; + font-size: 12px; + border-radius: 3px; + font-family: var(--font-mono); +} + +.meshcore-strip-btn { + padding: 4px 10px; + font-size: 12px; + border-radius: 3px; + cursor: pointer; + border: 1px solid var(--border-color); + background: var(--bg-input); + color: var(--text-primary); + transition: background 0.15s; +} + +.meshcore-strip-btn:hover { opacity: 0.85; } +.meshcore-strip-btn.connect { background: var(--accent-cyan); color: #000; border-color: var(--accent-cyan); } +.meshcore-strip-btn.disconnect { border-color: #f44336; color: #f44336; } +.meshcore-strip-btn:disabled { opacity: 0.4; cursor: not-allowed; } + +/* Transport tabs in strip */ .meshcore-transport-tabs { display: flex; - gap: 4px; - margin-bottom: 8px; + gap: 2px; } .meshcore-transport-tab { - flex: 1; - padding: 4px 0; + padding: 3px 8px; font-size: 11px; - text-align: center; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: 3px; @@ -67,23 +104,84 @@ border-color: var(--accent-cyan); } -/* ── Status indicator ── */ +/* Strip stats */ +.meshcore-strip-stat { + display: flex; + flex-direction: column; + align-items: center; + min-width: 40px; +} + +.meshcore-strip-value { + font-size: 14px; + font-weight: 600; + color: var(--accent-cyan); + font-family: var(--font-mono); + line-height: 1; +} + +.meshcore-strip-label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); +} + +/* ── Status dot ── */ .meshcore-status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; - margin-right: 6px; background: var(--text-muted); + flex-shrink: 0; } -.meshcore-status-dot.connected { background: #4caf50; box-shadow: 0 0 5px #4caf50; } -.meshcore-status-dot.connecting { background: #ff9800; animation: meshcore-pulse 1s infinite; } -.meshcore-status-dot.error { background: #f44336; } +.meshcore-status-dot.connected { background: #4caf50; box-shadow: 0 0 5px #4caf50; } +.meshcore-status-dot.connecting { background: #ff9800; animation: meshcore-pulse 1s infinite; } +.meshcore-status-dot.error { background: #f44336; } @keyframes meshcore-pulse { 0%, 100% { opacity: 1; } - 50% { opacity: 0.3; } + 50% { opacity: 0.3; } +} + +/* ── Body (panel + content) ── */ +.meshcore-body { + display: flex; + flex: 1; + min-height: 0; + overflow: hidden; +} + +/* ── Left contacts/nodes panel ── */ +.meshcore-panel { + width: 200px; + min-width: 200px; + background: var(--bg-card); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; + overflow: hidden; + flex-shrink: 0; +} + +.meshcore-panel-section { + padding: 10px; + border-bottom: 1px solid var(--border-color); + overflow-y: auto; +} + +.meshcore-panel-section--grow { + flex: 1; +} + +.meshcore-panel-title { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); + margin-bottom: 8px; } /* ── Node / contact list items ── */ @@ -116,18 +214,28 @@ .meshcore-node-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .meshcore-node-meta { font-size: 10px; color: var(--text-muted); } -/* ── Main content area ── */ -.meshcore-main { +.meshcore-empty { + font-size: 11px; + color: var(--text-muted); +} + +.meshcore-label { + font-size: 12px; + color: var(--text-muted); +} + +/* ── Right content ── */ +.meshcore-content { flex: 1; display: flex; flex-direction: column; + min-width: 0; overflow: hidden; } /* ── Tab bar ── */ .meshcore-tabs { display: flex; - gap: 0; border-bottom: 1px solid var(--border-color); background: var(--bg-card); flex-shrink: 0; @@ -147,6 +255,17 @@ border-bottom-color: var(--accent-cyan); } +/* ── Tab panels ── */ +.meshcore-tab-panel { + display: none; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; +} + +.meshcore-tab-panel.active { display: flex; } + /* ── Message feed ── */ .meshcore-messages { flex: 1; @@ -166,7 +285,7 @@ } .meshcore-message.pending { opacity: 0.6; border-style: dashed; } -.meshcore-message.direct { border-left: 3px solid var(--accent-cyan); } +.meshcore-message.direct { border-left: 3px solid var(--accent-cyan); } .meshcore-message-header { display: flex; @@ -177,7 +296,7 @@ } .meshcore-message-sender { color: var(--accent-cyan); font-weight: 600; } -.meshcore-message-text { color: var(--text-primary); } +.meshcore-message-text { color: var(--text-primary); } /* ── Compose bar ── */ .meshcore-compose { @@ -189,7 +308,17 @@ flex-shrink: 0; } -.meshcore-compose input { +.meshcore-compose-select { + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 6px 8px; + color: var(--text-primary); + font-size: 12px; + width: 140px; +} + +.meshcore-compose-input { flex: 1; background: var(--bg-input); border: 1px solid var(--border-color); @@ -200,19 +329,39 @@ font-family: var(--font-mono); } -.meshcore-compose input:focus { +.meshcore-compose-input:focus { outline: none; border-color: var(--accent-cyan); } -/* ── Repeaters tab table ── */ -.meshcore-repeater-table { +.meshcore-compose-btn { + padding: 6px 16px; + background: var(--accent-cyan); + color: #000; + border: none; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + white-space: nowrap; +} + +/* ── Map tab ── */ +#meshcoreTabMap { overflow: hidden; } + +#meshcoreMap { + width: 100%; + height: 100%; + min-height: 300px; +} + +/* ── Repeaters table ── */ +.meshcore-table { width: 100%; border-collapse: collapse; font-size: 12px; } -.meshcore-repeater-table th { +.meshcore-table th { text-align: left; padding: 6px 10px; font-size: 10px; @@ -222,7 +371,7 @@ border-bottom: 1px solid var(--border-color); } -.meshcore-repeater-table td { +.meshcore-table td { padding: 6px 10px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); @@ -232,7 +381,6 @@ .meshcore-traceroute-hops { display: flex; align-items: center; - gap: 0; flex-wrap: wrap; padding: 16px 0; } @@ -261,10 +409,3 @@ font-size: 10px; color: var(--text-muted); } - -/* ── Map tab ── */ -#meshcoreMap { - width: 100%; - height: 100%; - min-height: 300px; -} diff --git a/static/js/modes/meshcore.js b/static/js/modes/meshcore.js index 0ddad3b..fa291bc 100644 --- a/static/js/modes/meshcore.js +++ b/static/js/modes/meshcore.js @@ -9,9 +9,11 @@ const MeshCore = (function () { let _transport = 'serial'; let _eventSource = null; let _map = null; - let _markers = {}; // node_id → L.marker + let _markers = {}; let _telemetryChart = null; let _connected = false; + let _nodeCount = 0; + let _msgCount = 0; // ── Init / Destroy ───────────────────────────────────────────────────── function init() { @@ -25,6 +27,8 @@ const MeshCore = (function () { if (_map) { _map.remove(); _map = null; _markers = {}; } if (_telemetryChart) { _telemetryChart.destroy(); _telemetryChart = null; } _connected = false; + _nodeCount = 0; + _msgCount = 0; } function invalidateMap() { @@ -173,6 +177,9 @@ const MeshCore = (function () {
`; feed.appendChild(el); feed.scrollTop = feed.scrollHeight; + _msgCount++; + const mc = document.getElementById('meshcoreMsgCount'); + if (mc) mc.textContent = _msgCount; } async function sendMessage() { @@ -225,8 +232,12 @@ const MeshCore = (function () { el = document.createElement('div'); el.className = 'meshcore-node-item'; el.id = 'meshcore-node-' + node.node_id; - list.innerHTML = ''; + const empty = list.querySelector('.meshcore-empty'); + if (empty) empty.remove(); list.appendChild(el); + _nodeCount++; + const nc = document.getElementById('meshcoreNodeCount'); + if (nc) nc.textContent = _nodeCount; } const hops = node.hops_away !== null ? `${node.hops_away}h` : '?'; const snr = node.snr !== null ? `${node.snr}dB` : ''; @@ -272,10 +283,22 @@ const MeshCore = (function () { const container = document.getElementById('meshcoreMap'); if (!container || _map) return; _map = L.map('meshcoreMap', { zoomControl: true }).setView([20, 0], 2); - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap', - maxZoom: 18, + + const fallback = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', { + attribution: '© CartoDB', + maxZoom: 19, }).addTo(_map); + + if (typeof Settings !== 'undefined') { + Promise.race([ + Settings.init(), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)), + ]).then(() => { + fallback.remove(); + Settings.createTileLayer().addTo(_map); + Settings.registerMap(_map); + }).catch(e => console.warn('MeshCore: Settings init failed, using fallback tiles:', e)); + } } function _updateMapMarker(node) { diff --git a/templates/partials/modes/meshcore.html b/templates/partials/modes/meshcore.html index 9241dcf..b607c64 100644 --- a/templates/partials/modes/meshcore.html +++ b/templates/partials/modes/meshcore.html @@ -1,132 +1,133 @@ -{# Meshcore Mode Partial #} -