import React, { useCallback, useEffect, useMemo, useState } from "react"; import axios from "axios"; import { useToast } from "./components/ToastProvider.jsx"; const formatCreatedAt = (value) => { if (!value && value !== 0) return "-"; const normalizeToDate = (input) => { if (input instanceof Date) return input; if (typeof input === "number") { const epoch = input > 1e12 ? input : input * 1000; return new Date(epoch); } if (typeof input === "string") { const numeric = Number(input); if (!Number.isNaN(numeric)) { const epoch = numeric > 1e12 ? numeric : numeric * 1000; return new Date(epoch); } return new Date(input); } return null; }; const date = normalizeToDate(value); if (!date || Number.isNaN(date.getTime())) { return String(value); } return date.toLocaleString("de-DE", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }); }; const resolveStackType = (type) => { if (type === 1) return "Git"; if (type === 2) return "Compose"; return type ?? "-"; }; export default function Maintenance() { const { showToast } = useToast(); const [duplicates, setDuplicates] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(""); const [activeCleanupId, setActiveCleanupId] = useState(null); const [lastUpdated, setLastUpdated] = useState(null); const fetchDuplicates = useCallback(async ({ silent = false } = {}) => { if (silent) { setRefreshing(true); } else { setLoading(true); } setError(""); try { const response = await axios.get("/api/maintenance/duplicates"); const payload = response.data; const items = Array.isArray(payload) ? payload : payload?.items ?? []; setDuplicates(items); setLastUpdated(new Date()); } catch (err) { console.error("❌ Fehler beim Laden der Wartungsdaten:", err); setError("Fehler beim Laden der Wartungsdaten"); } finally { if (silent) { setRefreshing(false); } else { setLoading(false); } } }, []); useEffect(() => { fetchDuplicates(); }, [fetchDuplicates]); const totals = useMemo(() => { const groups = Array.isArray(duplicates) ? duplicates.length : 0; const duplicateCount = Array.isArray(duplicates) ? duplicates.reduce((sum, entry) => sum + ((entry?.duplicates?.length) || 0), 0) : 0; return { groups, duplicateCount }; }, [duplicates]); const handleCleanup = useCallback(async (entry) => { if (!entry) return; const canonicalId = entry.canonical?.Id; if (!canonicalId) return; const duplicateIds = (entry.duplicates || []).map((dup) => dup.Id).filter(Boolean); if (!duplicateIds.length) return; const canonicalName = entry.canonical?.Name || entry.name || `Stack ${canonicalId}`; if (typeof window !== "undefined") { const confirmation = window.confirm( `Bereinigung für \"${canonicalName}\" starten?\n` + `Es werden ${duplicateIds.length} Duplikate entfernt: ${duplicateIds.join(", ")}` ); if (!confirmation) { return; } } setActiveCleanupId(String(canonicalId)); try { const response = await axios.post("/api/maintenance/duplicates/cleanup", { canonicalId, duplicateIds }); const payload = response.data ?? {}; if (payload.success === false) { throw new Error(payload.error || "Bereinigung fehlgeschlagen"); } const removedIds = Array.isArray(payload.results) ? payload.results.filter((result) => result.status === "deleted").map((result) => result.id) : duplicateIds; showToast({ variant: "success", title: "Bereinigung abgeschlossen", description: `${canonicalName} – entfernte IDs: ${removedIds.join(", ")}` }); await fetchDuplicates({ silent: true }); } catch (err) { const message = err.response?.data?.error || err.message || "Bereinigung fehlgeschlagen"; console.error("❌ Fehler bei der Duplikat-Bereinigung:", err); showToast({ variant: "error", title: "Bereinigung fehlgeschlagen", description: message }); } finally { setActiveCleanupId(null); } }, [fetchDuplicates, showToast]); const isEmpty = !loading && totals.groups === 0; return (

Wartung

Werkzeuge für wiederkehrende Wartungsaufgaben. Entfernt derzeit doppelte Stack-Einträge.

Dieses Wartungsfeature ist noch nicht getestet. Bitte nicht in Produktivumgebungen einsetzen.

Doppelte Stacks

{loading ? "Analyse läuft…" : totals.groups === 0 ? "Keine Duplikate gefunden" : `${totals.groups} Stack-Namen mit insgesamt ${totals.duplicateCount} Duplikaten gefunden`}

{lastUpdated && (

Stand: {lastUpdated.toLocaleString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}

)}
{error && (
{error}
)} {loading ? (
Daten werden geladen…
) : isEmpty ? (
Es wurden keine doppelten Stacks gefunden.
) : (
{duplicates.map((entry) => { const canonicalId = entry?.canonical?.Id; const duplicatesForEntry = entry?.duplicates || []; const isProcessing = activeCleanupId === String(canonicalId); return (

{entry.name}

{duplicatesForEntry.length} Duplikat{duplicatesForEntry.length === 1 ? "" : "e"}

Behaltener Stack: ID {canonicalId} (Endpoint {entry?.canonical?.EndpointId ?? "-"})

Typ: {resolveStackType(entry?.canonical?.Type)} • Erstellt: {formatCreatedAt(entry?.canonical?.Created)}

{duplicatesForEntry.map((duplicate) => (
ID: {duplicate.Id} Endpoint: {duplicate.EndpointId ?? "-"} Typ: {resolveStackType(duplicate.Type)} Erstellt: {formatCreatedAt(duplicate.Created)}
))}
); })}
)}
); }