package gui const indexHTML = ` LazyUpdateManager

LazyUpdateManager

Pruefe Updates...

Aktive Updates 0
Ausgeblendet 0
Freier Speicher -
Kernel -

Updates

Noch nicht geprueft
Keine passenden Updates gefunden.
` const appCSS = ` :root { color-scheme: dark; --bg: #111317; --panel: #191d23; --panel-soft: #222832; --text: #f4f7fb; --muted: #9ba7b6; --line: #303844; --accent: #44d19d; --accent-strong: #2eb984; --warn: #f4c95d; --danger: #ff6f61; } * { box-sizing: border-box; } body { margin: 0; min-width: 320px; min-height: 100vh; color: var(--text); background: var(--bg); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } button, input, select { font: inherit; } .shell { width: min(1260px, calc(100vw - 32px)); margin: 0 auto; padding: 24px 0; } .topbar { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 16px; } h1, h2, p, dl, dd { margin: 0; } h1 { font-size: 28px; font-weight: 740; } h2 { font-size: 15px; font-weight: 700; } #summary, #lastCheck, #ignoredHint, #saveState { color: var(--muted); } #summary { margin-top: 5px; } .actions, .tools { display: flex; gap: 10px; } button { border: 1px solid var(--line); border-radius: 8px; min-height: 40px; padding: 0 14px; color: var(--text); background: var(--panel-soft); cursor: pointer; } button:hover { border-color: var(--accent); } button:disabled { cursor: progress; opacity: 0.62; } #installBtn, form button { border-color: transparent; color: #06130e; background: var(--accent); font-weight: 760; } #installBtn:hover, form button:hover { background: var(--accent-strong); } .stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; } .stat, .panel { border: 1px solid var(--line); border-radius: 8px; background: var(--panel); } .stat { display: grid; gap: 6px; min-height: 82px; padding: 14px; } .stat span, dt, small { color: var(--muted); } .stat strong { font-size: 24px; } .content { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 16px; align-items: start; } .main-column, .side-column { display: grid; gap: 16px; } .panel-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; min-height: 58px; padding: 0 16px; border-bottom: 1px solid var(--line); } .toolbar { align-items: center; } input, select { min-height: 40px; border: 1px solid var(--line); border-radius: 8px; padding: 0 10px; color: var(--text); background: var(--panel-soft); } #searchInput { width: min(32vw, 320px); } .warnings { margin: 14px 16px 0; padding: 12px; border: 1px solid rgba(244, 201, 93, 0.45); border-radius: 8px; color: var(--warn); background: rgba(244, 201, 93, 0.08); white-space: pre-wrap; } .empty { padding: 44px 16px; color: var(--muted); text-align: center; } table { width: 100%; border-collapse: collapse; } th, td { padding: 12px 16px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: middle; } th { color: var(--muted); font-size: 12px; text-transform: uppercase; } td { overflow-wrap: anywhere; } td:first-child { width: 92px; color: var(--accent); font-weight: 720; } td:last-child { width: 132px; text-align: right; } tr:last-child td { border-bottom: 0; } .ghost { min-height: 34px; padding: 0 10px; color: var(--muted); background: transparent; } .danger { color: var(--danger); } .system-list { display: grid; gap: 14px; padding: 16px; } .system-list div { display: flex; justify-content: space-between; gap: 16px; } .system-list dd { text-align: right; } #lockState { color: var(--accent); } #lockState.locked { color: var(--danger); } form { display: grid; gap: 16px; padding: 16px; } label { display: grid; gap: 8px; color: var(--muted); } .toggle { grid-template-columns: 18px 1fr; align-items: center; color: var(--text); } input[type="checkbox"] { width: 18px; min-height: 18px; accent-color: var(--accent); } @media (max-width: 920px) { .shell { width: min(100vw - 20px, 1260px); padding: 16px 0; } .topbar, .content, .stats { grid-template-columns: 1fr; display: grid; } .actions, .tools { display: grid; grid-template-columns: 1fr 1fr; } #searchInput { width: 100%; } th:nth-child(3), td:nth-child(3) { display: none; } } ` const appJS = ` const summary = document.querySelector("#summary"); const activeCount = document.querySelector("#activeCount"); const ignoredCount = document.querySelector("#ignoredCount"); const diskFree = document.querySelector("#diskFree"); const kernel = document.querySelector("#kernel"); const refreshBtn = document.querySelector("#refreshBtn"); const installBtn = document.querySelector("#installBtn"); const updatesTable = document.querySelector("#updatesTable"); const updatesBody = document.querySelector("#updatesBody"); const ignoredPanel = document.querySelector("#ignoredPanel"); const ignoredBody = document.querySelector("#ignoredBody"); const ignoredHint = document.querySelector("#ignoredHint"); const emptyState = document.querySelector("#emptyState"); const warnings = document.querySelector("#warnings"); const searchInput = document.querySelector("#searchInput"); const sourceFilter = document.querySelector("#sourceFilter"); const lastCheck = document.querySelector("#lastCheck"); const lastSuccess = document.querySelector("#lastSuccess"); const aurHelper = document.querySelector("#aurHelper"); const terminalStatus = document.querySelector("#terminalStatus"); const lockState = document.querySelector("#lockState"); const form = document.querySelector("#settingsForm"); const checkAur = document.querySelector("#checkAur"); const showIgnored = document.querySelector("#showIgnored"); const reminderHours = document.querySelector("#reminderHours"); const autoRefreshMinutes = document.querySelector("#autoRefreshMinutes"); const terminal = document.querySelector("#terminal"); const saveState = document.querySelector("#saveState"); let currentData = null; let refreshTimer = null; async function request(path, options = {}) { const response = await fetch(path, { headers: { "Content-Type": "application/json" }, ...options, }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Request failed"); } return data; } function formatDate(value) { if (!value || value.startsWith("0001-")) { return "-"; } return new Intl.DateTimeFormat("de-DE", { dateStyle: "short", timeStyle: "short", }).format(new Date(value)); } function visiblePackages() { if (!currentData) { return []; } const query = searchInput.value.trim().toLowerCase(); const source = sourceFilter.value; return currentData.packages.filter((pkg) => { if (pkg.Ignored && !currentData.settings.show_ignored) { return false; } if (source !== "all" && pkg.Source !== source) { return false; } if (query && !pkg.Name.toLowerCase().includes(query)) { return false; } return !pkg.Ignored; }); } function renderTable() { const packages = visiblePackages(); updatesBody.replaceChildren(); for (const pkg of packages) { const row = document.createElement("tr"); row.append(cell(pkg.Source)); row.append(cell(pkg.Name)); row.append(cell(pkg.Current || "-")); row.append(cell(pkg.Available || "-")); const actionCell = document.createElement("td"); const button = document.createElement("button"); button.className = "ghost"; button.type = "button"; button.textContent = "Ausblenden"; button.addEventListener("click", () => setIgnored(pkg.Name, true)); actionCell.append(button); row.append(actionCell); updatesBody.append(row); } updatesTable.hidden = packages.length === 0; emptyState.hidden = packages.length !== 0; } function renderIgnored() { const ignored = currentData.packages.filter((pkg) => pkg.Ignored); ignoredBody.replaceChildren(); ignoredPanel.hidden = ignored.length === 0 || !currentData.settings.show_ignored; ignoredHint.textContent = ignored.length === 1 ? "1 Paket" : ignored.length + " Pakete"; for (const pkg of ignored) { const row = document.createElement("tr"); row.append(cell(pkg.Name)); row.append(cell(pkg.Available || "-")); const actionCell = document.createElement("td"); const button = document.createElement("button"); button.className = "ghost"; button.type = "button"; button.textContent = "Einblenden"; button.addEventListener("click", () => setIgnored(pkg.Name, false)); actionCell.append(button); row.append(actionCell); ignoredBody.append(row); } } function cell(value) { const element = document.createElement("td"); element.textContent = value; return element; } function render(data) { currentData = data; summary.textContent = data.summary; activeCount.textContent = data.total; ignoredCount.textContent = data.ignored_total; installBtn.disabled = data.total === 0 || data.system.pacman_locked; diskFree.textContent = data.system.disk_free + " frei"; kernel.textContent = data.system.kernel; aurHelper.textContent = data.system.aur_helper || "nicht gefunden"; terminalStatus.textContent = data.system.terminal || "nicht gefunden"; lastCheck.textContent = "Letzte Pruefung: " + formatDate(data.state.last_check); lastSuccess.textContent = formatDate(data.state.last_success); lockState.textContent = data.system.pacman_locked ? "Pacman gesperrt" : "Bereit"; lockState.classList.toggle("locked", data.system.pacman_locked); checkAur.checked = data.settings.check_aur; showIgnored.checked = data.settings.show_ignored; reminderHours.value = data.settings.reminder_interval_hours; autoRefreshMinutes.value = data.settings.auto_refresh_minutes; terminal.value = data.settings.terminal || "auto"; warnings.hidden = data.warnings.length === 0; warnings.textContent = data.warnings.join("\\n"); renderTable(); renderIgnored(); scheduleAutoRefresh(); } async function loadStatus() { refreshBtn.disabled = true; summary.textContent = "Pruefe Updates..."; try { render(await request("/api/status")); } catch (error) { summary.textContent = error.message; } finally { refreshBtn.disabled = false; } } function scheduleAutoRefresh() { clearInterval(refreshTimer); const minutes = Number(autoRefreshMinutes.value); if (!minutes || minutes < 1) { return; } refreshTimer = setInterval(loadStatus, minutes * 60 * 1000); } async function setIgnored(name, ignored) { await request("/api/ignore", { method: "POST", body: JSON.stringify({ name, ignored }), }); await loadStatus(); } refreshBtn.addEventListener("click", loadStatus); searchInput.addEventListener("input", renderTable); sourceFilter.addEventListener("change", renderTable); installBtn.addEventListener("click", async () => { installBtn.disabled = true; try { await request("/api/install", { method: "POST" }); summary.textContent = "Installation im Terminal gestartet."; } catch (error) { summary.textContent = error.message; } finally { installBtn.disabled = false; } }); form.addEventListener("submit", async (event) => { event.preventDefault(); saveState.textContent = "Speichere..."; try { await request("/api/settings", { method: "PUT", body: JSON.stringify({ check_aur: checkAur.checked, show_ignored: showIgnored.checked, reminder_interval_hours: Number(reminderHours.value), auto_refresh_minutes: Number(autoRefreshMinutes.value), terminal: terminal.value, ignored_packages: currentData?.settings.ignored_packages || [], }), }); saveState.textContent = "Gespeichert"; await loadStatus(); } catch (error) { saveState.textContent = error.message; } }); loadStatus(); `