diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1c11102 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +PORT=4000 +NODE_ENV=production +PORTAINER_URL=https://your-portainer.example.com +PORTAINER_API_KEY=your_api_key_here diff --git a/frontend/src/Stacks.jsx b/frontend/src/Stacks.jsx index 9560ebd..8b7bf27 100644 --- a/frontend/src/Stacks.jsx +++ b/frontend/src/Stacks.jsx @@ -6,37 +6,59 @@ export default function Stacks() { const [stacks, setStacks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); + // Map: { [stackId]: boolean } const [redeploying, setRedeploying] = useState({}); - // WebSocket initialisieren + // ------------------------- + // WebSocket: connect + events + // ------------------------- useEffect(() => { - const socket = io("/"); - console.log("Socket connected"); + // wenn Frontend und Backend auf verschiedenen Hosts/Ports laufen: + // const socket = io("http://localhost:4001"); + const socket = io(); // gleiche Origin (Express static) -> passt normalerweise + socket.on("connect", () => console.log("Socket connected:", socket.id)); socket.on("redeployStatus", async ({ stackId, status }) => { + // sofort den lokalen map-state setzen (optimistisch) setRedeploying(prev => ({ ...prev, [stackId]: status })); - // Wenn Redeploy beendet, Stack-Status neu laden + // Wenn der Redeploy zu false geht, holen wir die aktuellen Stacks (aktualisieren UI) if (!status) { try { const res = await axios.get("/api/stacks"); const sortedStacks = res.data.sort((a, b) => a.Name.localeCompare(b.Name)); setStacks(sortedStacks); + + // Map aus API-Daten aufbauen (wichtig für F5 / Konsistenz) + const map = {}; + sortedStacks.forEach(s => { map[s.Id] = !!s.redeploying; }); + setRedeploying(map); } catch (err) { console.error("Fehler beim Aktualisieren des Status nach Redeploy:", err); } } }); - return () => socket.disconnect(); + return () => { + socket.off("redeployStatus"); + socket.disconnect(); + }; }, []); - // Stacks initial laden + // ------------------------- + // Initiale Stacks laden (auch setzt redeploying map) + // ------------------------- const fetchStacks = async () => { + setLoading(true); try { const res = await axios.get("/api/stacks"); const sortedStacks = res.data.sort((a, b) => a.Name.localeCompare(b.Name)); setStacks(sortedStacks); + + // Wichtig: redeploying-Map aus API-Daten setzen, damit F5 den echten Zustand zeigt + const map = {}; + sortedStacks.forEach(s => { map[s.Id] = !!s.redeploying; }); + setRedeploying(map); } catch (err) { console.error("❌ Fehler beim Abrufen der Stacks:", err); setError("Fehler beim Laden der Stacks"); @@ -49,60 +71,71 @@ export default function Stacks() { fetchStacks(); }, []); - // Redeploy eines Stacks + // ------------------------- + // Redeploy Trigger + // ------------------------- const handleRedeploy = async (stackId) => { + // sofort UI-Feedback setRedeploying(prev => ({ ...prev, [stackId]: true })); try { await axios.put(`/api/stacks/${stackId}/redeploy`); + // kein sofortiges setRedeploying(false) – Backend sendet das finale Event } catch (err) { console.error("❌ Fehler beim Redeploy:", err); + // Fehlerfall: wieder auf false setzen damit UI nicht hängen bleibt setRedeploying(prev => ({ ...prev, [stackId]: false })); } }; + // ------------------------- + // Render + // ------------------------- if (loading) return
Lade Stacks...
; if (error) return{error}
; return ({stack.Name}
ID: {stack.Id}
Keine Stacks gefunden.
}