diff --git a/src/data_monitor.py b/src/data_monitor.py index 80df67f..29f1d9d 100644 --- a/src/data_monitor.py +++ b/src/data_monitor.py @@ -16,9 +16,9 @@ _cache: dict[str, tuple] = {} @router.get("/monitor-feed") -async def get_monitor_feed(incident_id: int = Query(None)): +async def get_monitor_feed(incident_id: int = Query(45)): """Holt OSINT-Daten vom AegisSight Monitor.""" - cache_key = f"monitor:{incident_id or 'all'}" + cache_key = f"monitor:{incident_id}" if cache_key in _cache and time.time() - _cache[cache_key][0] < 120: return _cache[cache_key][1] diff --git a/static/css/globe.css b/static/css/globe.css index a745abe..9c15d06 100644 --- a/static/css/globe.css +++ b/static/css/globe.css @@ -256,3 +256,159 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo line-height: 1.4; } .layer-toggle, .layer-name, .vmode-btn { position: relative; } + + +/* === Rechte Sidebar === */ +.sidebar-right { + position: fixed; + top: 44px; + right: 0; + bottom: 28px; + width: 280px; + z-index: 100; + display: flex; + transition: transform 0.3s ease; +} +.sidebar-right.collapsed { + transform: translateX(280px); +} +.sidebar-right.collapsed .sidebar-toggle { + transform: translateX(-32px); +} +.sidebar-toggle { + position: absolute; + left: -28px; + top: 12px; + width: 28px; + height: 36px; + background: var(--bg-panel); + border: 1px solid var(--border); + border-right: none; + border-radius: 6px 0 0 6px; + color: var(--text); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + z-index: 101; +} +.sidebar-toggle:hover { color: var(--accent); border-color: var(--accent); } + +.sidebar-inner { + flex: 1; + background: var(--bg-panel); + border-left: 1px solid var(--border); + backdrop-filter: blur(12px); + display: flex; + flex-direction: column; + overflow: hidden; +} +.sidebar-search-wrap { + padding: 10px; + border-bottom: 1px solid var(--border); +} +.sidebar-search { + width: 100%; + padding: 6px 10px; + background: rgba(255,255,255,0.05); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); + font-family: var(--font-mono); + font-size: 11px; + outline: none; +} +.sidebar-search:focus { border-color: var(--accent); } +.sidebar-search::placeholder { color: var(--text-dim); } + +.sidebar-content { + flex: 1; + overflow-y: auto; + padding: 4px 0; +} +.sidebar-content::-webkit-scrollbar { width: 4px; } +.sidebar-content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } + +/* Sections */ +.sb-section { border-bottom: 1px solid rgba(255,255,255,0.04); } +.sb-header { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + cursor: pointer; + transition: background 0.15s; +} +.sb-header:hover { background: rgba(255,255,255,0.04); } +.sb-header-count { cursor: default; } +.sb-chevron { + font-size: 10px; + color: var(--text-dim); + transition: transform 0.2s; + width: 12px; + text-align: center; +} +.sb-title { + font-size: 10px; + font-weight: 700; + letter-spacing: 1px; + text-transform: uppercase; + color: var(--text); + flex: 1; +} +.sb-badge { + font-size: 9px; + color: var(--accent); + background: rgba(0,255,136,0.1); + padding: 1px 6px; + border-radius: 8px; + font-family: var(--font-mono); +} + +/* Items */ +.sb-list { padding: 0 4px 4px; } +.sb-item { + display: flex; + align-items: flex-start; + gap: 6px; + padding: 4px 8px; + border-radius: 3px; + cursor: pointer; + transition: background 0.15s; +} +.sb-item:hover { background: rgba(0,255,136,0.06); } +.sb-dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; + margin-top: 4px; +} +.sb-item-text { min-width: 0; flex: 1; } +.sb-item-name { + font-size: 11px; + color: var(--text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.sb-item-sub { + font-size: 9px; + color: var(--text-dim); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.sb-more { + font-size: 9px; + color: var(--text-dim); + padding: 4px 8px; + text-align: center; +} +.sb-empty { + padding: 20px; + text-align: center; + font-size: 11px; + color: var(--text-dim); +} diff --git a/static/index.html b/static/index.html index 24b710a..6185aa2 100644 --- a/static/index.html +++ b/static/index.html @@ -126,6 +126,22 @@ + + + +
@@ -145,6 +161,7 @@ + diff --git a/static/js/app.js b/static/js/app.js index a20ac82..d69626c 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -82,6 +82,7 @@ const Globe = { this._toggleLabels(true); if (typeof VisualModes !== 'undefined') VisualModes.init(); + if (typeof Sidebar !== 'undefined') Sidebar.init(); document.getElementById('bottom-stats').textContent = 'Globe initialisiert — Lade Daten...'; }, diff --git a/static/js/ui/sidebar.js b/static/js/ui/sidebar.js new file mode 100644 index 0000000..5e0c37a --- /dev/null +++ b/static/js/ui/sidebar.js @@ -0,0 +1,171 @@ +/** + * Rechte Sidebar: Uebersicht aller aktiven Datenpunkte mit Suche. + */ +const Sidebar = { + _open: true, + _searchTerm: '', + + init() { + var self = this; + var toggle = document.getElementById('sidebar-toggle'); + var search = document.getElementById('sidebar-search'); + if (toggle) toggle.addEventListener('click', function() { self.toggle(); }); + if (search) search.addEventListener('input', function() { + self._searchTerm = this.value.toLowerCase(); + self.update(); + }); + // Periodisch aktualisieren + setInterval(function() { self.update(); }, 5000); + }, + + toggle() { + this._open = !this._open; + var panel = document.getElementById('sidebar-right'); + var toggle = document.getElementById('sidebar-toggle'); + if (panel) panel.classList.toggle('collapsed', !this._open); + if (toggle) toggle.innerHTML = this._open + ? '' + : ''; + }, + + update() { + var container = document.getElementById('sidebar-content'); + if (!container) return; + var html = ''; + var term = this._searchTerm; + + // Monitor OSINT + if (typeof MonitorLayer !== 'undefined' && MonitorLayer._data.length) { + html += this._renderSection('monitor', 'OSINT Monitor', MonitorLayer._data, function(f) { + var p = f.properties; + return { + name: p.name || '?', + sub: (p.incident_title || '') + (p.article_count > 1 ? ' (' + p.article_count + ' Artikel)' : ''), + color: { primary: '#ff2222', secondary: '#ff8800', tertiary: '#4499ff', mentioned: '#888' }[p.category] || '#888', + lat: f.geometry.coordinates[1], + lon: f.geometry.coordinates[0], + }; + }, term); + } + + // Katastrophen + if (typeof DisastersLayer !== 'undefined' && DisastersLayer._dataSource && DisastersLayer._count > 0) { + var entities = DisastersLayer._dataSource.entities.values; + var items = []; + for (var i = 0; i < entities.length; i++) { + var e = entities[i]; + var name = e.name || (e._name && e._name.getValue ? e._name.getValue() : ''); + if (!name && e.description) { + var match = e.description.getValue().match(/]*>([^<]+)<\/strong>/); + if (match) name = match[1]; + } + var pos = e.position ? e.position.getValue(Cesium.JulianDate.now()) : null; + var carto = pos ? Cesium.Cartographic.fromCartesian(pos) : null; + items.push({ + name: name || 'Ereignis', + sub: '', + color: '#ff4400', + lat: carto ? Cesium.Math.toDegrees(carto.latitude) : 0, + lon: carto ? Cesium.Math.toDegrees(carto.longitude) : 0, + entity: e, + }); + } + html += this._renderEntitySection('disasters', 'Katastrophen + Erdbeben', items, term); + } + + // Flugverkehr (nur count, keine Liste — zu viele) + if (typeof FlightsLayer !== 'undefined' && FlightsLayer._count > 0) { + html += this._renderCountSection('flights', 'Flugverkehr', FlightsLayer._count, '#00ff88'); + } + + // Schiffsverkehr + if (typeof ShipsLayer !== 'undefined' && ShipsLayer._count > 0) { + html += this._renderCountSection('ships', 'Schiffsverkehr', ShipsLayer._count, '#4499ff'); + } + + // Satelliten + if (typeof SatellitesLayer !== 'undefined' && SatellitesLayer._count > 0) { + html += this._renderCountSection('satellites', 'Satelliten', SatellitesLayer._count, '#ff4444'); + } + + if (!html) { + html = '
Keine Layer aktiv
'; + } + + container.innerHTML = html; + this._attachListeners(); + }, + + _renderSection(id, title, features, mapFn, term) { + var items = features.map(mapFn).filter(function(item) { + if (!term) return true; + return (item.name + ' ' + item.sub).toLowerCase().indexOf(term) >= 0; + }); + if (!items.length) return ''; + return '
' + + '
' + + '' + + '' + title + '' + + '' + items.length + '
' + + '
' + + items.slice(0, 100).map(function(item) { + return '
' + + '' + + '
' + item.name + '
' + + (item.sub ? '
' + item.sub + '
' : '') + + '
'; + }).join('') + + (items.length > 100 ? '
+ ' + (items.length - 100) + ' weitere
' : '') + + '
'; + }, + + _renderEntitySection(id, title, items, term) { + if (term) { + items = items.filter(function(item) { return item.name.toLowerCase().indexOf(term) >= 0; }); + } + if (!items.length) return ''; + return '
' + + '
' + + '' + + '' + title + '' + + '' + items.length + '
' + + '
' + + items.slice(0, 100).map(function(item, idx) { + return '
' + + '' + + '
' + item.name + '
'; + }).join('') + + '
'; + }, + + _renderCountSection(id, title, count, color) { + return '
' + + '
' + + '' + + '' + title + '' + + '' + count.toLocaleString('de-DE') + '
'; + }, + + _toggleSection(id) { + var list = document.getElementById('sb-list-' + id); + var chev = document.getElementById('sb-chev-' + id); + if (list) { + var open = list.style.display !== 'none'; + list.style.display = open ? 'none' : 'block'; + if (chev) chev.style.transform = open ? 'rotate(-90deg)' : ''; + } + }, + + _flyTo(lat, lon) { + if (typeof Globe !== 'undefined' && Globe.viewer) { + Globe.viewer.camera.flyTo({ + destination: Cesium.Cartesian3.fromDegrees(lon, lat, 800000), + duration: 1.5, + }); + } + }, + + _attachListeners() { + // Wird nach jedem Update aufgerufen + }, +};