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..89d93ec --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,4 @@ +PORTAINER_URL=your_portainer_url_here +PORTAINER_API_KEY=your_api_key_here +PORTAINER_ENDPOINT_ID=Enpoint_ID_from_Portainer + 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}`); -});