import React, { useEffect, useState } from "react"; import axios from "axios"; import { io } from "socket.io-client"; const REDEPLOY_STATE = { IDLE: "idle", QUEUED: "queued", RUNNING: "running" }; export default function Stacks() { const [stacks, setStacks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [selectedStackIds, setSelectedStackIds] = useState([]); const mergeStackState = (previousStacks, incomingStacks) => { const prevMap = new Map(previousStacks.map((stack) => [stack.Id, stack])); const sortedIncoming = [...incomingStacks].sort((a, b) => a.Name.localeCompare(b.Name)); return sortedIncoming.map((stack) => { const previous = prevMap.get(stack.Id); const redeployState = stack.redeployState ?? previous?.redeployState ?? (previous?.redeploying ? REDEPLOY_STATE.RUNNING : REDEPLOY_STATE.IDLE); return { ...stack, redeployState, redeploying: redeployState === REDEPLOY_STATE.RUNNING, redeployQueued: redeployState === REDEPLOY_STATE.QUEUED, redeployDisabled: stack.redeployDisabled ?? previous?.redeployDisabled ?? false }; }); }; useEffect(() => { const socket = io("/", { path: "/socket.io", transports: ["websocket"] }); console.log("🔌 Socket connected"); socket.on("redeployStatus", async ({ stackId, status }) => { const nextState = status || REDEPLOY_STATE.IDLE; console.log(`🔄 Stack ${stackId} Redeploy Status: ${nextState}`); setStacks(prev => prev.map(stack => { if (stack.Id !== stackId) return stack; const updated = { ...stack, redeployState: nextState, redeploying: nextState === REDEPLOY_STATE.RUNNING, redeployQueued: nextState === REDEPLOY_STATE.QUEUED }; if (nextState === REDEPLOY_STATE.IDLE) { updated.updateStatus = "✅"; } return updated; }) ); if (nextState === REDEPLOY_STATE.IDLE) { try { const res = await axios.get("/api/stacks"); setStacks(prev => mergeStackState(prev, res.data)); } catch (err) { console.error("Fehler beim Aktualisieren nach Redeploy:", err); } } }); return () => socket.disconnect(); }, []); const fetchStacks = async () => { setLoading(true); try { const res = await axios.get("/api/stacks"); setStacks(prev => mergeStackState(prev, res.data)); } catch (err) { console.error("❌ Fehler beim Abrufen der Stacks:", err); setError("Fehler beim Laden der Stacks"); } finally { setLoading(false); } }; useEffect(() => { fetchStacks(); }, []); useEffect(() => { setSelectedStackIds(prev => { const filtered = prev.filter(id => { const match = stacks.find(stack => stack.Id === id); return match && match.updateStatus !== "✅" && !match.redeployDisabled && (match.redeployState ?? REDEPLOY_STATE.IDLE) === REDEPLOY_STATE.IDLE; }); return filtered.length === prev.length ? prev : filtered; }); }, [stacks]); const toggleStackSelection = (stackId, disabled) => { if (disabled) return; setSelectedStackIds(prev => prev.includes(stackId) ? prev.filter(id => id !== stackId) : [...prev, stackId] ); }; const handleRedeploy = async (stackId) => { const targetStack = stacks.find((stack) => stack.Id === stackId); if (!targetStack || targetStack.redeployDisabled || targetStack.redeployState !== REDEPLOY_STATE.IDLE) return; setSelectedStackIds((prev) => prev.filter((id) => id !== stackId)); setStacks((prev) => prev.map((stack) => stack.Id === stackId ? { ...stack, redeployState: REDEPLOY_STATE.RUNNING, redeploying: true, redeployQueued: false } : stack ) ); try { await axios.put(`/api/stacks/${stackId}/redeploy`); // Statusupdates kommen über Socket.IO } catch (err) { console.error("❌ Fehler beim Redeploy:", err); setStacks((prev) => prev.map((stack) => stack.Id === stackId ? { ...stack, redeployState: REDEPLOY_STATE.IDLE, redeploying: false, redeployQueued: false } : stack ) ); } }; const handleRedeployAll = async () => { const outdatedStacks = stacks.filter((stack) => stack.updateStatus !== "✅" && !stack.redeployDisabled && (stack.redeployState ?? REDEPLOY_STATE.IDLE) === REDEPLOY_STATE.IDLE ); if (!outdatedStacks.length) return; const outdatedIds = new Set(outdatedStacks.map((stack) => stack.Id)); setStacks(prev => prev.map(stack => outdatedIds.has(stack.Id) ? { ...stack, redeployState: REDEPLOY_STATE.QUEUED, redeploying: false, redeployQueued: true } : stack ) ); try { await axios.put("/api/stacks/redeploy-all"); setSelectedStackIds((prev) => prev.filter((id) => !outdatedIds.has(id))); // Statusupdates kommen über Socket.IO } catch (err) { console.error("❌ Fehler beim Redeploy ALL:", err); setStacks(prev => prev.map(stack => outdatedIds.has(stack.Id) ? { ...stack, redeployState: REDEPLOY_STATE.IDLE, redeploying: false, redeployQueued: false } : stack ) ); } }; const handleRedeploySelection = async () => { if (!selectedStackIds.length) return; const eligibleIds = selectedStackIds.filter((id) => { const stack = stacks.find((entry) => entry.Id === id); return stack && stack.updateStatus !== "✅" && !stack.redeployDisabled && (stack.redeployState ?? REDEPLOY_STATE.IDLE) === REDEPLOY_STATE.IDLE; }); if (!eligibleIds.length) { setSelectedStackIds([]); return; } const eligibleSet = new Set(eligibleIds); setStacks(prev => prev.map(stack => eligibleSet.has(stack.Id) ? { ...stack, redeployState: REDEPLOY_STATE.QUEUED, redeploying: false, redeployQueued: true } : stack ) ); try { await axios.put("/api/stacks/redeploy-selection", { stackIds: eligibleIds }); setSelectedStackIds((prev) => prev.filter((id) => !eligibleSet.has(id))); // Statusupdates kommen über Socket.IO } catch (err) { console.error("❌ Fehler beim Redeploy Auswahl:", err); setStacks(prev => prev.map(stack => eligibleSet.has(stack.Id) ? { ...stack, redeployState: REDEPLOY_STATE.IDLE, redeploying: false, redeployQueued: false } : stack ) ); } }; const hasSelection = selectedStackIds.length > 0; const hasRedeployableStacks = stacks.some((stack) => stack.updateStatus !== "✅" && !stack.redeployDisabled && (stack.redeployState ?? REDEPLOY_STATE.IDLE) === REDEPLOY_STATE.IDLE ); const bulkButtonLabel = hasSelection ? `Redeploy Auswahl (${selectedStackIds.length})` : "Redeploy All"; const bulkActionDisabled = hasSelection ? selectedStackIds.length === 0 || selectedStackIds.every(id => { const targetStack = stacks.find(stack => stack.Id === id); return !targetStack || targetStack.updateStatus === "✅" || targetStack.redeployDisabled || (targetStack.redeployState ?? REDEPLOY_STATE.IDLE) !== REDEPLOY_STATE.IDLE; }) : !hasRedeployableStacks; const handleBulkRedeploy = () => { if (hasSelection) { handleRedeploySelection(); } else { handleRedeployAll(); } }; if (loading) return
Lade Stacks...
; if (error) return{error}
; return ({stack.Name}
ID: {stack.Id}
{stack.duplicateName && (⚠️ Doppelter Name erkannt
)}Keine Stacks gefunden.
}