feat(monitoring): add priority screenshot pipeline with screenshot_type + docs cleanup
Implement end-to-end support for typed screenshots and priority rendering in monitoring. Added - Accept and forward screenshot_type from MQTT screenshot/dashboard payloads (periodic, event_start, event_stop) - Extend screenshot upload handling to persist typed screenshots and metadata - Add dedicated priority screenshot serving endpoint with fallback behavior - Extend monitoring overview with priority screenshot fields and summary count - Add configurable PRIORITY_SCREENSHOT_TTL_SECONDS window for active priority state Fixed - Ensure screenshot cache-busting updates reliably via screenshot hash updates - Preserve normal periodic screenshot flow while introducing event_start/event_stop priority path Improved - Monitoring dashboard now displays screenshot type badges - Adaptive polling: faster refresh while priority screenshots are active - Priority screenshot presentation is surfaced immediately to operators Docs - Update README and copilot-instructions to match new screenshot_type behavior, priority endpoint, TTL config, monitoring fields, and retention model - Remove redundant/duplicate documentation blocks and improve troubleshooting section clarity
This commit is contained in:
@@ -26,6 +26,10 @@ export interface MonitoringClient {
|
||||
screenHealthStatus?: string | null;
|
||||
lastScreenshotAnalyzed?: string | null;
|
||||
lastScreenshotHash?: string | null;
|
||||
latestScreenshotType?: 'periodic' | 'event_start' | 'event_stop' | null;
|
||||
priorityScreenshotType?: 'event_start' | 'event_stop' | null;
|
||||
priorityScreenshotReceivedAt?: string | null;
|
||||
hasActivePriorityScreenshot?: boolean;
|
||||
screenshotUrl: string;
|
||||
logCounts24h: {
|
||||
error: number;
|
||||
@@ -47,6 +51,7 @@ export interface MonitoringOverview {
|
||||
criticalClients: number;
|
||||
errorLogs: number;
|
||||
warnLogs: number;
|
||||
activePriorityScreenshots: number;
|
||||
};
|
||||
periodHours: number;
|
||||
gracePeriodSeconds: number;
|
||||
|
||||
@@ -194,6 +194,32 @@
|
||||
margin-top: 0.55rem;
|
||||
font-size: 0.88rem;
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.monitoring-shot-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
padding: 0.15rem 0.55rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.monitoring-shot-type-periodic {
|
||||
background: #e2e8f0;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.monitoring-shot-type-event {
|
||||
background: #ffedd5;
|
||||
color: #9a3412;
|
||||
}
|
||||
|
||||
.monitoring-shot-type-active {
|
||||
box-shadow: 0 0 0 2px #fdba74;
|
||||
}
|
||||
|
||||
.monitoring-error-box {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
||||
import './monitoring.css';
|
||||
|
||||
const REFRESH_INTERVAL_MS = 15000;
|
||||
const PRIORITY_REFRESH_INTERVAL_MS = 3000;
|
||||
|
||||
const hourOptions = [
|
||||
{ text: 'Letzte 6 Stunden', value: 6 },
|
||||
@@ -95,6 +96,19 @@ function statusBadge(status: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function screenshotTypeBadge(type?: string | null, hasPriority = false) {
|
||||
const normalized = (type || 'periodic').toLowerCase();
|
||||
const map: Record<string, { label: string; className: string }> = {
|
||||
periodic: { label: 'Periodisch', className: 'monitoring-shot-type-periodic' },
|
||||
event_start: { label: 'Event-Start', className: 'monitoring-shot-type-event' },
|
||||
event_stop: { label: 'Event-Stopp', className: 'monitoring-shot-type-event' },
|
||||
};
|
||||
|
||||
const info = map[normalized] || map.periodic;
|
||||
const classes = `monitoring-shot-type ${info.className}${hasPriority ? ' monitoring-shot-type-active' : ''}`;
|
||||
return <span className={classes}>{info.label}</span>;
|
||||
}
|
||||
|
||||
function renderMetricCard(title: string, value: number, subtitle: string, accent: string) {
|
||||
return (
|
||||
<div className="e-card monitoring-metric-card" style={{ borderTop: `4px solid ${accent}` }}>
|
||||
@@ -188,12 +202,14 @@ const MonitoringDashboard: React.FC = () => {
|
||||
}, [hours, loadOverview]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const hasActivePriorityScreenshots = (overview?.summary.activePriorityScreenshots || 0) > 0;
|
||||
const intervalMs = hasActivePriorityScreenshots ? PRIORITY_REFRESH_INTERVAL_MS : REFRESH_INTERVAL_MS;
|
||||
const intervalId = window.setInterval(() => {
|
||||
loadOverview(hours);
|
||||
}, REFRESH_INTERVAL_MS);
|
||||
}, intervalMs);
|
||||
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, [hours, loadOverview]);
|
||||
}, [hours, loadOverview, overview?.summary.activePriorityScreenshots]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!selectedClientUuid) {
|
||||
@@ -288,6 +304,7 @@ const MonitoringDashboard: React.FC = () => {
|
||||
{renderMetricCard('Warnungen', overview?.summary.warningClients || 0, 'Warn-Logs oder Übergangszustände', '#d97706')}
|
||||
{renderMetricCard('Kritisch', overview?.summary.criticalClients || 0, 'Crashs oder Fehler-Logs', '#dc2626')}
|
||||
{renderMetricCard('Offline', overview?.summary.offlineClients || 0, 'Keine frischen Signale', '#475569')}
|
||||
{renderMetricCard('Prioritäts-Screens', overview?.summary.activePriorityScreenshots || 0, 'Event-Start/Stop aktiv', '#ea580c')}
|
||||
{renderMetricCard('Fehler-Logs', overview?.summary.errorLogs || 0, 'Im gewählten Zeitraum', '#b91c1c')}
|
||||
</div>
|
||||
|
||||
@@ -380,6 +397,21 @@ const MonitoringDashboard: React.FC = () => {
|
||||
<span>Letzte Analyse</span>
|
||||
<strong>{formatTimestamp(selectedClient.lastScreenshotAnalyzed)}</strong>
|
||||
</div>
|
||||
<div className="monitoring-detail-row">
|
||||
<span>Screenshot-Typ</span>
|
||||
<strong>
|
||||
{screenshotTypeBadge(
|
||||
selectedClient.latestScreenshotType,
|
||||
!!selectedClient.hasActivePriorityScreenshot
|
||||
)}
|
||||
</strong>
|
||||
</div>
|
||||
{selectedClient.priorityScreenshotReceivedAt && (
|
||||
<div className="monitoring-detail-row">
|
||||
<span>Priorität empfangen</span>
|
||||
<strong>{formatTimestamp(selectedClient.priorityScreenshotReceivedAt)}</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<MessageComponent severity="Info" content="Wählen Sie links einen Client aus." />
|
||||
@@ -407,7 +439,14 @@ const MonitoringDashboard: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
<div className="monitoring-screenshot-meta">
|
||||
Empfangen: {formatTimestamp(selectedClient.lastScreenshotAnalyzed)}
|
||||
<span>Empfangen: {formatTimestamp(selectedClient.lastScreenshotAnalyzed)}</span>
|
||||
<span>
|
||||
Typ:{' '}
|
||||
{screenshotTypeBadge(
|
||||
selectedClient.latestScreenshotType,
|
||||
!!selectedClient.hasActivePriorityScreenshot
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user