update-ui/main.py

214 lines
6.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from jinja2 import Template
import requests
import json
from dateutil import parser, tz
from pathlib import Path
app = FastAPI()
API_URL = "http://192.168.18.4:8000/results/updatecheck/"
READ_API_KEY = "read_key_1"
REFERENCE_FILE = "reference_versions.json"
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:
return {}
def fetch_latest_per_host():
lines = fetch_raw_lines()
reference = fetch_reference()
latest = {}
for line in lines:
parts = line.strip().split(",", 3)
if len(parts) < 4:
continue
timestamp, hostname, _, packages = parts
if hostname not in latest or timestamp > latest[hostname]["timestamp"]:
latest[hostname] = {
"timestamp": timestamp,
"raw": packages
}
result = {}
for host, entry in latest.items():
try:
dt = parser.isoparse(entry["timestamp"]).astimezone(tz.gettz("Europe/Berlin"))
display_time = dt.strftime("%Y-%m-%d %H:%M:%S")
except:
display_time = entry["timestamp"]
pkgs = {}
for p in entry["raw"].strip(";").split(";"):
if "=" in p:
k, v = p.split("=", 1)
pkgs[k] = {
"current": v,
"expected": reference.get(k)
}
result[host] = {
"timestamp": display_time,
"packages": pkgs
}
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 or parts[1] != hostname:
continue
timestamp, _, _, raw = parts
pkgs = {}
for p in raw.strip(";").split(";"):
if "=" in p:
k, v = p.split("=", 1)
pkgs[k] = v
history.append({"timestamp": timestamp, "packages": pkgs})
history.sort(key=lambda x: x["timestamp"], reverse=True)
return history
@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_view(hostname: str):
return HOST_TEMPLATE.render(hostname=hostname, history=fetch_history_for_host(hostname))
@app.get("/edit", response_class=HTMLResponse)
def edit_view():
try:
data = Path(REFERENCE_FILE).read_text()
except:
data = "{}"
return EDIT_TEMPLATE.render(content=data)
@app.post("/edit", response_class=RedirectResponse)
def edit_post(content: str = Form(...)):
try:
json.loads(content)
Path(REFERENCE_FILE).write_text(content)
except Exception as e:
print(f"Fehler beim Schreiben der Referenzdatei: {e}")
return RedirectResponse(url="/edit", status_code=303)
# ========== Templates ==========
MAIN_TEMPLATE = Template("""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Update Übersicht</title>
<style>
body { font-family: sans-serif; margin: 2rem; }
.ok { background: #d4edda; }
.outdated { background: #f8d7da; }
.missing { background: #fff3cd; }
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td { border: 1px solid #ccc; padding: 4px 10px; }
#search { margin-bottom: 1rem; padding: 4px; width: 250px; }
</style>
<script>
function toggle(id) {
const el = document.getElementById(id);
el.style.display = el.style.display === "none" ? "block" : "none";
}
function filterTable() {
let q = document.getElementById("search").value.toLowerCase();
document.querySelectorAll(".host-block").forEach(div => {
div.style.display = div.innerText.toLowerCase().includes(q) ? "block" : "none";
});
}
</script>
</head>
<body>
<h1>Update Übersicht</h1>
<input id="search" placeholder="🔍 Host oder Paket..." onkeyup="filterTable()">
<a href="/edit">⚙️ Referenz bearbeiten</a>
{% for host, d in data.items() %}
<div class="host-block">
<button onclick="toggle('b-{{ host }}')">{{ host }} (Stand: {{ d.timestamp }})</button>
<a href="/host/{{ host }}">🔍 Verlauf</a>
<div id="b-{{ host }}" style="display:none">
<table>
<tr><th>Paket</th><th>Installiert</th><th>Referenz</th><th>Status</th></tr>
{% for p, info in d.packages.items() %}
<tr class="{% if not info.expected %}missing{% elif info.current != info.expected %}outdated{% else %}ok{% endif %}">
<td>{{ p }}</td>
<td>{{ info.current }}</td>
<td>{{ info.expected or '' }}</td>
<td>
{% if not info.expected %}❓ fehlt
{% elif info.current != info.expected %}❌
{% else %}✅
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endfor %}
</body>
</html>
""")
HOST_TEMPLATE = Template("""
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>{{ hostname }}</title>
<style>
body { font-family: sans-serif; margin: 2rem; }
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td { border: 1px solid #ccc; padding: 4px 10px; }
</style></head>
<body>
<h1>Verlauf: {{ hostname }}</h1>
<a href="/">⬅ Zurück</a>
{% for h in history %}
<h3>{{ h.timestamp }}</h3>
<table>
<tr><th>Paket</th><th>Version</th></tr>
{% for p, v in h.packages.items() %}
<tr><td>{{ p }}</td><td>{{ v }}</td></tr>
{% endfor %}
</table>
{% endfor %}
</body>
</html>
""")
EDIT_TEMPLATE = Template("""
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Referenz bearbeiten</title>
<style>body { font-family: sans-serif; margin: 2rem; }</style>
</head><body>
<h1>Referenzdatei bearbeiten</h1>
<a href="/">⬅ Zurück</a>
<form method="post">
<textarea name="content" style="width:100%;height:400px;">{{ content }}</textarea><br>
<button type="submit">💾 Speichern</button>
</form>
</body>
</html>
""")