From ec2d998bc52c930c7b45eb30cb497441e69f4032 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 1 Oct 2025 10:40:52 +0000 Subject: [PATCH] Update --- backend/index.js | 42 +++++++++++++++++++++++---- docker-compose.yml | 1 + frontend/src/Stacks.jsx | 63 +++++++++++++++++++++++++++++------------ 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/backend/index.js b/backend/index.js index 487f51c..c22e158 100644 --- a/backend/index.js +++ b/backend/index.js @@ -62,6 +62,8 @@ const REDEPLOY_TYPES = { SELECTION: 'Auswahl' }; +const SELF_STACK_ID = process.env.SELF_STACK_ID ? String(process.env.SELF_STACK_ID) : null; + const isStackOutdated = async (stackId) => { try { const statusRes = await axiosInstance.get(`/api/stacks/${stackId}/images_status?refresh=true`); @@ -76,7 +78,9 @@ const filterOutdatedStacks = async (stacks = []) => { const results = await Promise.all( stacks.map(async (stack) => ({ stack, - outdated: await isStackOutdated(stack.Id) + outdated: SELF_STACK_ID && String(stack.Id) === SELF_STACK_ID + ? false + : await isStackOutdated(stack.Id) })) ); @@ -95,11 +99,31 @@ app.get('/api/stacks', async (req, res) => { const stacksRes = await axiosInstance.get('/api/stacks'); const filteredStacks = stacksRes.data.filter(stack => stack.EndpointId === ENDPOINT_ID); - const uniqueStacksMap = {}; + const stacksByName = new Map(); + const duplicateNames = new Set(); + filteredStacks.forEach(stack => { - if (!uniqueStacksMap[stack.Name]) uniqueStacksMap[stack.Name] = stack; + const name = stack.Name; + const isSelf = SELF_STACK_ID && String(stack.Id) === SELF_STACK_ID; + const existingEntry = stacksByName.get(name); + + if (!existingEntry) { + stacksByName.set(name, { stack, isSelf }); + return; + } + + duplicateNames.add(name); + + if (!existingEntry.isSelf && isSelf) { + stacksByName.set(name, { stack, isSelf }); + } }); - const uniqueStacks = Object.values(uniqueStacksMap); + + const uniqueStacks = Array.from(stacksByName.values()).map(entry => entry.stack); + + if (duplicateNames.size) { + console.warn(`⚠️ [API] GET /api/stacks: Doppelte Stack-Namen erkannt: ${Array.from(duplicateNames).join(', ')}`); + } const stacksWithStatus = await Promise.all( uniqueStacks.map(async (stack) => { @@ -111,11 +135,17 @@ app.get('/api/stacks', async (req, res) => { return { ...stack, updateStatus: statusEmoji, - redeploying: redeployingStacks[stack.Id] || false + redeploying: redeployingStacks[stack.Id] || false, + redeployDisabled: SELF_STACK_ID ? String(stack.Id) === SELF_STACK_ID : false }; } catch (err) { console.error(`❌ Fehler beim Abrufen des Status für Stack ${stack.Id}:`, err.message); - return { ...stack, updateStatus: '❌', redeploying: redeployingStacks[stack.Id] || false }; + return { + ...stack, + updateStatus: '❌', + redeploying: redeployingStacks[stack.Id] || false, + redeployDisabled: SELF_STACK_ID ? String(stack.Id) === SELF_STACK_ID : false + }; } }) ); diff --git a/docker-compose.yml b/docker-compose.yml index 1202a14..0bbde2c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: PORTAINER_URL: "Your_Portainer_Server_Address" PORTAINER_API_KEY: "Your_Portainer_API_Key" PORTAINER_ENDPOINT_ID: "Your_Portainer_Endpoint_ID" + SELF_STACK_ID: "Stackpulse ID" restart: unless-stopped volumes: diff --git a/frontend/src/Stacks.jsx b/frontend/src/Stacks.jsx index fd59069..d96e3fe 100644 --- a/frontend/src/Stacks.jsx +++ b/frontend/src/Stacks.jsx @@ -8,6 +8,20 @@ export default function Stacks() { 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 + }; + }); + }; + useEffect(() => { const socket = io("/", { path: "/socket.io", @@ -18,22 +32,25 @@ export default function Stacks() { 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"); - const sortedStacks = [...res.data].sort((a, b) => a.Name.localeCompare(b.Name)); - setStacks(sortedStacks.map(stack => ({ ...stack, redeploying: stack.redeploying || false }))); + setStacks(prev => mergeStackState(prev, res.data)); } catch (err) { console.error("Fehler beim Aktualisieren nach Redeploy:", err); } - } else { - // UI direkt auf redeploying setzen - setStacks(prev => - prev.map(stack => - stack.Id === stackId ? { ...stack, redeploying: true } : stack - ) - ); } }); @@ -44,8 +61,7 @@ export default function Stacks() { setLoading(true); try { const res = await axios.get("/api/stacks"); - const sortedStacks = [...res.data].sort((a, b) => a.Name.localeCompare(b.Name)); - setStacks(sortedStacks.map(stack => ({ ...stack, redeploying: stack.redeploying || false }))); + setStacks(prev => mergeStackState(prev, res.data)); } catch (err) { console.error("❌ Fehler beim Abrufen der Stacks:", err); setError("Fehler beim Laden der Stacks"); @@ -62,7 +78,7 @@ export default function Stacks() { setSelectedStackIds(prev => { const filtered = prev.filter(id => { const match = stacks.find(stack => stack.Id === id); - return match && match.updateStatus !== '✅'; + return match && match.updateStatus !== '✅' && !match.redeployDisabled; }); return filtered.length === prev.length ? prev : filtered; }); @@ -78,6 +94,9 @@ export default function Stacks() { }; const handleRedeploy = async (stackId) => { + const targetStack = stacks.find((stack) => stack.Id === stackId); + if (targetStack?.redeployDisabled) return; + setSelectedStackIds((prev) => prev.filter((id) => id !== stackId)); setStacks((prev) => prev.map((stack) => @@ -99,7 +118,7 @@ export default function Stacks() { }; const handleRedeployAll = async () => { - const outdatedStacks = stacks.filter((stack) => stack.updateStatus !== '✅'); + const outdatedStacks = stacks.filter((stack) => stack.updateStatus !== '✅' && !stack.redeployDisabled); if (!outdatedStacks.length) return; const outdatedIds = new Set(outdatedStacks.map((stack) => stack.Id)); @@ -133,7 +152,7 @@ export default function Stacks() { const eligibleIds = selectedStackIds.filter((id) => { const stack = stacks.find((entry) => entry.Id === id); - return stack && stack.updateStatus !== '✅'; + return stack && stack.updateStatus !== '✅' && !stack.redeployDisabled; }); if (!eligibleIds.length) { @@ -168,7 +187,7 @@ export default function Stacks() { }; const hasSelection = selectedStackIds.length > 0; - const hasOutdatedStacks = stacks.some((stack) => stack.updateStatus !== '✅'); + const hasOutdatedStacks = stacks.some((stack) => stack.updateStatus !== '✅' && !stack.redeployDisabled); const bulkButtonLabel = hasSelection ? `Redeploy Auswahl (${selectedStackIds.length})` : 'Redeploy All'; @@ -176,9 +195,9 @@ export default function Stacks() { const bulkActionDisabled = hasSelection ? selectedStackIds.length === 0 || selectedStackIds.every(id => { const targetStack = stacks.find(stack => stack.Id === id); - return !targetStack || targetStack.redeploying || targetStack.updateStatus === '✅'; + return !targetStack || targetStack.redeploying || targetStack.updateStatus === '✅' || targetStack.redeployDisabled; }) - : !hasOutdatedStacks || stacks.every(stack => stack.updateStatus !== '✅' || stack.redeploying); + : !hasOutdatedStacks || stacks.every(stack => stack.updateStatus !== '✅' || stack.redeploying || stack.redeployDisabled); const handleBulkRedeploy = () => { if (hasSelection) { @@ -208,7 +227,8 @@ export default function Stacks() { const isRedeploying = stack.redeploying; const isSelected = selectedStackIds.includes(stack.Id); const isCurrent = stack.updateStatus === '✅'; - const isSelectable = !isRedeploying && !isCurrent; + const isSelfStack = Boolean(stack.redeployDisabled); + const isSelectable = !isRedeploying && !isCurrent && !isSelfStack; return (
Redeploy läuft… + ) : isSelfStack ? ( + <> + System + Redeploy deaktiviert + ) : isCurrent ? ( <> Status @@ -250,6 +275,8 @@ export default function Stacks() { ) : ( <> + Status + Veraltet