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 (
{stacks.map(stack => {
- const isRedeploying = redeploying[stack.Id] || false;
- const isUpToDate = stack.updateStatus === "✅";
+ const isRedeploying = Boolean(redeploying[stack.Id]);
return (
"gedämpfter" Container (sichtbar deaktiviert)
className={`flex justify-between items-center p-5 rounded-xl shadow-lg transition
- ${isRedeploying ? "bg-gray-700 cursor-not-allowed" : "bg-gray-800 hover:bg-gray-700"}`}
+ ${isRedeploying ? "bg-gray-700 opacity-60" : "bg-gray-800 hover:bg-gray-700"}`}
+ aria-disabled={isRedeploying}
>
- {/* Status Indicator */}
-
+ />
{stack.Name}
ID: {stack.Id}
- {!isUpToDate && (
-
- )}
+ {/* Button ist jetzt immer sichtbar */}
+
);
})}
+
{stacks.length === 0 &&
Keine Stacks gefunden.
}
);
diff --git a/frontend/src/Stacks.jsx.bak b/frontend/src/Stacks.jsx.bak
new file mode 100644
index 0000000..9560ebd
--- /dev/null
+++ b/frontend/src/Stacks.jsx.bak
@@ -0,0 +1,109 @@
+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 [redeploying, setRedeploying] = useState({});
+
+ // WebSocket initialisieren
+ useEffect(() => {
+ const socket = io("/");
+ console.log("Socket connected");
+
+ socket.on("redeployStatus", async ({ stackId, status }) => {
+ setRedeploying(prev => ({ ...prev, [stackId]: status }));
+
+ // Wenn Redeploy beendet, Stack-Status neu laden
+ if (!status) {
+ try {
+ const res = await axios.get("/api/stacks");
+ const sortedStacks = res.data.sort((a, b) => a.Name.localeCompare(b.Name));
+ setStacks(sortedStacks);
+ } catch (err) {
+ console.error("Fehler beim Aktualisieren des Status nach Redeploy:", err);
+ }
+ }
+ });
+
+ return () => socket.disconnect();
+ }, []);
+
+ // Stacks initial laden
+ const fetchStacks = async () => {
+ try {
+ const res = await axios.get("/api/stacks");
+ const sortedStacks = res.data.sort((a, b) => a.Name.localeCompare(b.Name));
+ setStacks(sortedStacks);
+ } catch (err) {
+ console.error("❌ Fehler beim Abrufen der Stacks:", err);
+ setError("Fehler beim Laden der Stacks");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchStacks();
+ }, []);
+
+ // Redeploy eines Stacks
+ const handleRedeploy = async (stackId) => {
+ setRedeploying(prev => ({ ...prev, [stackId]: true }));
+
+ try {
+ await axios.put(`/api/stacks/${stackId}/redeploy`);
+ } catch (err) {
+ console.error("❌ Fehler beim Redeploy:", err);
+ setRedeploying(prev => ({ ...prev, [stackId]: false }));
+ }
+ };
+
+ if (loading) return Lade Stacks...
;
+ if (error) return {error}
;
+
+ return (
+
+ {stacks.map(stack => {
+ const isRedeploying = redeploying[stack.Id] || false;
+ const isUpToDate = stack.updateStatus === "✅";
+
+ return (
+
+
+ {/* Status Indicator */}
+
+
+
+
{stack.Name}
+
ID: {stack.Id}
+
+
+
+ {!isUpToDate && (
+
+ )}
+
+ );
+ })}
+ {stacks.length === 0 &&
Keine Stacks gefunden.
}
+
+ );
+}
diff --git a/scripts/docker-release.sh b/scripts/docker-release.sh
old mode 100644
new mode 100755
index c079f8e..d584e68
--- a/scripts/docker-release.sh
+++ b/scripts/docker-release.sh
@@ -2,8 +2,8 @@
set -e
# --- Konfiguration ---
-GHCR_USERNAME=""
-REPO_NAME=""
+GHCR_USERNAME="mboehmlaender"
+REPO_NAME="stackpulse"
# --- Branch prüfen ---
BRANCH=$(git rev-parse --abbrev-ref HEAD)
@@ -14,32 +14,10 @@ fi
# --- Versionsnummer abfragen ---
while true; do
- read -p "Bitte Versionsnummer für das Release eingeben (z.B. v0.2.0): " VERSION_TAG
+ read -p "Bitte Versionsnummer für das Docker-Image eingeben (z.B. v0.1): " VERSION_TAG
if [[ -n "$VERSION_TAG" ]]; then break; else echo "Versionsnummer darf nicht leer sein."; fi
done
-# --- Git: Pull und Merge sicherstellen ---
-git pull origin master
-
-# --- Git: Commit ausstehender Änderungen ---
-git add .
-read -p "Commit-Nachricht eingeben (default: 'Release $VERSION_TAG'): " COMMIT_MSG
-COMMIT_MSG=${COMMIT_MSG:-"Release $VERSION_TAG"}
-git commit -m "$COMMIT_MSG" || echo "Keine Änderungen zum Committen."
-
-# --- Git: Tag setzen ---
-if git rev-parse "$VERSION_TAG" >/dev/null 2>&1; then
- echo "Tag $VERSION_TAG existiert bereits. Bitte zuerst löschen oder neuen Tag wählen."
- exit 1
-fi
-
-git tag -a "$VERSION_TAG" -m "Release $VERSION_TAG"
-
-# --- Git: Push master + Tag ---
-git push origin master
-git push origin "$VERSION_TAG"
-echo "Tag $VERSION_TAG gesetzt und auf master gepusht."
-
# --- Docker: Login ---
if [ -z "$CR_PAT" ]; then
echo "CR_PAT (GitHub Token) nicht gesetzt! Bitte export CR_PAT="
@@ -55,4 +33,4 @@ docker tag ghcr.io/$GHCR_USERNAME/$REPO_NAME:$VERSION_TAG ghcr.io/$GHCR_USERNAME
docker push ghcr.io/$GHCR_USERNAME/$REPO_NAME:$VERSION_TAG
docker push ghcr.io/$GHCR_USERNAME/$REPO_NAME:latest
-echo "Release $VERSION_TAG erfolgreich auf master + GHCR gepusht!"
+echo "Docker-Release $VERSION_TAG erfolgreich gebaut und zu GHCR gepusht!"