Enterprise-Backup-, Recovery-, Verification-, Security- und Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme. Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes geht in keine Bewertung als "gut" ein. Umfang (Phasen 0-23): - Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll, Katalogaufbau allein aus den Manifesten — ohne Datenbank - Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck - Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell) - Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive - Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit - Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center, Ransomware-Heuristik (meldet, handelt nie) - Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing - Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository - Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64 Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs. Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md und docs/release-candidate.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
/**
|
|
* Kennzahlen und Diagramme.
|
|
*
|
|
* Alle zwoelf Diagramme aus dem Plan erscheinen. Das eine ohne Datengrundlage
|
|
* — die Ressourcenlast der Agenten — steht mit dabei und sagt, was fehlt.
|
|
* Dieselbe Regel wie in der Uebersicht: Ein weggelassenes Diagramm sieht aus wie
|
|
* ein vergessenes, ein leeres wie eine Anlage ohne Betrieb.
|
|
*/
|
|
|
|
import { useCallback, useState } from 'react';
|
|
import { useApiResource } from '../../api/useApiResource';
|
|
import { ErrorState, LoadingState } from '../../components/PageState';
|
|
import { LineChart } from './LineChart';
|
|
import { fetchChart, fetchChartCatalog } from './metricsApi';
|
|
import type { Chart, ChartCatalog, ChartDefinition } from './metricsApi';
|
|
|
|
/** Auswaehlbare Zeitraeume mit ihrer Beschriftung. */
|
|
const RANGE_OPTIONS: readonly { readonly value: string; readonly label: string }[] = [
|
|
{ value: '1h', label: 'Letzte Stunde' },
|
|
{ value: '24h', label: 'Letzte 24 Stunden' },
|
|
{ value: '7d', label: 'Letzte 7 Tage' },
|
|
{ value: '30d', label: 'Letzte 30 Tage' },
|
|
{ value: '90d', label: 'Letzte 90 Tage' },
|
|
{ value: '1y', label: 'Letztes Jahr' },
|
|
];
|
|
|
|
/** Zeigt die Kennzahlen. */
|
|
export function MetricsPage(): React.JSX.Element {
|
|
const [selectedRange, setSelectedRange] = useState('7d');
|
|
|
|
const loadCatalog = useCallback((abortSignal: AbortSignal) => fetchChartCatalog(abortSignal), []);
|
|
const { loadState, data, loadError, reload } = useApiResource<ChartCatalog>(loadCatalog);
|
|
|
|
return (
|
|
<section className="page">
|
|
<header className="page__header">
|
|
<h1 className="page__title">Kennzahlen</h1>
|
|
|
|
{data !== null && (
|
|
<span className="page__meta">
|
|
{data.available_count} von {data.charts.length} Diagrammen haben eine Datengrundlage
|
|
</span>
|
|
)}
|
|
</header>
|
|
|
|
<div className="filter-bar">
|
|
<label className="filter-bar__field">
|
|
<span className="filter-bar__label">Zeitraum</span>
|
|
<select
|
|
className="filter-bar__select"
|
|
value={selectedRange}
|
|
onChange={(changeEvent) => setSelectedRange(changeEvent.target.value)}
|
|
>
|
|
{RANGE_OPTIONS.map((rangeOption) => (
|
|
<option key={rangeOption.value} value={rangeOption.value}>
|
|
{rangeOption.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
{loadState === 'loading' && <LoadingState what="Die Kennzahlen" />}
|
|
{loadState === 'failed' && loadError !== null && (
|
|
<ErrorState error={loadError} onRetry={reload} />
|
|
)}
|
|
|
|
{loadState === 'loaded' && data !== null && (
|
|
<div className="chart-list">
|
|
{data.charts.map((chartDefinition) => (
|
|
<ChartCard
|
|
key={chartDefinition.metric}
|
|
definition={chartDefinition}
|
|
timeRange={selectedRange}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/** Eigenschaften einer Diagrammkachel. */
|
|
interface ChartCardProperties {
|
|
/** Das darzustellende Diagramm. */
|
|
readonly definition: ChartDefinition;
|
|
/** Der gewaehlte Zeitraum. */
|
|
readonly timeRange: string;
|
|
}
|
|
|
|
/** Zeigt ein einzelnes Diagramm. */
|
|
function ChartCard({ definition, timeRange }: ChartCardProperties): React.JSX.Element {
|
|
const loadChart = useCallback(
|
|
(abortSignal: AbortSignal) => fetchChart(definition.metric, timeRange, abortSignal),
|
|
[definition.metric, timeRange],
|
|
);
|
|
|
|
// Ein Diagramm ohne Datengrundlage wird gar nicht erst abgerufen: Der Server
|
|
// antwortete mit 501, und ein Fehler in der Oberflaeche saehe aus wie eine
|
|
// Stoerung. Es ist keine — die Funktion gibt es nur noch nicht.
|
|
const { loadState, data, loadError, reload } = useApiResource<Chart>(
|
|
loadChart,
|
|
definition.available ? `${definition.metric}|${timeRange}` : 'unavailable',
|
|
);
|
|
|
|
if (!definition.available) {
|
|
return (
|
|
<article className="chart-card chart-card--unavailable">
|
|
<h2 className="chart-card__title">{definition.title}</h2>
|
|
<p className="chart-card__badge">noch nicht verfuegbar</p>
|
|
<p className="chart-card__description">{definition.unavailable_reason}</p>
|
|
</article>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<article className="chart-card">
|
|
<h2 className="chart-card__title">{definition.title}</h2>
|
|
<p className="chart-card__description">{definition.description}</p>
|
|
|
|
{loadState === 'loading' && <LoadingState what="Die Reihe" />}
|
|
{loadState === 'failed' && loadError !== null && (
|
|
<ErrorState error={loadError} onRetry={reload} />
|
|
)}
|
|
|
|
{loadState === 'loaded' && data !== null && (
|
|
<>
|
|
<LineChart series={data.series} unit={definition.unit} />
|
|
|
|
{/* Der Hinweis ordnet ein, was die Kurve wert ist: Zwei Punkte sehen
|
|
aus wie ein Trend und sind keiner. */}
|
|
{data.note !== undefined && data.note !== '' && (
|
|
<p className="chart-card__note">{data.note}</p>
|
|
)}
|
|
</>
|
|
)}
|
|
</article>
|
|
);
|
|
}
|