import React, { useEffect, useState } from "react"; import axios from "axios"; import { io } from "socket.io-client"; 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); return { ...stack, redeploying: previous?.redeploying || stack.redeploying || false, redeployDisabled: stack.redeployDisabled ?? previous?.redeployDisabled ?? false, duplicateName: stack.duplicateName ?? previous?.duplicateName ?? false }; }); }; useEffect(() => { const socket = io("/", { path: "/socket.io", transports: ["websocket"] }); console.log("🔌 Socket connected"); socket.on("redeployStatus", async ({ stackId, status }) => { console.log(`🔄 Stack ${stackId} Redeploy Status: ${status ? "running" : "finished"}`); setStacks(prev => prev.map(stack => { if (stack.Id !== stackId) return stack; return { ...stack, redeploying: status, updateStatus: status ? stack.updateStatus : '✅' }; }) ); if (!status) { // Status nach Redeploy neu vom Server holen 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; }); 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) => { setSelectedStackIds((prev) => prev.filter((id) => id !== stackId)); setStacks((prev) => prev.map((stack) => stack.Id === stackId ? { ...stack, redeploying: true } : 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, redeploying: false } : stack ) ); } }; const handleRedeployAll = async () => { const outdatedStacks = stacks.filter((stack) => stack.updateStatus !== '✅' && !stack.redeployDisabled); if (!outdatedStacks.length) return; const outdatedIds = new Set(outdatedStacks.map((stack) => stack.Id)); setStacks(prev => prev.map(stack => outdatedIds.has(stack.Id) ? { ...stack, redeploying: 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, redeploying: 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; }); if (!eligibleIds.length) { setSelectedStackIds([]); return; } const eligibleSet = new Set(eligibleIds); setStacks(prev => prev.map(stack => eligibleSet.has(stack.Id) ? { ...stack, redeploying: 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, redeploying: false } : stack ) ); } }; const hasSelection = selectedStackIds.length > 0; const hasOutdatedStacks = stacks.some((stack) => stack.updateStatus !== '✅' && !stack.redeployDisabled); 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.redeploying || targetStack.updateStatus === '✅' || targetStack.redeployDisabled; }) : !hasOutdatedStacks || stacks.every(stack => stack.updateStatus !== '✅' || stack.redeploying || stack.redeployDisabled); const handleBulkRedeploy = () => { if (hasSelection) { handleRedeploySelection(); } else { handleRedeployAll(); } }; if (loading) return

Lade Stacks...

; if (error) return

{error}

; return (
{stacks.map(stack => { const isRedeploying = stack.redeploying; const isSelected = selectedStackIds.includes(stack.Id); const isCurrent = stack.updateStatus === '✅'; const isSelfStack = Boolean(stack.redeployDisabled); const isSelectable = !isRedeploying && !isCurrent && !isSelfStack; return (
toggleStackSelection(stack.Id, !isSelectable)} className={`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${!isSelectable ? 'opacity-40 cursor-not-allowed' : ''}`} disabled={!isSelectable} />

{stack.Name}

ID: {stack.Id}

{stack.duplicateName && (

⚠️ Doppelter Name erkannt

)}
{isRedeploying ? ( <> Redeploy läuft… ) : isSelfStack ? ( <> System Redeploy deaktiviert ) : isCurrent ? ( <> Status Aktuell ) : ( <> )}
); })} {stacks.length === 0 &&

Keine Stacks gefunden.

}
); }