diff --git a/app/checkRunner.js b/app/checkRunner.js index 4ee95a1..d4d5c90 100644 --- a/app/checkRunner.js +++ b/app/checkRunner.js @@ -27,10 +27,20 @@ async function runCheck(check) { } if (check.type === "script") { + + if (!check.script_name) { + return { + status: "FAIL", + message: "No script_name defined", + duration: Date.now() - start + }; + } + return new Promise((resolve) => { const scriptPath = path.join(CHECKS_DIR, check.script_name); + const child = spawn("sh", [scriptPath], { - timeout: check.timeout_seconds * 1000 + timeout: (check.timeout_seconds || 10) * 1000 }); child.on("close", (code) => { diff --git a/app/checks/check_http_schooltech.sh b/app/checks/check_http_schooltech.sh new file mode 100755 index 0000000..c5c8fae --- /dev/null +++ b/app/checks/check_http_schooltech.sh @@ -0,0 +1,3 @@ +#!/bin/sh +wget -q --spider https://server.schooltech.de/ || exit 1 +exit $? \ No newline at end of file diff --git a/app/server.js b/app/server.js index 243573d..2113e31 100644 --- a/app/server.js +++ b/app/server.js @@ -2,6 +2,8 @@ const express = require("express"); const pool = require("./db"); const scheduleChecks = require("./scheduler"); +const runCheck = require("./checkRunner"); + const app = express(); app.set("view engine", "ejs"); app.use(express.static("public")); @@ -11,6 +13,32 @@ app.get("/", async (req, res) => { res.render("index", { checks: rows }); }); +app.post("/run/:id", async (req, res) => { + const checkId = req.params.id; + + const { rows } = await pool.query("SELECT * FROM checks WHERE id = $1", [checkId]); + if (rows.length === 0) { + return res.status(404).send("Check not found"); + } + + const check = rows[0]; + const result = await runCheck(check); + + await pool.query( + `INSERT INTO results (check_id, status, message, duration_ms) + VALUES ($1,$2,$3,$4)`, + [check.id, result.status, result.message, result.duration] + ); + + await pool.query( + `UPDATE checks SET last_run = NOW(), last_status = $1 WHERE id = $2`, + [result.status, check.id] + ); + + res.redirect("/"); +}); + + app.listen(3000, async () => { console.log("Server läuft auf Port 3000"); await scheduleChecks(); diff --git a/app/views/index.ejs b/app/views/index.ejs index 28ad693..95bda8d 100644 --- a/app/views/index.ejs +++ b/app/views/index.ejs @@ -12,16 +12,23 @@ Name Status Last Run +Action <% checks.forEach(c => { %> -<%= c.id %> + + <%= c.display_id || c.id %> <%= c.name %> <%= c.last_status || 'N/A' %> <%= c.last_run || 'N/A' %> + +
+ +
+ <% }) %> diff --git a/db/init.sql b/db/init.sql index 75ba892..36ad80a 100644 --- a/db/init.sql +++ b/db/init.sql @@ -26,3 +26,26 @@ VALUES ('Google HTTP', 'script', 'Prüft google.com', NULL), ('Postgres Container Running', 'docker_container', 'Ist Postgres Container aktiv?', 'info-postgres'), ('Default Network contains Postgres', 'docker_network', 'Ist Postgres im default Netzwerk?', 'bridge'); + +UPDATE checks +SET script_name = 'check_http_google.sh' +WHERE name = 'Google HTTP'; + +UPDATE checks +SET target = 'appServer_InfoDB' +WHERE name = 'Postgres Container Running'; + +UPDATE checks +SET target = 'appserver_default,appServer_InfoDB' +WHERE name = 'Default Network contains Postgres'; + +ALTER TABLE checks +ADD COLUMN display_id TEXT; + +UPDATE checks +SET display_id = id +WHERE name = 'Google HTTP'; + +INSERT INTO checks (name, type, description, target, script_name, display_id) +VALUES +('schooltech.ch erreichbar', 'script', 'Prüft schooltech.ch', NULL, 'check_http_schooltech.sh', '1.1') \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 64b2acb..7173f86 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,9 +11,9 @@ services: - ./checks:/app/checks - /var/run/docker.sock:/var/run/docker.sock ports: - - "3000:3000" + - "3030:3000" depends_on: - - db + - appServerInfoDB environment: DB_HOST: db DB_USER: postgres @@ -22,7 +22,7 @@ services: DB_PORT: 5432 restart: unless-stopped - db: + appServerInfoDB: image: postgres:16-alpine container_name: info-postgres restart: unless-stopped @@ -35,3 +35,19 @@ services: - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "5432:5432" + + appServerInfoPgadmin: + image: dpage/pgadmin4:latest + container_name: appServer_InfoPgadmin + environment: + PGADMIN_DEFAULT_EMAIL: admin@local.dev + PGADMIN_DEFAULT_PASSWORD: admin + PGADMIN_CONFIG_SERVER_MODE: 'False' + ports: + - "5050:80" + depends_on: + - appServerInfoDB + volumes: + - /home/chk/Documents/appServerInfo/pgadmin/data:/var/lib/pgadmin + - /home/chk/Documents/appServerInfo/pgadmin/servers.json:/pgadmin4/servers.json + restart: unless-stopped diff --git a/pgadmin/servers.json b/pgadmin/servers.json new file mode 100755 index 0000000..32ec183 --- /dev/null +++ b/pgadmin/servers.json @@ -0,0 +1,13 @@ +{ + "Servers": { + "1": { + "Name": "appServerInfoDB", + "Group": "Servers", + "Host": "appServer_InfoDB", + "Port": 5432, + "MaintenanceDB": "infodb", + "Username": "postgres", + "SSLMode": "prefer" + } + } +} \ No newline at end of file diff --git a/question_GPT.txt b/question_GPT.txt new file mode 100755 index 0000000..cf83f0d --- /dev/null +++ b/question_GPT.txt @@ -0,0 +1,343 @@ +Eine WebPage die den Status vom Netzwerk und Docker-Containern angeben soll. + +== +NodeJS: Server.js +========================== + +const express = require("express"); +const pool = require("./db"); +const scheduleChecks = require("./scheduler"); + +const runCheck = require("./checkRunner"); + +const app = express(); +app.set("view engine", "ejs"); +app.use(express.static("public")); + +app.get("/", async (req, res) => { + const { rows } = await pool.query("SELECT * FROM checks ORDER BY id"); + res.render("index", { checks: rows }); +}); + +app.post("/run/:id", async (req, res) => { + const checkId = req.params.id; + + const { rows } = await pool.query("SELECT * FROM checks WHERE id = $1", [checkId]); + if (rows.length === 0) { + return res.status(404).send("Check not found"); + } + + const check = rows[0]; + const result = await runCheck(check); + + await pool.query( + `INSERT INTO results (check_id, status, message, duration_ms) + VALUES ($1,$2,$3,$4)`, + [check.id, result.status, result.message, result.duration] + ); + + await pool.query( + `UPDATE checks SET last_run = NOW(), last_status = $1 WHERE id = $2`, + [result.status, check.id] + ); + + res.redirect("/"); +}); + + +app.listen(3000, async () => { + console.log("Server läuft auf Port 3000"); + await scheduleChecks(); +}); + + +== +NodeJS: scheduler.js +============================================= +const cron = require("node-cron"); +const pool = require("./db"); +const runCheck = require("./checkRunner"); + +async function scheduleChecks() { + const { rows } = await pool.query("SELECT * FROM checks WHERE active = true"); + + rows.forEach(check => { + cron.schedule(`*/${check.schedule_seconds} * * * * *`, async () => { + const result = await runCheck(check); + + await pool.query( + `INSERT INTO results (check_id, status, message, duration_ms) + VALUES ($1,$2,$3,$4)`, + [check.id, result.status, result.message, result.duration] + ); + + await pool.query( + `UPDATE checks SET last_run = NOW(), last_status = $1 WHERE id = $2`, + [result.status, check.id] + ); + }); + }); +} + +module.exports = scheduleChecks; + + +== +nodeJS: db.js +==================================================== +const { Pool } = require("pg"); + +module.exports = new Pool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + port: process.env.DB_PORT +}); + + +== +nodeJS: dockerApi.js +=============================================================== +const http = require("http"); + +function dockerRequest(path) { + return new Promise((resolve, reject) => { + const options = { + socketPath: "/var/run/docker.sock", + path, + method: "GET" + }; + + const req = http.request(options, (res) => { + let data = ""; + res.on("data", chunk => data += chunk); + res.on("end", () => { + try { + resolve(JSON.parse(data)); + } catch { + resolve(data); + } + }); + }); + + req.on("error", reject); + req.end(); + }); +} + +async function isContainerRunning(name) { + try { + const data = await dockerRequest(`/containers/${name}/json`); + return data.State?.Running === true; + } catch { + return false; + } +} + +async function networkContainsContainer(networkName, containerName) { + try { + const data = await dockerRequest(`/networks/${networkName}`); + if (!data.Containers) return false; + return Object.values(data.Containers) + .some(c => c.Name === containerName); + } catch { + return false; + } +} + +module.exports = { isContainerRunning, networkContainsContainer }; + + +== +NodeJS: checkRunner +==================================================== + +const { spawn } = require("child_process"); +const path = require("path"); +const dockerApi = require("./dockerApi"); + +const CHECKS_DIR = path.join(__dirname, "checks"); + +async function runCheck(check) { + const start = Date.now(); + + if (check.type === "docker_container") { + const result = await dockerApi.isContainerRunning(check.target); + return { + status: result ? "OK" : "FAIL", + message: `Container ${check.target} running: ${result}`, + duration: Date.now() - start + }; + } + + if (check.type === "docker_network") { + const [network, container] = check.target.split(","); + const result = await dockerApi.networkContainsContainer(network, container); + return { + status: result ? "OK" : "FAIL", + message: `Container ${container} in network ${network}: ${result}`, + duration: Date.now() - start + }; + } + + if (check.type === "script") { + + if (!check.script_name) { + return { + status: "FAIL", + message: "No script_name defined", + duration: Date.now() - start + }; + } + + return new Promise((resolve) => { + const scriptPath = path.join(CHECKS_DIR, check.script_name); + + const child = spawn("sh", [scriptPath], { + timeout: (check.timeout_seconds || 10) * 1000 + }); + + child.on("close", (code) => { + resolve({ + status: code === 0 ? "OK" : "FAIL", + message: `Exit code: ${code}`, + duration: Date.now() - start + }); + }); + }); + } +} + +module.exports = runCheck; + + +== +index.ejs +============================ + + + + + Docker Info Dashboard + + + +

System Status

+ + + + + + + + + +<% checks.forEach(c => { %> + + + + + + + +<% }) %> + +
IDNameStatusLast RunAction
<%= c.id %><%= c.name %> +<%= c.last_status || 'N/A' %> +<%= c.last_run || 'N/A' %> +
+ +
+
+ + + +== +checks/check_http_google.sh +============================= +#!/bin/sh +wget -q --spider https://google.com +exit $? + + +== +DB +=================================== +CREATE TABLE checks ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + type TEXT NOT NULL, -- script | docker_container | docker_network + description TEXT, + target TEXT, + script_name TEXT, + schedule_seconds INTEGER DEFAULT 60, + timeout_seconds INTEGER DEFAULT 20, + active BOOLEAN DEFAULT TRUE, + last_run TIMESTAMP, + last_status TEXT +); + +CREATE TABLE results ( + id SERIAL PRIMARY KEY, + check_id INTEGER REFERENCES checks(id) ON DELETE CASCADE, + run_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status TEXT, + message TEXT, + duration_ms INTEGER +); + +INSERT INTO checks (name, type, description, target) +VALUES +('Google HTTP', 'script', 'Prüft google.com', NULL), +('Postgres Container Running', 'docker_container', 'Ist Postgres Container aktiv?', 'info-postgres'), +('Default Network contains Postgres', 'docker_network', 'Ist Postgres im default Netzwerk?', 'bridge'); + + +== +Docker-compose.yaml +========================================= + + +services: + app: + image: node:20-alpine + container_name: info-node-app + working_dir: /app + command: sh -c "npm install && node server.js" + volumes: + - ./app:/app + - ./checks:/app/checks + - /var/run/docker.sock:/var/run/docker.sock + ports: + - "3000:3000" + depends_on: + - db + environment: + DB_HOST: db + DB_USER: postgres + DB_PASSWORD: postgres + DB_NAME: infodb + DB_PORT: 5432 + restart: unless-stopped + + db: + image: postgres:16-alpine + container_name: info-postgres + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: infodb + volumes: + - ./pgdata:/var/lib/postgresql/data + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql + ports: + - "5432:5432" + + +==================================== +==================================== +==================================== + +