"""HTML-Geruest und Stylesheet der Web-Oberflaeche. Alles ist eingebettet - die Seite laedt nichts aus dem Internet nach und funktioniert damit auch in abgeschotteten Netzen. """ from __future__ import annotations STYLE = """ :root { color-scheme: light dark; --bg: #f6f7f9; --fg: #1c1f24; --muted: #6b7280; --line: #d8dce3; --card: #ffffff; --accent: #2563eb; --accent-fg: #ffffff; --hover: #eef2ff; } @media (prefers-color-scheme: dark) { :root { --bg: #16181d; --fg: #e6e8ec; --muted: #9aa1ac; --line: #2c313a; --card: #1e2127; --accent: #4b82f0; --accent-fg: #0b0d10; --hover: #232a39; } } * { box-sizing: border-box; } body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; } header { background: var(--card); border-bottom: 1px solid var(--line); padding: .7rem 1rem; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; position: sticky; top: 0; z-index: 5; } header h1 { font-size: 1rem; margin: 0; font-weight: 650; } header .spacer { flex: 1 1 auto; } main { padding: 1rem; max-width: 1200px; margin: 0 auto; } a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; } .muted { color: var(--muted); } .card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: .25rem .75rem; margin-bottom: 1rem; } table { width: 100%; border-collapse: collapse; } th, td { text-align: left; padding: .45rem .5rem; border-bottom: 1px solid var(--line); } th { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); } tr:last-child td { border-bottom: 0; } tbody tr:hover { background: var(--hover); } td.size, th.size { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; } td.when, th.when { white-space: nowrap; color: var(--muted); font-size: .9em; } td.pick { width: 2.2rem; } .name { display: flex; gap: .5rem; align-items: center; word-break: break-all; } .icon { width: 1.2rem; text-align: center; flex: 0 0 auto; } button, .button { font: inherit; padding: .4rem .8rem; border-radius: 7px; cursor: pointer; border: 1px solid var(--line); background: var(--card); color: var(--fg); } button.primary, .button.primary { background: var(--accent); color: var(--accent-fg); border-color: transparent; } button:hover, .button:hover { filter: brightness(1.08); text-decoration: none; } .bar { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin: .75rem 0; } .crumbs { display: flex; gap: .3rem; flex-wrap: wrap; align-items: center; word-break: break-all; } .crumbs span { color: var(--muted); } .note { border-left: 3px solid var(--accent); background: var(--card); padding: .6rem .8rem; border-radius: 0 8px 8px 0; margin-bottom: 1rem; } .warn { border-left-color: #d9534f; } .pill { display: inline-block; padding: .1rem .5rem; border-radius: 999px; background: var(--hover); font-size: .82em; color: var(--muted); } input[type=checkbox] { width: 1.05rem; height: 1.05rem; } .empty { padding: 2rem 1rem; text-align: center; color: var(--muted); } .login { max-width: 26rem; margin: 3rem auto; } .login .card { padding: 1rem; display: flex; flex-direction: column; gap: .8rem; } .login label { display: flex; flex-direction: column; gap: .3rem; font-size: .9rem; color: var(--muted); } .login input, .login select { font: inherit; color: var(--fg); background: var(--bg); border: 1px solid var(--line); border-radius: 7px; padding: .5rem .6rem; } .login button { margin-top: .3rem; padding: .55rem; } @media (max-width: 640px) { td.when, th.when { display: none; } main { padding: .5rem; } } """ # Auswahl-Kaestchen: "alle" umschalten und Anzahl anzeigen. SCRIPT = """ function pvesnapInit() { var all = document.getElementById('pick-all'); var boxes = Array.prototype.slice.call(document.querySelectorAll('.pick-one')); var info = document.getElementById('pick-info'); function update() { var n = boxes.filter(function (b) { return b.checked; }).length; if (info) { info.textContent = n ? (n + ' ausgewaehlt') : ''; } if (all) { all.checked = n > 0 && n === boxes.length; } } if (all) { all.addEventListener('change', function () { boxes.forEach(function (b) { b.checked = all.checked; }); update(); }); } boxes.forEach(function (b) { b.addEventListener('change', update); }); update(); } document.addEventListener('DOMContentLoaded', pvesnapInit); """ LOGIN_FORM = """
%(note)s

Anmeldung

Mit den Benutzern von Proxmox VE - dieselben Zugangsdaten wie im Proxmox-Webinterface.

""" PAGE = """ %(title)s

pvesnap

%(subtitle)s %(actions)s
%(body)s
"""