From 241a509a15b7ced5a98e3a69d4a2f57bb6cee8ca Mon Sep 17 00:00:00 2001 From: root Date: Mon, 29 Sep 2025 19:04:26 +0000 Subject: [PATCH] Log and Database --- README.md | 2 +- backend/data/.gitignore | 1 + backend/data/stackpulse.db-shm | Bin 0 -> 32768 bytes backend/data/stackpulse.db-wal | 0 backend/db/index.js | 18 +++ backend/db/migrate.js | 19 +++ backend/index.js | 59 +++++++++ backend/package-lock.json | 219 +++++++++++++++++++++++++++++++++ backend/package.json | 18 ++- frontend/package-lock.json | 39 ++++++ frontend/package.json | 1 + frontend/src/App.jsx | 36 +++++- frontend/src/Logs.jsx | 122 ++++++++++++++++++ frontend/src/main.jsx | 5 +- scripts/start-dev.sh | 1 + 15 files changed, 532 insertions(+), 8 deletions(-) create mode 100644 backend/data/.gitignore create mode 100644 backend/data/stackpulse.db-shm create mode 100644 backend/data/stackpulse.db-wal create mode 100644 backend/db/index.js create mode 100644 backend/db/migrate.js create mode 100644 frontend/src/Logs.jsx diff --git a/README.md b/README.md index 6d71400..32339aa 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Ziel: - [ ] Anbindung einer SQLite-Datenbank - [ ] Logging der Redeploy-Aktionen in SQLite speichern - [ ] API-Endpunkte für Log-Abfragen - +2 ### Frontend - [ ] Anzeige der Logs (inkl. Statusfarben) - [ ] UI-Komponenten für Log-Details diff --git a/backend/data/.gitignore b/backend/data/.gitignore new file mode 100644 index 0000000..3aef848 --- /dev/null +++ b/backend/data/.gitignore @@ -0,0 +1 @@ +stackpulse.db diff --git a/backend/data/stackpulse.db-shm b/backend/data/stackpulse.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3 { } }); +// Redeploy-Logs abrufen +app.get('/api/logs', (req, res) => { + const { stackId, status, from, to } = req.query; + const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500); + const offset = Math.max(parseInt(req.query.offset, 10) || 0, 0); + + const filters = []; + const params = { limit, offset }; + + if (stackId) { + filters.push('stack_id = @stackId'); + params.stackId = stackId; + } + + if (status) { + filters.push('status = @status'); + params.status = status; + } + + if (from) { + filters.push('timestamp >= @from'); + params.from = from; + } + + if (to) { + filters.push('timestamp <= @to'); + params.to = to; + } + + const whereClause = filters.length ? `WHERE ${filters.join(' AND ')}` : ''; + const query = ` + SELECT + id, + timestamp, + stack_id AS stackId, + stack_name AS stackName, + status, + message, + endpoint + FROM redeploy_logs + ${whereClause} + ORDER BY datetime(timestamp) DESC + LIMIT @limit OFFSET @offset + `; + + try { + const stmt = db.prepare(query); + const logs = stmt.all(params); + res.json(logs); + } catch (err) { + console.error('❌ Fehler beim Abrufen der Redeploy-Logs:', err.message); + if (err.message.includes('no such table')) { + return res.status(500).json({ error: 'redeploy_logs table nicht gefunden. Bitte Migration ausführen.' }); + } + res.status(500).json({ error: 'Fehler beim Abrufen der Redeploy-Logs' }); + } +}); + // Einzel-Redeploy app.put('/api/stacks/:id/redeploy', async (req, res) => { const { id } = req.params; diff --git a/backend/package-lock.json b/backend/package-lock.json index 6008df4..8d0c8a9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "axios": "^1.7.0", + "better-sqlite3": "^9.6.0", "dockerode": "^4.0.7", "dotenv": "^16.0.0", "express": "^4.18.0", @@ -230,6 +231,24 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/better-sqlite3": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", + "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -441,6 +460,28 @@ "ms": "2.0.0" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -466,6 +507,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", + "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "engines": { + "node": ">=8" + } + }, "node_modules/docker-modem": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", @@ -686,6 +735,14 @@ "node": ">= 0.6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -731,6 +788,11 @@ "url": "https://opencollective.com/express" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -854,6 +916,11 @@ "node": ">= 0.4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -951,6 +1018,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1039,6 +1111,25 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -1055,6 +1146,11 @@ "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", "optional": true }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1063,6 +1159,17 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.77.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.77.0.tgz", + "integrity": "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1114,6 +1221,31 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/protobufjs": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", @@ -1199,6 +1331,20 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -1244,6 +1390,17 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1367,6 +1524,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/socket.io": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", @@ -1530,6 +1730,14 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -1564,6 +1772,17 @@ "node": ">=0.6" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", diff --git a/backend/package.json b/backend/package.json index 959770e..290f8ff 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1 +1,17 @@ -{"name":"backend","version":"1.0.0","type":"module","scripts":{"start":"node index.js"},"dependencies":{"axios":"^1.7.0","dockerode":"^4.0.7","dotenv":"^16.0.0","express":"^4.18.0","socket.io":"^4.8.1"}} \ No newline at end of file +{ + "name": "backend", + "version": "1.0.0", + "type": "module", + "scripts": { + "start": "node index.js", + "migrate": "node db/migrate.js" + }, + "dependencies": { + "axios": "^1.7.0", + "better-sqlite3": "^9.6.0", + "dockerode": "^4.0.7", + "dotenv": "^16.0.0", + "express": "^4.18.0", + "socket.io": "^4.8.1" + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c8f96c0..3555d9e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.7.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.26.2", "socket.io-client": "^4.8.1" }, "devDependencies": { @@ -758,6 +759,14 @@ "node": ">=14" } }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -2188,6 +2197,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 75934ec..ece7f7b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "axios": "^1.7.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.26.2", "socket.io-client": "^4.8.1" }, "devDependencies": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3dd713a..c6db119 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,15 +1,41 @@ import React from "react"; +import { NavLink, Route, Routes } from "react-router-dom"; import Stacks from "./Stacks.jsx"; +import Logs from "./Logs.jsx"; + +const navLinkBase = + "px-4 py-2 rounded-md font-medium transition-colors duration-150"; + +const getNavClass = ({ isActive }) => + `${navLinkBase} ${isActive ? "bg-purple-600 text-white" : "text-gray-300 hover:bg-gray-700"}`; export default function App() { return (
-
-

StackPulse

-

Verwalte deine Docker Stacks

+
+
+
+
+

StackPulse

+

Verwalte deine Docker Stacks

+
+ +
+
-
- +
+ + } /> + } /> + } /> +
); diff --git a/frontend/src/Logs.jsx b/frontend/src/Logs.jsx new file mode 100644 index 0000000..b1d2df8 --- /dev/null +++ b/frontend/src/Logs.jsx @@ -0,0 +1,122 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +const STATUS_COLORS = { + success: "text-green-400", + warning: "text-yellow-400", + error: "text-red-400" +}; + +const formatTimestamp = (value) => { + if (!value) return "-"; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return value; + return date.toLocaleString("de-DE", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit" + }); +}; + +export default function Logs() { + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + + const fetchLogs = async () => { + setLoading(true); + setError(""); + try { + const response = await axios.get("/api/logs", { + params: { limit: 200 } + }); + setLogs(response.data); + } catch (err) { + console.error("❌ Fehler beim Laden der Logs:", err); + setError("Fehler beim Laden der Redeploy-Logs"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchLogs(); + }, []); + + if (loading) { + return

Lade Logs...

; + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + return ( +
+
+

Redeploy-Logs

+ +
+ +
+ + + + + + + + + + + + {logs.length === 0 && ( + + + + )} + {logs.map((log) => { + const statusClass = STATUS_COLORS[log.status] || "text-blue-300"; + return ( + + + + + + + + ); + })} + +
ZeitpunktStackStatusNachrichtEndpoint
+ Keine Logs vorhanden. +
+ {formatTimestamp(log.timestamp)} + +
+ {log.stackName || "Unbekannt"} + ID: {log.stackId} +
+
+ {log.status} + + {log.message || "-"} + + {log.endpoint ?? "-"} +
+
+
+ ); +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index b194025..032e87b 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,10 +1,13 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; import App from "./App.jsx"; import './index.css'; // Tailwind CSS ReactDOM.createRoot(document.getElementById("root")).render( - + + + ); diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh index 8caf75d..85a6b7d 100755 --- a/scripts/start-dev.sh +++ b/scripts/start-dev.sh @@ -6,6 +6,7 @@ echo "🚀 Starte StackPulse Dev-Umgebung..." # --- Backend --- cd backend npm install +npm run migrate npm start & BACK_PID=$! cd ..