Rechte Sidebar + Monitor-Feed nur Naturkatastrophen

SIDEBAR (neu):
- Aufklappbare Uebersicht aller aktiven Datenpunkte rechts
- Suchfeld filtert alle Eintraege
- Sektionen: OSINT Monitor, Katastrophen (auffaecherbar)
- Flug/Schiff/Satelliten als Zaehler
- Klick auf Eintrag fliegt zur Position
- Ein-/ausklappbar per Pfeil-Button am rechten Rand

MONITOR-FEED FIX:
- Nur Lage 45 (Naturkatastrophen international) wird abgefragt
- Keine anderen Lagen mehr im Globe sichtbar
Dieser Commit ist enthalten in:
Claude Dev
2026-03-24 13:29:43 +01:00
Ursprung 27516b1a8a
Commit 65a30e0d06
5 geänderte Dateien mit 347 neuen und 2 gelöschten Zeilen

Datei anzeigen

@@ -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]

Datei anzeigen

@@ -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);
}

Datei anzeigen

@@ -126,6 +126,22 @@
</div>
</aside>
<!-- Rechte Sidebar: Datenpunkt-Uebersicht -->
<aside id="sidebar-right" class="sidebar-right">
<button id="sidebar-toggle" class="sidebar-toggle" title="Seitenleiste ein-/ausblenden">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
</button>
<div class="sidebar-inner">
<div class="sidebar-search-wrap">
<input type="text" id="sidebar-search" class="sidebar-search" placeholder="Suchen...">
</div>
<div id="sidebar-content" class="sidebar-content">
<div class="sb-empty">Keine Layer aktiv</div>
</div>
</div>
</aside>
<!-- CesiumJS Container -->
<div id="cesiumContainer"></div>
<div id="visual-overlay" class="visual-overlay" style="display:none"></div>
@@ -145,6 +161,7 @@
<script src="/static/js/layers/gdelt.js"></script>
<script src="/static/js/layers/satellites.js"></script>
<script src="/static/js/layers/disasters.js"></script>
<script src="/static/js/ui/sidebar.js"></script>
<script src="/static/js/layers/monitor.js"></script>
<script src="/static/js/layers/visualmodes.js"></script>
</body>

Datei anzeigen

@@ -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...';
},

171
static/js/ui/sidebar.js Normale Datei
Datei anzeigen

@@ -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
? '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>'
: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>';
},
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[^>]*>([^<]+)<\/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 = '<div class="sb-empty">Keine Layer aktiv</div>';
}
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 '<div class="sb-section" id="sb-' + id + '">' +
'<div class="sb-header" onclick="Sidebar._toggleSection(\'' + id + '\')">' +
'<span class="sb-chevron" id="sb-chev-' + id + '">&#9662;</span>' +
'<span class="sb-title">' + title + '</span>' +
'<span class="sb-badge">' + items.length + '</span></div>' +
'<div class="sb-list" id="sb-list-' + id + '">' +
items.slice(0, 100).map(function(item) {
return '<div class="sb-item" onclick="Sidebar._flyTo(' + item.lat + ',' + item.lon + ')" title="' + item.name + '">' +
'<span class="sb-dot" style="background:' + item.color + '"></span>' +
'<div class="sb-item-text"><div class="sb-item-name">' + item.name + '</div>' +
(item.sub ? '<div class="sb-item-sub">' + item.sub + '</div>' : '') +
'</div></div>';
}).join('') +
(items.length > 100 ? '<div class="sb-more">+ ' + (items.length - 100) + ' weitere</div>' : '') +
'</div></div>';
},
_renderEntitySection(id, title, items, term) {
if (term) {
items = items.filter(function(item) { return item.name.toLowerCase().indexOf(term) >= 0; });
}
if (!items.length) return '';
return '<div class="sb-section" id="sb-' + id + '">' +
'<div class="sb-header" onclick="Sidebar._toggleSection(\'' + id + '\')">' +
'<span class="sb-chevron" id="sb-chev-' + id + '">&#9662;</span>' +
'<span class="sb-title">' + title + '</span>' +
'<span class="sb-badge">' + items.length + '</span></div>' +
'<div class="sb-list" id="sb-list-' + id + '">' +
items.slice(0, 100).map(function(item, idx) {
return '<div class="sb-item" onclick="Sidebar._flyTo(' + item.lat + ',' + item.lon + ')" title="' + item.name + '">' +
'<span class="sb-dot" style="background:' + item.color + '"></span>' +
'<div class="sb-item-text"><div class="sb-item-name">' + item.name + '</div></div></div>';
}).join('') +
'</div></div>';
},
_renderCountSection(id, title, count, color) {
return '<div class="sb-section">' +
'<div class="sb-header sb-header-count">' +
'<span class="sb-dot" style="background:' + color + '"></span>' +
'<span class="sb-title">' + title + '</span>' +
'<span class="sb-badge">' + count.toLocaleString('de-DE') + '</span></div></div>';
},
_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
},
};