diff --git a/.env.example b/.env.example deleted file mode 100644 index 1c11102..0000000 --- a/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -PORT=4000 -NODE_ENV=production -PORTAINER_URL=https://your-portainer.example.com -PORTAINER_API_KEY=your_api_key_here diff --git a/.gitignore b/.gitignore index deed335..1ae8931 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules/ dist/ -.env +backend/.env diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..097ab98 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,3 @@ +PORTAINER_URL=Your_Portainer_Server_Adress +PORTAINER_API_KEY=Your_Portainer_API_Key +PORTAINER_ENDPOINT_ID=Your_Portainer_Endpoint_ID diff --git a/backend/index.js.bak b/backend/index.js.bak deleted file mode 100644 index ee67420..0000000 --- a/backend/index.js.bak +++ /dev/null @@ -1,154 +0,0 @@ -import express from 'express'; -import dotenv from 'dotenv'; -import https from 'https'; -import axios from 'axios'; -import http from 'http'; -import { Server } from 'socket.io'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -dotenv.config(); - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const app = express(); -app.use(express.json()); - -// Statische Dateien -app.use(express.static(path.join(__dirname, 'public'))); - -// SPA-Fallback -app.get('*', (req, res, next) => { - if (req.path.startsWith('/api')) return next(); - res.sendFile(path.join(__dirname, 'public', 'index.html')); -}); - -const PORT = 4001; -const ENDPOINT_ID = Number(process.env.PORTAINER_ENDPOINT_ID); - -// HTTPS Agent -const agent = new https.Agent({ rejectUnauthorized: false }); - -// Axios fΓΌr Portainer -const axiosInstance = axios.create({ - httpsAgent: agent, - headers: { "X-API-Key": process.env.PORTAINER_API_KEY }, - baseURL: process.env.PORTAINER_URL, -}); - -const redeployingStacks = {}; - -// HTTP Server + Socket.IO -const server = http.createServer(app); -const io = new Server(server, { cors: { origin: "*" } }); - -io.on("connection", (socket) => { - console.log(`πŸ”Œ [Socket] Client verbunden: ${socket.id}`); -}); - -const broadcastRedeployStatus = (stackId, status) => { - redeployingStacks[stackId] = status; - io.emit("redeployStatus", { stackId, status }); - console.log(`πŸ”„ [RedeployStatus] Stack ${stackId} ist jetzt ${status ? "im Redeploy" : "fertig"}`); -}; - -// --- API Endpoints --- - -// Stacks abrufen -app.get('/api/stacks', async (req, res) => { - console.log("ℹ️ [API] GET /api/stacks: Abruf gestartet"); - try { - const stacksRes = await axiosInstance.get('/api/stacks'); - const filteredStacks = stacksRes.data.filter(stack => stack.EndpointId === ENDPOINT_ID); - - const uniqueStacksMap = {}; - filteredStacks.forEach(stack => { - if (!uniqueStacksMap[stack.Name]) uniqueStacksMap[stack.Name] = stack; - }); - const uniqueStacks = Object.values(uniqueStacksMap); - - const stacksWithStatus = await Promise.all( - uniqueStacks.map(async (stack) => { - try { - const statusRes = await axiosInstance.get( - `/api/stacks/${stack.Id}/images_status?refresh=true` - ); - const statusEmoji = statusRes.data.Status === 'outdated' ? '⚠️' : 'βœ…'; - return { - ...stack, - updateStatus: statusEmoji, - redeploying: redeployingStacks[stack.Id] || false - }; - } catch (err) { - console.error(`❌ [API] Fehler beim Abrufen Status Stack ${stack.Id}:`, err.message); - return { ...stack, updateStatus: '❌', redeploying: redeployingStacks[stack.Id] || false }; - } - }) - ); - - stacksWithStatus.sort((a, b) => a.Name.localeCompare(b.Name)); - console.log(`βœ… [API] GET /api/stacks: Abruf erfolgreich, ${stacksWithStatus.length} Stacks geladen`); - res.json(stacksWithStatus); - } catch (err) { - console.error(`❌ [API] Fehler beim Abrufen der Stacks:`, err.message); - res.status(500).json({ error: err.message }); - } -}); - -// Stack redeployen -app.put('/api/stacks/:id/redeploy', async (req, res) => { - const { id } = req.params; - console.log(`πŸ”„ [API] PUT /api/stacks/${id}/redeploy: Redeploy gestartet`); - - try { - broadcastRedeployStatus(id, true); - - const stackRes = await axiosInstance.get(`/api/stacks/${id}`); - const stack = stackRes.data; - - if (stack.EndpointId !== ENDPOINT_ID) throw new Error(`Stack gehΓΆrt nicht zum Endpoint ${ENDPOINT_ID}`); - - if (stack.Type === 1) { - console.log(`πŸ”„ [Redeploy] Git Stack "${stack.Name}" (${id}) wird redeployed`); - await axiosInstance.put(`/api/stacks/${id}/git/redeploy?endpointId=${stack.EndpointId}`); - } else if (stack.Type === 2) { - console.log(`πŸ”„ [Redeploy] Compose Stack "${stack.Name}" (${id}) wird redeployed`); - const fileRes = await axiosInstance.get(`/api/stacks/${id}/file`); - const stackFileContent = fileRes.data?.StackFileContent; - if (!stackFileContent) throw new Error("Stack file konnte nicht geladen werden"); - - const services = fileRes.data?.Config?.services || {}; - for (const serviceName in services) { - const imageName = services[serviceName].image; - if (!imageName) continue; - try { - console.log(`πŸ–ΌοΈ [Redeploy] Pulling image "${imageName}" fΓΌr Service "${serviceName}"`); - await axiosInstance.post( - `/api/endpoints/${stack.EndpointId}/docker/images/create?fromImage=${encodeURIComponent(imageName)}` - ); - } catch (err) { - console.error(`❌ [Redeploy] Fehler beim Pulling von Image "${imageName}":`, err.message); - } - } - - await axiosInstance.put(`/api/stacks/${id}`, - { StackFileContent: stackFileContent, Prune: false, PullImage: true }, - { params: { endpointId: stack.EndpointId } } - ); - } - - broadcastRedeployStatus(id, false); - console.log(`βœ… [API] PUT /api/stacks/${id}/redeploy: Redeploy erfolgreich abgeschlossen`); - res.json({ success: true, message: 'Stack redeployed' }); - } catch (err) { - broadcastRedeployStatus(id, false); - console.error(`❌ [API] Fehler beim Redeploy von Stack ${id}:`, err.message); - res.status(500).json({ error: err.message }); - } -}); - -// Server starten -server.listen(PORT, '0.0.0.0', () => { - console.log(`πŸš€ [Server] Backend lΓ€uft auf Port ${PORT}`); -}); diff --git a/frontend/src/Stacks.jsx.bak b/frontend/src/Stacks.jsx.bak deleted file mode 100644 index e890c62..0000000 --- a/frontend/src/Stacks.jsx.bak +++ /dev/null @@ -1,112 +0,0 @@ -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(""); - - // Socket.IO initialisieren (Proxy leitet /socket.io an Backend) - useEffect(() => { - const socket = io("/", { transports: ["websocket"] }); - console.log("πŸ”Œ Socket connected"); - - socket.on("redeployStatus", ({ stackId, status }) => { - console.log(`πŸ”„ Stack ${stackId} Redeploy Status: ${status ? "running" : "finished"}`); - setStacks(prevStacks => - prevStacks.map(stack => - stack.Id === stackId - ? { ...stack, redeploying: status, updateStatus: status ? stack.updateStatus : "βœ…" } - : stack - ) - ); - }); - - return () => socket.disconnect(); - }, []); - - // Stacks initial laden - const fetchStacks = async () => { - setLoading(true); - try { - const res = await axios.get("/api/stacks"); - setStacks( - res.data - .sort((a, b) => a.Name.localeCompare(b.Name)) - .map(stack => ({ ...stack, redeploying: stack.redeploying || false })) - ); - } catch (err) { - console.error("❌ Fehler beim Abrufen der Stacks:", err); - setError("Fehler beim Laden der Stacks"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchStacks(); - }, []); - - // Redeploy starten - const handleRedeploy = async (stackId) => { - // Sofort UI auf redeploying setzen - setStacks(prev => - prev.map(stack => stack.Id === stackId ? { ...stack, redeploying: true } : stack) - ); - - try { - await axios.put(`/api/stacks/${stackId}/redeploy`); - // Backend sendet Socket-Event β†’ UI aktualisiert automatisch - } catch (err) { - console.error("❌ Fehler beim Redeploy:", err); - setStacks(prev => - prev.map(stack => stack.Id === stackId ? { ...stack, redeploying: false } : stack) - ); - } - }; - - if (loading) return

Lade Stacks...

; - if (error) return

{error}

; - - return ( -
- {stacks.map(stack => { - const isRedeploying = stack.redeploying; - - return ( -
-
- {/* Status Indicator */} -
-
-

{stack.Name}

-

ID: {stack.Id}

-
-
- - {/* Redeploy Button */} - -
- ); - })} - {stacks.length === 0 &&

Keine Stacks gefunden.

} -
- ); -}