From 029caa8565752e25044aef31983a444df0feac81 Mon Sep 17 00:00:00 2001 From: chickenandrice02 Date: Wed, 25 Jun 2025 14:06:53 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20commit=20=E2=80=93=20Update=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++ Dockerfile | 10 +++ main.py | 194 ++++++++++++++++++++++++++++++++++++++++ reference_versions.json | 4 + run_container.sh | 22 +++++ 5 files changed, 235 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 main.py create mode 100644 reference_versions.json create mode 100755 run_container.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdc38f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +*.log +input/ +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..52ff427 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +WORKDIR /app + +RUN pip install fastapi jinja2 uvicorn requests python-dateutil + +COPY main.py . +COPY reference_versions.json ./reference_versions.json + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/main.py b/main.py new file mode 100644 index 0000000..7c0f32d --- /dev/null +++ b/main.py @@ -0,0 +1,194 @@ +from fastapi import FastAPI +from fastapi.responses import HTMLResponse +from jinja2 import Template +import requests +import json +from dateutil import parser, tz + +app = FastAPI() + +API_URL = "http://192.168.18.4:8000/results/updatecheck/" +READ_API_KEY = "read_key_1" +REFERENCE_FILE = "reference_versions.json" + +# ================= Templates ================== + +MAIN_TEMPLATE = Template(""" + + + + + UpdateLog Übersicht + + + + +

UpdateLog Übersicht

+ {% for server, content in data.items() %} +
+ + 🔍 Verlauf anzeigen + +
+ {% endfor %} + + +""") + +HISTORY_TEMPLATE = Template(""" + + + + + Verlauf: {{ hostname }} + + + +

Update-Verlauf für {{ hostname }}

+ ⬅ Zurück zur Übersicht + {% for entry in history %} +

{{ entry.timestamp }}

+ + + {% for pkg, ver in entry.packages.items() %} + + + + + {% endfor %} +
PaketInstalliert
{{ pkg }}{{ ver }}
+ {% endfor %} + + +""") + +# ================= Routes ================== + +@app.get("/", response_class=HTMLResponse) +def index(): + data = fetch_latest_per_host() + return MAIN_TEMPLATE.render(data=data) + +@app.get("/host/{hostname}", response_class=HTMLResponse) +def host_history(hostname: str): + history = fetch_history_for_host(hostname) + return HISTORY_TEMPLATE.render(hostname=hostname, history=history) + +# ================= Datenfunktionen ================== + +def fetch_raw_lines(): + try: + response = requests.get(API_URL, headers={"Authorization": f"Bearer {READ_API_KEY}"}, timeout=5) + response.raise_for_status() + return response.json().get("lines", []) + except Exception as e: + print(f"❌ Fehler beim Abrufen der Logs: {e}") + return [] + +def fetch_reference(): + try: + with open(REFERENCE_FILE) as f: + return json.load(f) + except Exception as e: + print(f"❌ Fehler beim Laden der Referenzversionen: {e}") + return {} + +def fetch_latest_per_host(): + lines = fetch_raw_lines() + reference = fetch_reference() + latest_by_host = {} + + for line in lines: + parts = line.strip().split(",", 3) + if len(parts) < 4: + continue + timestamp, hostname, _, packages = parts + if hostname not in latest_by_host or timestamp > latest_by_host[hostname]["timestamp"]: + latest_by_host[hostname] = { + "timestamp": timestamp, + "raw": packages + } + + result = {} + for hostname, entry in latest_by_host.items(): + # Zeitstempel umrechnen + try: + dt_utc = parser.isoparse(entry["timestamp"]) + dt_local = dt_utc.astimezone(tz.gettz("Europe/Berlin")) + display_time = dt_local.strftime("%Y-%m-%d %H:%M:%S") + except Exception: + display_time = entry["timestamp"] + + package_str = entry["raw"] + result[hostname] = { + "timestamp": display_time, + "packages": {} + } + for item in package_str.strip(";").split(";"): + if "=" not in item: + continue + pkg, version = item.split("=", 1) + expected = reference.get(pkg) + if expected: + result[hostname]["packages"][pkg] = { + "current": version, + "expected": expected + } + + return result + +def fetch_history_for_host(hostname): + lines = fetch_raw_lines() + history = [] + for line in lines: + parts = line.strip().split(",", 3) + if len(parts) < 4: + continue + timestamp, host, _, packages = parts + if host != hostname: + continue + parsed = {} + for item in packages.strip(";").split(";"): + if "=" not in item: + continue + pkg, version = item.split("=", 1) + parsed[pkg] = version + history.append({ + "timestamp": timestamp, + "packages": parsed + }) + history.sort(key=lambda x: x["timestamp"], reverse=True) + return history diff --git a/reference_versions.json b/reference_versions.json new file mode 100644 index 0000000..987f8ef --- /dev/null +++ b/reference_versions.json @@ -0,0 +1,4 @@ +{ + "python3": "3.12.3-0ubuntu2", + "openssh-server": "1:9.6p1-3ubuntu13.11" +} diff --git a/run_container.sh b/run_container.sh new file mode 100755 index 0000000..ca61608 --- /dev/null +++ b/run_container.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +CONTAINER_NAME="update-ui" +IMAGE_NAME="update-ui:latest" +PORT="8080" + +docker stop $CONTAINER_NAME 2>/dev/null || true +docker rm $CONTAINER_NAME 2>/dev/null || true + +docker build -t $IMAGE_NAME . + +docker run -d \ + --name $CONTAINER_NAME \ + -p $PORT:8080 \ + --read-only \ + --tmpfs /tmp \ + --cap-drop ALL \ + --security-opt no-new-privileges \ + $IMAGE_NAME + +echo "✅ Update-UI läuft unter http://localhost:$PORT"