Lage-Auswahl im Header + dynamischer Monitor-Feed
Dropdown in der Top-Leiste zeigt alle oeffentlichen Lagen. [L] = Live-Monitoring, [R] = Recherche. Immer nur eine Lage aktiv. Bei Auswahl: - OSINT Monitor Layer zeigt nur Daten dieser Lage - Klick auf Katastrophen zeigt Summary dieser Lage - Sidebar zeigt Punkte dieser Lage Ohne Auswahl: kein Monitor-Overlay.
Dieser Commit ist enthalten in:
@@ -15,10 +15,31 @@ _MONITOR_KEY = os.getenv("MONITOR_API_KEY", "")
|
||||
_cache: dict[str, tuple] = {}
|
||||
|
||||
|
||||
@router.get("/monitor-incidents")
|
||||
async def get_monitor_incidents():
|
||||
"""Holt Liste aller Lagen vom Monitor."""
|
||||
cache_key = "incidents"
|
||||
if cache_key in _cache and time.time() - _cache[cache_key][0] < 300:
|
||||
return _cache[cache_key][1]
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get(
|
||||
f"{_MONITOR_URL}/globe-incidents",
|
||||
headers={"X-API-Key": _MONITOR_KEY},
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
_cache[cache_key] = (time.time(), data)
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.warning(f"Monitor incidents Fehler: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@router.get("/monitor-feed")
|
||||
async def get_monitor_feed(incident_id: int = Query(45)):
|
||||
async def get_monitor_feed(incident_id: int = Query(None)):
|
||||
"""Holt OSINT-Daten vom AegisSight Monitor."""
|
||||
cache_key = f"monitor:{incident_id}"
|
||||
cache_key = f"monitor:{incident_id or 0}"
|
||||
if cache_key in _cache and time.time() - _cache[cache_key][0] < 120:
|
||||
return _cache[cache_key][1]
|
||||
|
||||
|
||||
@@ -412,3 +412,24 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
|
||||
/* === Lage-Selector === */
|
||||
.header-lage { display: flex; align-items: center; }
|
||||
.lage-select {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
max-width: 300px;
|
||||
outline: none;
|
||||
}
|
||||
.lage-select:hover { border-color: var(--accent); }
|
||||
.lage-select option {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</svg>
|
||||
<span class="header-title">AegisSight Globe</span>
|
||||
</div>
|
||||
<div class="header-stats" id="header-stats"></div>
|
||||
<div class="header-lage"><select id="lage-select" class="lage-select" onchange="Globe._onLageChange(this.value)"><option value="">-- Lage waehlen --</option></select></div><div class="header-stats" id="header-stats"></div>
|
||||
<div class="header-actions">
|
||||
<button class="header-btn" id="btn-fullscreen" title="Vollbild">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||||
|
||||
@@ -5,6 +5,7 @@ const Globe = {
|
||||
viewer: null,
|
||||
layers: {},
|
||||
_statsInterval: null,
|
||||
_currentLageId: null,
|
||||
_labelsLayer: null,
|
||||
|
||||
init() {
|
||||
@@ -83,6 +84,7 @@ const Globe = {
|
||||
this._toggleLabels(true);
|
||||
if (typeof VisualModes !== 'undefined') VisualModes.init();
|
||||
if (typeof Sidebar !== 'undefined') Sidebar.init();
|
||||
this._loadLagen();
|
||||
document.getElementById('bottom-stats').textContent = 'Globe initialisiert — Lade Daten...';
|
||||
},
|
||||
|
||||
@@ -107,6 +109,35 @@ const Globe = {
|
||||
}
|
||||
},
|
||||
|
||||
_loadLagen() {
|
||||
var select = document.getElementById('lage-select');
|
||||
if (!select) return;
|
||||
fetch('/api/monitor-incidents')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(lagen) {
|
||||
var html = '<option value="">-- Lage waehlen --</option>';
|
||||
(lagen || []).forEach(function(l) {
|
||||
var typeLabel = l.type === 'research' ? '[R] ' : '[L] ';
|
||||
html += '<option value="' + l.id + '">' + typeLabel + l.title + '</option>';
|
||||
});
|
||||
select.innerHTML = html;
|
||||
})
|
||||
.catch(function() {});
|
||||
},
|
||||
|
||||
_onLageChange(lageId) {
|
||||
this._currentLageId = lageId ? parseInt(lageId) : null;
|
||||
// Monitor-Layer aktualisieren wenn aktiv
|
||||
if (typeof MonitorLayer !== 'undefined' && MonitorLayer._points) {
|
||||
MonitorLayer._data = [];
|
||||
MonitorLayer._incidents = [];
|
||||
MonitorLayer._render();
|
||||
if (lageId) MonitorLayer._fetchForLage(lageId);
|
||||
}
|
||||
// Sidebar aktualisieren
|
||||
if (typeof Sidebar !== 'undefined') Sidebar.update();
|
||||
},
|
||||
|
||||
_setupLayerToggles() {
|
||||
var toggles = {
|
||||
'layer-monitor': function(on) { on ? MonitorLayer.start(Globe.viewer) : MonitorLayer.stop(); },
|
||||
|
||||
@@ -120,13 +120,34 @@ const MonitorLayer = {
|
||||
});
|
||||
},
|
||||
|
||||
_fetchForLage(lageId) {
|
||||
var self = this;
|
||||
var loadEl = document.getElementById('loading-monitor');
|
||||
var statusEl = document.getElementById('status-monitor');
|
||||
if (loadEl) loadEl.classList.add('active');
|
||||
if (statusEl) { statusEl.textContent = 'Lade Lage-Daten...'; statusEl.classList.add('active'); }
|
||||
fetch('/api/monitor-feed?incident_id=' + lageId)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
self._data = data.features || [];
|
||||
self._incidents = data.incidents || [];
|
||||
self._count = self._data.length;
|
||||
self._render();
|
||||
var title = self._incidents.length ? self._incidents[0].title : '';
|
||||
if (statusEl) statusEl.textContent = self._count + ' Orte' + (title ? ' (' + title + ')' : '');
|
||||
})
|
||||
.catch(function(e) { console.warn('Monitor error:', e); if (statusEl) statusEl.textContent = 'Fehler'; })
|
||||
.finally(function() { if (loadEl) loadEl.classList.remove('active'); setTimeout(function() { if (statusEl) statusEl.classList.remove('active'); }, 8000); });
|
||||
},
|
||||
|
||||
_fetch() {
|
||||
var self = this;
|
||||
var loadEl = document.getElementById('loading-monitor');
|
||||
var statusEl = document.getElementById('status-monitor');
|
||||
if (loadEl) loadEl.classList.add('active');
|
||||
if (statusEl) { statusEl.textContent = 'Lade Monitor-Daten...'; statusEl.classList.add('active'); }
|
||||
fetch('/api/monitor-feed')
|
||||
var lageId = (typeof Globe !== 'undefined' && Globe._currentLageId) ? Globe._currentLageId : '';
|
||||
fetch('/api/monitor-feed' + (lageId ? '?incident_id=' + lageId : ''))
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
self._data = data.features || [];
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren