"""Server-rendered HTML. One embedded stylesheet, no JavaScript beyond a confirm() on the destructive buttons.""" from __future__ import annotations import html from datetime import date from .. import util from ..settings import DEFAULTS, EDITABLE, MASKED_KEYS STYLE = """ :root { --bg: #14161a; --panel: #1c1f26; --line: #2c313b; --text: #e6e8ec; --muted: #99a0ae; --accent: #6aa9ff; --warn: #ffb454; --bad: #ff6b6b; --good: #6ade9b; } * { box-sizing: border-box; } body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; } main { max-width: 62rem; margin: 0 auto; padding: 1.5rem 1rem 4rem; } h1 { font-size: 1.4rem; margin: 0; } h2 { font-size: 1.05rem; margin: 2rem 0 .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; } header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; border-bottom: 1px solid var(--line); padding-bottom: .75rem; } header .sub { color: var(--muted); font-size: .85rem; } a { color: var(--accent); } .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 1rem; } table { width: 100%; border-collapse: collapse; } th, td { text-align: left; padding: .55rem .5rem; border-bottom: 1px solid var(--line); vertical-align: middle; } th { color: var(--muted); font-weight: 600; font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; } td.num { text-align: right; font-variant-numeric: tabular-nums; } .muted { color: var(--muted); } .badge { display: inline-block; padding: .1rem .45rem; border-radius: 999px; font-size: .75rem; border: 1px solid currentColor; } .badge.warn { color: var(--warn); } .badge.good { color: var(--good); } .badge.bad { color: var(--bad); } input[type=text], input[type=password], input[type=number], select { background: #12141a; color: var(--text); border: 1px solid var(--line); border-radius: 7px; padding: .45rem .55rem; font: inherit; width: 100%; } button { background: var(--accent); color: #0b1017; border: 0; border-radius: 7px; padding: .5rem .9rem; font: inherit; font-weight: 600; cursor: pointer; } button.secondary { background: #2b3140; color: var(--text); } button.danger { background: transparent; color: var(--bad); border: 1px solid var(--bad); font-weight: 500; padding: .3rem .6rem; } button.link { background: transparent; color: var(--accent); border: 0; padding: .3rem .4rem; font-weight: 500; } form.inline { display: inline; } .row { display: flex; gap: .6rem; align-items: center; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: .85rem; } label { display: block; font-size: .82rem; color: var(--muted); margin-bottom: .25rem; } .field { margin-bottom: .3rem; } .error { color: var(--bad); font-size: .8rem; margin-top: .2rem; } .flash { border-radius: 8px; padding: .6rem .8rem; margin-bottom: 1rem; border: 1px solid; } .flash.ok { color: var(--good); border-color: var(--good); } .flash.bad { color: var(--bad); border-color: var(--bad); } .login { max-width: 21rem; margin: 6rem auto; } footer { margin-top: 2.5rem; color: var(--muted); font-size: .8rem; } @media (max-width: 40rem) { th.hide, td.hide { display: none; } } """ def _e(value) -> str: return html.escape("" if value is None else str(value), quote=True) def page(title: str, body: str) -> bytes: return f"""
Sign in to manage subscriptions.
{alert}| Channel | On disk | Latest upload | Last poll | Retention (days) |
|---|