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] = {}
|
_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")
|
@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."""
|
"""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:
|
if cache_key in _cache and time.time() - _cache[cache_key][0] < 120:
|
||||||
return _cache[cache_key][1]
|
return _cache[cache_key][1]
|
||||||
|
|
||||||
|
|||||||
@@ -412,3 +412,24 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-dim);
|
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>
|
</svg>
|
||||||
<span class="header-title">AegisSight Globe</span>
|
<span class="header-title">AegisSight Globe</span>
|
||||||
</div>
|
</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">
|
<div class="header-actions">
|
||||||
<button class="header-btn" id="btn-fullscreen" title="Vollbild">
|
<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>
|
<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,
|
viewer: null,
|
||||||
layers: {},
|
layers: {},
|
||||||
_statsInterval: null,
|
_statsInterval: null,
|
||||||
|
_currentLageId: null,
|
||||||
_labelsLayer: null,
|
_labelsLayer: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -83,6 +84,7 @@ const Globe = {
|
|||||||
this._toggleLabels(true);
|
this._toggleLabels(true);
|
||||||
if (typeof VisualModes !== 'undefined') VisualModes.init();
|
if (typeof VisualModes !== 'undefined') VisualModes.init();
|
||||||
if (typeof Sidebar !== 'undefined') Sidebar.init();
|
if (typeof Sidebar !== 'undefined') Sidebar.init();
|
||||||
|
this._loadLagen();
|
||||||
document.getElementById('bottom-stats').textContent = 'Globe initialisiert — Lade Daten...';
|
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() {
|
_setupLayerToggles() {
|
||||||
var toggles = {
|
var toggles = {
|
||||||
'layer-monitor': function(on) { on ? MonitorLayer.start(Globe.viewer) : MonitorLayer.stop(); },
|
'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() {
|
_fetch() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var loadEl = document.getElementById('loading-monitor');
|
var loadEl = document.getElementById('loading-monitor');
|
||||||
var statusEl = document.getElementById('status-monitor');
|
var statusEl = document.getElementById('status-monitor');
|
||||||
if (loadEl) loadEl.classList.add('active');
|
if (loadEl) loadEl.classList.add('active');
|
||||||
if (statusEl) { statusEl.textContent = 'Lade Monitor-Daten...'; statusEl.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(r) { return r.json(); })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
self._data = data.features || [];
|
self._data = data.features || [];
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren