feat(ops): værtens apt-sikkerhedsopdateringer på driftssiden
Proxmox VE får 'nyeste' fra værtens apt-kandidat, ny sektion med sikkerhedspakker/genstartsbehov, ntfy ved nye sikkerhedsopdateringer. _norm stripper release-præfiks (qBittorrent). Claude-Session: https://claude.ai/code/session_01AUj1LSMmrowcomkn1ciAgj
This commit is contained in:
@@ -245,7 +245,8 @@ async def ops_content(request: Request):
|
||||
version, rp, vers = await asyncio.gather(
|
||||
svc.assets_version(), svc.rp_status(), versions.all_versions())
|
||||
return templates.TemplateResponse(request, "admin_ops_body.html", {
|
||||
"request": request, "version": version, "rp": rp, "versions": vers})
|
||||
"request": request, "version": version, "rp": rp, "versions": vers,
|
||||
"host": versions.host_updates()})
|
||||
|
||||
|
||||
@app.get("/admin/stats", response_class=HTMLResponse)
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
var fstate = { q:'', mode:'all', level:'all' };
|
||||
// Filter ligger i shell (persistent) og genanvendes efter hver poll → fokus/værdi bevares.
|
||||
function applyFilter(){
|
||||
var rows = box.querySelectorAll('.vtable tbody tr'), shown = 0;
|
||||
var rows = box.querySelectorAll('.vtable tbody tr[data-name]'), shown = 0;
|
||||
rows.forEach(function(tr){
|
||||
var name = tr.getAttribute('data-name') || '',
|
||||
st = tr.getAttribute('data-status'), lvl = tr.getAttribute('data-level'),
|
||||
|
||||
@@ -41,6 +41,27 @@
|
||||
</table>
|
||||
<p id="opsnomatch" class="muted" style="display:none;margin:.4rem 0">Ingen tjenester matcher filteret.</p>
|
||||
|
||||
{% if host %}
|
||||
<h2 style="display:flex;align-items:baseline;gap:.8rem;flex-wrap:wrap">Proxmox-vært (apt)
|
||||
{% if host.security %}<span class="vbadge eol">🔒 {{ host.security }} sikkerhedsopdatering{{ 'er' if host.security != 1 }}</span>
|
||||
{% else %}<span class="vbadge ok">ingen sikkerhedsopdateringer</span>{% endif %}
|
||||
{% if host.upgradable %}<span class="vbadge update">{{ host.upgradable }} pakker i alt</span>{% endif %}
|
||||
{% if host.reboot_required %}<span class="vbadge soon" title="kører {{ host.kernel_running }}">genstart krævet{% if host.new_kernel %} (ny kerne){% endif %}</span>{% endif %}
|
||||
</h2>
|
||||
<p class="muted" style="margin:.2rem 0 .6rem">Kerne {{ host.kernel_running }}{% if host.new_kernel %} → {{ host.new_kernel }}{% endif %}
|
||||
· apt-lister opdateret {{ (host.apt_checked or '?')|replace('T',' ') }}
|
||||
· opdatér med <code>apt update && apt dist-upgrade</code> på værten</p>
|
||||
{% if host.security_pkgs %}
|
||||
<details><summary class="muted" style="cursor:pointer">Vis sikkerhedspakker ({{ host.security }})</summary>
|
||||
<table class="vtable" style="margin-top:.5rem">
|
||||
<thead><tr><th>Pakke</th><th>Installeret</th><th>Ny</th></tr></thead>
|
||||
<tbody>{% for p in host.security_pkgs %}
|
||||
<tr><td data-label="Pakke"><strong>{{ p.name }}</strong></td><td class="font-mono" data-label="Installeret">{{ p['from'] or '—' }}</td><td class="font-mono muted" data-label="Ny">{{ p.to }}</td></tr>
|
||||
{% endfor %}</tbody>
|
||||
</table></details>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<h2>Static-assets</h2>
|
||||
<div class="stat" style="max-width:18rem">
|
||||
<div class="n">{{ version if version else '—' }}</div>
|
||||
|
||||
+13
-3
@@ -74,8 +74,8 @@ SERVICES = [
|
||||
def _norm(v):
|
||||
if not v:
|
||||
return ""
|
||||
# behold dato-versioner (fx NPMplus 2026-04-21-r2) — strip kun v-præfiks + build-metadata
|
||||
return re.sub(r"^[vV]", "", str(v).strip()).split("+")[0].strip().lower()
|
||||
# behold dato-versioner (fx NPMplus 2026-04-21-r2) — strip kun v-/release-præfiks + build-metadata
|
||||
return re.sub(r"^(release-)?[vV]?", "", str(v).strip()).split("+")[0].strip().lower()
|
||||
|
||||
|
||||
async def _latest(src, client, store):
|
||||
@@ -229,7 +229,8 @@ async def _one(name, kind, repo, client, local, store):
|
||||
cur, lat_builtin = await _current(kind, client)
|
||||
if not cur:
|
||||
cur = local.get(name) # vært-indsamlet snapshot for LXC-tjenester
|
||||
latest = lat_builtin or await _latest(repo, client, store)
|
||||
# _latest i snapshot'et = nyeste fra værten selv (fx apt-kandidat for pve-manager)
|
||||
latest = lat_builtin or await _latest(repo, client, store) or local.get("_latest", {}).get(name)
|
||||
eol = await _eol(name, cur, client)
|
||||
level = None
|
||||
if cur and latest:
|
||||
@@ -263,6 +264,10 @@ async def _maybe_alert(res, client):
|
||||
if e and e.get("state") in ("eol", "soon"):
|
||||
sig = f"eol:{v['name']}:{e.get('date')}"
|
||||
cur.add(sig); detail[sig] = f"⏳ {v['name']} EOL {e.get('date')}"
|
||||
h = host_updates()
|
||||
if h and h.get("security"):
|
||||
sig = "sec:host:" + ",".join(sorted(f"{p['name']}={p['to']}" for p in h["security_pkgs"]))
|
||||
cur.add(sig); detail[sig] = f"🔒 Proxmox-vært: {h['security']} sikkerhedsopdateringer"
|
||||
try:
|
||||
stored = set(json.loads(ALERT_STATE.read_text("utf-8")))
|
||||
except Exception:
|
||||
@@ -285,6 +290,11 @@ async def _maybe_alert(res, client):
|
||||
ALERT_STATE.write_text(json.dumps(sorted(cur)), "utf-8")
|
||||
|
||||
|
||||
def host_updates():
|
||||
"""Værtens apt-status (indsamlet af portal-versions.py) eller None."""
|
||||
return _load_local().get("_host")
|
||||
|
||||
|
||||
async def all_versions():
|
||||
hit = cache.get("ver:all")
|
||||
if hit is not None:
|
||||
|
||||
Reference in New Issue
Block a user