jarld keeps every answer for a while (stats.retention.queries, default 7 days) in the statistics database: time, client, view, name, type, result and what blocked it. Every client, name, view, record type, result and blocklist on the Statistics page leads to an explore page for it: answers over time, the top values of every other dimension (each a link that narrows further) and the latest answers. GET /stats/explore. The dashboard shows the size of Jarl's databases (and the query log's rows) and, per disk Jarl uses, how full it is and the databases' share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
616 lines
35 KiB
CSS
616 lines
35 KiB
CSS
/* Jarl web UI. Plain CSS, light and dark themes. */
|
|
:root {
|
|
--bg: #f6f7f9;
|
|
--panel: #ffffff;
|
|
--text: #1d2330;
|
|
--muted: #5f6b7d;
|
|
--hatch: rgba(95, 107, 125, 0.13);
|
|
--border: #d9dee6;
|
|
--accent: #2f5fb3;
|
|
--accent-text: #ffffff;
|
|
--accent-soft: #e5edfa;
|
|
--ok: #1e7a46;
|
|
--ok-soft: #e3f4ea;
|
|
--warn: #8a5a00;
|
|
--warn-soft: #fdf2d9;
|
|
--err: #b3261e;
|
|
--err-soft: #fbe7e5;
|
|
--info-soft: #eef1f6;
|
|
--code-bg: #f0f2f5;
|
|
--add: #e6f6ea;
|
|
--add-text: #135c2c;
|
|
--del: #fde8e7;
|
|
--del-text: #8c1d18;
|
|
--hunk: #e8eefb;
|
|
--shadow: 0 1px 2px rgba(20, 30, 50, 0.08);
|
|
--radius: 6px;
|
|
--vc-green: #3d8160; --vc-teal: #2f7c80; --vc-purple: #7a5ba8; --vc-pink: #a8557f;
|
|
--vc-orange: #a8652f; --vc-yellow: #8a7424; --vc-red: #b0534c; --vc-grey: #6b7280;
|
|
--mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
|
|
color-scheme: light;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme="light"]) {
|
|
--bg: #14171c;
|
|
--panel: #1c2027;
|
|
--text: #e3e7ee;
|
|
--muted: #9aa5b5;
|
|
--hatch: rgba(154, 165, 181, 0.12);
|
|
--border: #2f3540;
|
|
--accent: #6d9be8;
|
|
--accent-text: #0f1520;
|
|
--accent-soft: #23314a;
|
|
--ok: #59c287;
|
|
--ok-soft: #173326;
|
|
--warn: #e5b450;
|
|
--warn-soft: #3a2f15;
|
|
--err: #f08077;
|
|
--err-soft: #3c1f1d;
|
|
--info-soft: #232833;
|
|
--code-bg: #242933;
|
|
--add: #183a25;
|
|
--add-text: #9fe3b5;
|
|
--del: #43201e;
|
|
--del-text: #f7b2ac;
|
|
--hunk: #23314a;
|
|
--shadow: none;
|
|
--vc-green: #8fcfa9; --vc-teal: #87cdd0; --vc-purple: #bca6e6; --vc-pink: #e6a6c6;
|
|
--vc-orange: #ebbb8f; --vc-yellow: #e3d08e; --vc-red: #eba29c; --vc-grey: #aab2bf;
|
|
color-scheme: dark;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--bg: #14171c; --panel: #1c2027; --text: #e3e7ee; --muted: #9aa5b5; --border: #2f3540; --hatch: rgba(154, 165, 181, 0.12);
|
|
--accent: #6d9be8; --accent-text: #0f1520; --accent-soft: #23314a; --ok: #59c287; --ok-soft: #173326;
|
|
--warn: #e5b450; --warn-soft: #3a2f15; --err: #f08077; --err-soft: #3c1f1d; --info-soft: #232833;
|
|
--code-bg: #242933; --add: #183a25; --add-text: #9fe3b5; --del: #43201e; --del-text: #f7b2ac; --hunk: #23314a;
|
|
--shadow: none; color-scheme: dark;
|
|
--vc-green: #8fcfa9; --vc-teal: #87cdd0; --vc-purple: #bca6e6; --vc-pink: #e6a6c6;
|
|
--vc-orange: #ebbb8f; --vc-yellow: #e3d08e; --vc-red: #eba29c; --vc-grey: #aab2bf;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
html, body { margin: 0; padding: 0; }
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, sans-serif;
|
|
}
|
|
a { color: var(--accent); text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
code, pre, .mono, textarea.mono, input.mono { font-family: var(--mono); font-size: 13px; }
|
|
pre { background: var(--code-bg); padding: 10px 12px; border-radius: var(--radius); overflow: auto; margin: 0; white-space: pre; }
|
|
h1 { font-size: 20px; margin: 0 0 4px; }
|
|
h2 { font-size: 16px; margin: 18px 0 8px; }
|
|
h3 { font-size: 14px; margin: 14px 0 6px; }
|
|
.muted { color: var(--muted); }
|
|
.small { font-size: 12px; }
|
|
.nowrap { white-space: nowrap; }
|
|
.warn-text { color: var(--warn); }
|
|
.right { text-align: right; }
|
|
.hidden { display: none !important; }
|
|
|
|
/* Layout */
|
|
header.top {
|
|
display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
|
|
background: var(--panel); border-bottom: 1px solid var(--border);
|
|
padding: 8px 16px; position: sticky; top: 0; z-index: 10;
|
|
}
|
|
header.top .brand { display: inline-flex; align-items: center; gap: 8px; color: var(--text); text-decoration: none; }
|
|
header.top .brand .logo { display: block; transition: transform 0.15s; }
|
|
header.top .brand .brand-name { display: block; color: var(--text); }
|
|
header.top .brand:hover { text-decoration: none; }
|
|
header.top .brand:hover .brand-name { color: var(--accent); }
|
|
header.top .brand:hover .logo { transform: scale(1.06); }
|
|
nav.main { display: flex; gap: 2px; flex-wrap: wrap; }
|
|
nav.main a { padding: 6px 10px; border-radius: var(--radius); color: var(--text); }
|
|
nav.main a:hover { background: var(--info-soft); text-decoration: none; }
|
|
nav.main a.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
header.top .spacer { flex: 1; }
|
|
header.top form.search input { width: 220px; }
|
|
header.top .user { color: var(--muted); }
|
|
main { padding: 16px; max-width: 1400px; margin: 0 auto; }
|
|
.pending-bar {
|
|
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
|
background: var(--warn-soft); color: var(--text); border-bottom: 1px solid var(--border);
|
|
padding: 8px 16px;
|
|
}
|
|
.pending-bar strong { color: var(--warn); }
|
|
.pending-bar .apply-now { margin-left: auto; }
|
|
/* The review/live switch in the header. */
|
|
.mode-toggle { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 500; background: transparent; color: var(--muted); border: 1px solid var(--border); }
|
|
.mode-toggle:hover { color: var(--text); border-color: var(--accent); }
|
|
.mode-toggle .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); }
|
|
.mode-toggle.live { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }
|
|
.mode-toggle.live .dot { background: var(--warn); }
|
|
.page-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
.page-head .grow { flex: 1; min-width: 200px; }
|
|
.actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
|
|
|
/* Panels and cards */
|
|
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow); margin-bottom: 14px; }
|
|
.panel > h2:first-child, .panel > h3:first-child { margin-top: 0; }
|
|
/* Tiles share a row and grow to fill it, so a wrapped tile never sits
|
|
alone beside an empty space. */
|
|
.cards { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
|
|
.cards > * { flex: 1 1 170px; min-width: 0; }
|
|
.card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; box-shadow: var(--shadow); }
|
|
.card .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
|
|
.card .value { font-size: 18px; font-weight: 600; margin-top: 2px; }
|
|
.card .sub { color: var(--muted); font-size: 12px; margin-top: 4px; }
|
|
|
|
/* Tables */
|
|
.table-wrap { overflow-x: auto; }
|
|
table { border-collapse: collapse; width: 100%; background: var(--panel); }
|
|
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
th { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; white-space: nowrap; }
|
|
tr:hover td { background: var(--info-soft); }
|
|
td.data { font-family: var(--mono); font-size: 13px; word-break: break-all; }
|
|
td.name { font-family: var(--mono); font-size: 13px; white-space: nowrap; }
|
|
a.dnssec-lock { display: inline-flex; vertical-align: -1px; margin-left: 6px; color: var(--ok); text-decoration: none; }
|
|
a.dnssec-lock.off { color: var(--warn); }
|
|
tr.same-name-next td { border-bottom-color: color-mix(in srgb, var(--border) 40%, transparent); }
|
|
td.actions-cell { white-space: nowrap; text-align: right; width: 1%; }
|
|
tr.editing td { background: var(--accent-soft); }
|
|
tr.pending-add td { background: var(--add); }
|
|
tr.pending-change td, tr.pending-disable td, tr.pending-enable td { background: var(--warn-soft); }
|
|
tr.op-failed td { background: var(--err-soft); }
|
|
tr.op-failed td:first-child { box-shadow: inset 3px 0 0 var(--err); }
|
|
tr.pending-delete td { background: var(--del); text-decoration: line-through; color: var(--muted); }
|
|
tr.disabled td:not(.actions-cell), tr.disabled td strong { color: var(--muted); }
|
|
/* Disabled records: hatched, so they stand out from live and pending rows. */
|
|
tr.disabled td { background-image: repeating-linear-gradient(135deg, transparent 0 7px, var(--hatch) 7px 9px); }
|
|
.badge.off { background: var(--muted); color: var(--panel); }
|
|
button.toggle { min-width: 5.2em; }
|
|
.empty { color: var(--muted); padding: 18px; text-align: center; }
|
|
|
|
/* Forms */
|
|
input, select, textarea, button {
|
|
font: inherit; color: var(--text);
|
|
}
|
|
input[type=text], input[type=password], input[type=number], input[type=search], input:not([type]), select, textarea {
|
|
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 5px 8px; min-width: 0;
|
|
}
|
|
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
|
|
textarea { width: 100%; min-height: 80px; resize: vertical; }
|
|
.form-grid { display: grid; grid-template-columns: minmax(140px, 200px) 1fr; gap: 8px 14px; align-items: start; }
|
|
.form-grid > label { padding-top: 6px; color: var(--muted); }
|
|
.form-grid .help { grid-column: 2; margin-top: -4px; color: var(--muted); font-size: 12px; }
|
|
.form-grid input, .form-grid select { width: 100%; max-width: 560px; }
|
|
.form-grid input[type=checkbox] { width: auto; }
|
|
.inline-form { display: flex; gap: 6px; flex-wrap: wrap; align-items: flex-end; }
|
|
.inline-form .f { display: flex; flex-direction: column; gap: 2px; }
|
|
.inline-form .f label:not(.check) { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.3px; }
|
|
.inline-form .grow { flex: 1; min-width: 220px; }
|
|
.inline-form .grow input { width: 100%; }
|
|
.hint { color: var(--muted); font-size: 12px; margin-top: 4px; }
|
|
.check { display: inline-flex; gap: 6px; align-items: center; cursor: pointer; }
|
|
|
|
button, .button {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
|
|
padding: 5px 12px; cursor: pointer; white-space: nowrap; color: var(--text);
|
|
}
|
|
button:hover, .button:hover { background: var(--info-soft); text-decoration: none; }
|
|
button.primary, .button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); font-weight: 600; }
|
|
button.primary:hover { filter: brightness(1.08); background: var(--accent); }
|
|
button.danger { color: var(--err); border-color: var(--err); }
|
|
button.danger:hover { background: var(--err-soft); }
|
|
button.small, .button.small { padding: 2px 8px; font-size: 12px; }
|
|
button.link { border: none; background: none; color: var(--accent); padding: 0 4px; }
|
|
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
/* Badges and messages */
|
|
.badge { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 11px; font-weight: 600; background: var(--info-soft); color: var(--muted); white-space: nowrap; }
|
|
.badge.ok { background: var(--ok-soft); color: var(--ok); }
|
|
.badge.warn { background: var(--warn-soft); color: var(--warn); }
|
|
.badge.err { background: var(--err-soft); color: var(--err); }
|
|
.badge.accent { background: var(--accent-soft); color: var(--accent); }
|
|
.msg { border-radius: var(--radius); padding: 8px 12px; margin: 8px 0; border: 1px solid transparent; }
|
|
.msg.err { background: var(--err-soft); color: var(--err); border-color: var(--err); }
|
|
.msg.warn { background: var(--warn-soft); color: var(--warn); }
|
|
.msg.ok { background: var(--ok-soft); color: var(--ok); }
|
|
.msg.info { background: var(--info-soft); }
|
|
.msg ul { margin: 4px 0 0; padding-left: 18px; }
|
|
.problems { list-style: none; padding: 0; margin: 6px 0; }
|
|
.problems li { padding: 4px 8px; border-left: 3px solid var(--border); margin-bottom: 3px; background: var(--info-soft); border-radius: 0 var(--radius) var(--radius) 0; }
|
|
.problems li.error { border-color: var(--err); background: var(--err-soft); }
|
|
.problems li.warning { border-color: var(--warn); background: var(--warn-soft); }
|
|
.problems li .where { color: var(--muted); font-size: 12px; margin-left: 6px; }
|
|
|
|
/* Diff */
|
|
.diff { background: var(--code-bg); border-radius: var(--radius); overflow: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.4; }
|
|
.diff div { white-space: pre; padding: 0 10px; }
|
|
.diff .add { background: var(--add); color: var(--add-text); }
|
|
.diff .del { background: var(--del); color: var(--del-text); }
|
|
.diff .hunk { background: var(--hunk); color: var(--accent); }
|
|
.diff .file { font-weight: 700; padding-top: 6px; }
|
|
|
|
/* Tabs */
|
|
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 12px; flex-wrap: wrap; }
|
|
.tabs a { padding: 7px 12px; border: 1px solid transparent; border-bottom: none; border-radius: var(--radius) var(--radius) 0 0; color: var(--muted); }
|
|
.tabs a.active { background: var(--panel); border-color: var(--border); color: var(--text); font-weight: 600; margin-bottom: -1px; }
|
|
|
|
/* Modal */
|
|
.modal-back { position: fixed; inset: 0; background: rgba(10, 15, 25, 0.45); display: flex; align-items: flex-start; justify-content: center; padding: 60px 16px; z-index: 50; overflow: auto; }
|
|
.modal { background: var(--panel); border-radius: 8px; border: 1px solid var(--border); width: min(760px, 100%); padding: 18px 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.25); }
|
|
.modal.wide { width: min(1100px, 100%); }
|
|
.modal h2 { margin-top: 0; }
|
|
.modal .buttons { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
|
|
|
|
/* Toasts */
|
|
#toasts { position: fixed; bottom: 16px; right: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 100; max-width: min(480px, calc(100% - 32px)); }
|
|
.toast { background: var(--panel); border: 1px solid var(--border); border-left: 4px solid var(--accent); padding: 10px 14px; border-radius: var(--radius); box-shadow: 0 4px 16px rgba(0,0,0,0.18); }
|
|
.toast.ok { border-left-color: var(--ok); }
|
|
.toast.err { border-left-color: var(--err); }
|
|
.toast.warn { border-left-color: var(--warn); }
|
|
|
|
/* Login */
|
|
.login { max-width: 360px; margin: 10vh auto; }
|
|
.login-logo { display: block; margin: 8px auto 10px; }
|
|
.login h1 { text-align: center; margin: 0 0 8px; }
|
|
.wordmark { display: block; width: 220px; height: auto; margin: 0 auto; color: var(--text); }
|
|
.wordmark-rule .fill { fill: var(--accent); }
|
|
.wordmark-rule .line { fill: none; stroke: var(--accent); stroke-width: 2.6; }
|
|
.wordmark-rule .dns { fill: var(--accent); font: 700 12.5px system-ui, sans-serif; letter-spacing: 0.5px; }
|
|
.login .tag { text-align: center; color: var(--muted); margin-bottom: 18px; }
|
|
.login form { display: flex; flex-direction: column; gap: 10px; }
|
|
.login input { width: 100%; padding: 8px 10px; }
|
|
.login button { justify-content: center; padding: 8px; }
|
|
|
|
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; }
|
|
.kv dt { color: var(--muted); }
|
|
.kv dd { margin: 0; }
|
|
.grants { font-family: var(--mono); font-size: 12px; }
|
|
.copy-box { display: flex; gap: 6px; align-items: center; }
|
|
.login.wide { max-width: 520px; }
|
|
.totp-steps { padding-left: 20px; }
|
|
.totp-steps li { margin-bottom: 12px; }
|
|
.totp-qr { margin: 10px 0; }
|
|
.totp-qr img { display: block; width: 200px; height: 200px; background: #fff; border-radius: var(--radius); }
|
|
.totp-confirm { display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
|
|
input.totp-code { font-family: var(--mono); font-size: 18px; letter-spacing: 0.15em; max-width: 14em; }
|
|
.recovery-codes { font-family: var(--mono); font-size: 15px; columns: 2; max-width: 360px; }
|
|
.copy-box input { flex: 1; font-family: var(--mono); }
|
|
.ds-record { margin: 12px 0 16px; }
|
|
.ds-record > .copy-box { max-width: 900px; }
|
|
.ds-record .kv { margin-top: 8px; align-items: center; grid-template-columns: max-content minmax(0, 1fr); }
|
|
.ds-record .kv dd { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
|
|
.ds-record .kv dd .copy-box { flex: 1 1 200px; max-width: 640px; min-width: 0; }
|
|
.copy-box input { min-width: 0; }
|
|
td.break { word-break: break-all; }
|
|
.sortable-row { display: flex; align-items: center; gap: 8px; }
|
|
@media (max-width: 700px) {
|
|
.form-grid { grid-template-columns: 1fr; }
|
|
.form-grid .help { grid-column: 1; }
|
|
header.top form.search input { width: 140px; }
|
|
}
|
|
|
|
/* ---- Statistics charts. Colors from the validated reference palette
|
|
(categorical slots 1-3, sequential blue), checked against this UI's
|
|
panel surfaces in both themes. */
|
|
:root {
|
|
--series-1: #2a78d6;
|
|
--series-2: #eb6834;
|
|
--series-3: #1baf7a;
|
|
--chart-grid: #eceef2;
|
|
--chart-axis: #c9cfd8;
|
|
/* Sequential ramp, low to high (blue 100 -> 700). */
|
|
--heat-0: #cde2fb; --heat-1: #9ec5f4; --heat-2: #6da7ec; --heat-3: #3987e5;
|
|
--heat-4: #256abf; --heat-5: #184f95; --heat-6: #0d366b;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme="light"]) {
|
|
--series-1: #3987e5;
|
|
--series-2: #d95926;
|
|
--series-3: #199e70;
|
|
--chart-grid: #272c34;
|
|
--chart-axis: #3a414d;
|
|
/* In dark, low values recede into the surface: the ramp runs dark -> light. */
|
|
--heat-0: #0d366b; --heat-1: #184f95; --heat-2: #256abf; --heat-3: #3987e5;
|
|
--heat-4: #6da7ec; --heat-5: #9ec5f4; --heat-6: #cde2fb;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--series-1: #3987e5;
|
|
--series-2: #d95926;
|
|
--series-3: #199e70;
|
|
--chart-grid: #272c34;
|
|
--chart-axis: #3a414d;
|
|
--heat-0: #0d366b; --heat-1: #184f95; --heat-2: #256abf; --heat-3: #3987e5;
|
|
--heat-4: #6da7ec; --heat-5: #9ec5f4; --heat-6: #cde2fb;
|
|
}
|
|
|
|
.refreshing { opacity: 0.6; transition: opacity 0.2s; }
|
|
/* Secondary row actions show when the row is hovered or focused; touch
|
|
screens, which can't hover, always show them. Opacity keeps them in the
|
|
layout (no shifting) and reachable with Tab. */
|
|
tr .reveal { opacity: 0; transition: opacity 0.1s; }
|
|
tr:hover .reveal, tr:focus-within .reveal { opacity: 1; }
|
|
@media (hover: none) { tr .reveal { opacity: 1; } }
|
|
/* Blocking steps, in order, on the Blocking page. */
|
|
.steps-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin: 4px 0 10px; }
|
|
.step-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border: 1px solid var(--border); border-radius: 12px; font-family: var(--mono); font-size: 13px; background: var(--panel); }
|
|
.step-chip.accent { border-color: var(--accent); }
|
|
.step-tools { display: inline-flex; gap: 2px; margin-left: 2px; }
|
|
.step-tools button { padding: 0 5px; min-width: 0; }
|
|
h2.section { margin: 24px 0 10px; font-size: 18px; }
|
|
.filter-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--panel); }
|
|
.segmented a { padding: 4px 12px; color: var(--text); border-right: 1px solid var(--border); text-decoration: none; font-variant-numeric: tabular-nums; }
|
|
.segmented a:last-child { border-right: none; }
|
|
.segmented a:hover { background: var(--info-soft); }
|
|
.segmented a.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
|
|
.card.stat .value.big { font-size: 26px; font-weight: 600; line-height: 1.2; }
|
|
|
|
.chart-card { margin: 0 0 14px; }
|
|
.chart-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
|
|
.chart-head .grow { flex: 1; min-width: 200px; }
|
|
.chart-head h2 { margin: 0; font-size: 16px; }
|
|
/* Two or three columns; the last cards stretch to fill their row, so no
|
|
card is left alone beside an empty space. */
|
|
.chart-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
|
|
.chart-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }
|
|
@media (min-width: 1100px) {
|
|
.chart-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.chart-grid > :last-child:nth-child(odd) { grid-column: auto; }
|
|
.chart-grid > :last-child:nth-child(3n+1) { grid-column: 1 / -1; }
|
|
.chart-grid > :last-child:nth-child(3n+2) { grid-column: span 2; }
|
|
}
|
|
.chart-grid > .panel h2 { margin: 0; font-size: 16px; }
|
|
.list-sub { margin: 2px 0 8px; }
|
|
|
|
.chart { position: relative; outline: none; }
|
|
.chart:focus-visible { box-shadow: 0 0 0 2px var(--accent); border-radius: var(--radius); }
|
|
.chart svg { display: block; overflow: visible; }
|
|
.chart .grid { stroke: var(--chart-grid); stroke-width: 1; }
|
|
.chart .axis { stroke: var(--chart-axis); stroke-width: 1; }
|
|
.chart .tick, .chart .end-label { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
.chart .end-label { fill: var(--text); }
|
|
.chart .line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
|
.chart .area { stroke: none; fill-opacity: 0.1; }
|
|
.chart .dot { stroke: var(--panel); stroke-width: 2; }
|
|
.chart .crosshair { stroke: var(--muted); stroke-width: 1; }
|
|
.chart .hit { fill: transparent; cursor: crosshair; }
|
|
.chart .s1, .key.s1 { stroke: var(--series-1); }
|
|
.chart .s2, .key.s2 { stroke: var(--series-2); }
|
|
.chart .s3, .key.s3 { stroke: var(--series-3); }
|
|
.chart .area.s1, .chart .dot.s1 { fill: var(--series-1); }
|
|
.chart .area.s2, .chart .dot.s2 { fill: var(--series-2); }
|
|
.chart .area.s3, .chart .dot.s3 { fill: var(--series-3); }
|
|
.key.line { display: inline-block; width: 14px; height: 0; border-top: 2px solid; vertical-align: middle; margin-right: 6px; }
|
|
.key.line.s1 { border-color: var(--series-1); }
|
|
.key.line.s2 { border-color: var(--series-2); }
|
|
.key.line.s3 { border-color: var(--series-3); }
|
|
.chart-legend { list-style: none; display: flex; gap: 14px; margin: 2px 0 0; padding: 0; font-size: 13px; color: var(--text); flex-wrap: wrap; }
|
|
|
|
.chart-tip { position: absolute; z-index: 5; pointer-events: none; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16); padding: 6px 10px; font-size: 13px; white-space: nowrap; }
|
|
.chart-tip .tip-head { color: var(--muted); font-size: 12px; margin-bottom: 2px; }
|
|
.chart-tip .tip-row strong { font-variant-numeric: tabular-nums; }
|
|
.chart-table .scroll, .chart-table .table-wrap { max-height: 360px; overflow: auto; }
|
|
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
|
|
.heatmap-wrap { position: relative; }
|
|
.heatmap { display: grid; grid-template-columns: 36px repeat(24, minmax(10px, 1fr)); gap: 2px; outline: none; }
|
|
.heatmap:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
|
|
.hm-corner, .hm-col, .hm-row { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
.hm-row { display: flex; align-items: center; }
|
|
.hm-cell { height: 22px; border-radius: 3px; }
|
|
.hm-cell:hover, .hm-cell:focus { outline: 2px solid var(--text); outline-offset: -1px; }
|
|
.hm-cell.empty, .hm-swatch.empty { background: transparent; box-shadow: inset 0 0 0 1px var(--chart-grid); }
|
|
.heat-0 { background: var(--heat-0); } .heat-1 { background: var(--heat-1); } .heat-2 { background: var(--heat-2); }
|
|
.heat-3 { background: var(--heat-3); } .heat-4 { background: var(--heat-4); } .heat-5 { background: var(--heat-5); }
|
|
.heat-6 { background: var(--heat-6); }
|
|
.hm-scale { display: flex; align-items: center; gap: 2px; margin-top: 10px; flex-wrap: wrap; }
|
|
.hm-scale .muted { margin: 0 6px; }
|
|
.hm-swatch { display: inline-block; width: 18px; height: 10px; border-radius: 2px; }
|
|
|
|
.bar-list { list-style: none; margin: 0; padding: 0; }
|
|
.bar-list li { padding: 4px 0; }
|
|
.bar-text { display: flex; gap: 8px; justify-content: space-between; font-size: 13px; }
|
|
.bar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 12.5px; min-width: 0; }
|
|
.bar-value { white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
.bar-track { height: 6px; margin-top: 3px; }
|
|
.bar-fill { height: 6px; background: var(--series-1); border-radius: 0 3px 3px 0; min-width: 2px; }
|
|
@media (max-width: 700px) {
|
|
.heatmap { grid-template-columns: 30px repeat(24, minmax(6px, 1fr)); gap: 1px; }
|
|
.hm-cell { height: 16px; }
|
|
.chart-grid { grid-template-columns: 1fr; }
|
|
}
|
|
|
|
/* Address match list suggestions. */
|
|
.aml-suggest { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; margin-top: 6px; }
|
|
.aml-suggest .muted { margin-left: 4px; }
|
|
.aml-suggest .muted:first-child { margin-left: 0; }
|
|
button.chip { padding: 1px 8px; font-size: 12px; font-family: var(--mono); border-radius: 999px; background: var(--info-soft); border: 1px solid var(--border); color: var(--text); }
|
|
button.chip:hover { background: var(--accent-soft); border-color: var(--accent); }
|
|
.aml-problems .msg { margin: 4px 0 0; padding: 3px 8px; }
|
|
.range-hints .msg { margin: 4px 0 0; padding: 3px 8px; }
|
|
.range-hint { margin-top: 4px; color: var(--muted); }
|
|
.range-hint summary { cursor: pointer; }
|
|
.range-hint .prefixes { display: flex; flex-wrap: wrap; gap: 2px 12px; margin: 2px 0 0 14px; }
|
|
.range-hint .prefixes span { white-space: nowrap; }
|
|
|
|
/* Zone name and record previews. */
|
|
.segmented button { border: none; border-right: 1px solid var(--border); border-radius: 0; background: transparent; padding: 4px 12px; color: var(--text); }
|
|
.segmented button:last-child { border-right: none; }
|
|
.segmented button:hover { background: var(--info-soft); }
|
|
.segmented button.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
|
|
.name-preview { margin-top: 6px; font-size: 13px; }
|
|
.name-preview .msg, .will-add .msg { margin: 4px 0 0; padding: 3px 8px; }
|
|
.will-add { margin-top: 8px; font-size: 13px; }
|
|
.will-add code { font-family: var(--mono); background: var(--code-bg); padding: 1px 6px; border-radius: 4px; word-break: break-all; }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
header.top .brand .logo { transition: none; }
|
|
header.top .brand:hover .logo { transform: none; }
|
|
}
|
|
|
|
/* a zone tab's previous content while the next tab loads */
|
|
.zone-body.stale { opacity: 0.5; transition: opacity 0.15s 0.1s; pointer-events: none; }
|
|
|
|
/* the "← Zones" link above a page's heading */
|
|
.back-link { font-size: 13px; margin-bottom: 2px; }
|
|
|
|
/* view colours (the default is the link colour) */
|
|
.vc-green { color: var(--vc-green); } .vc-teal { color: var(--vc-teal); } .vc-purple { color: var(--vc-purple); }
|
|
.vc-pink { color: var(--vc-pink); } .vc-orange { color: var(--vc-orange); } .vc-yellow { color: var(--vc-yellow); }
|
|
.vc-red { color: var(--vc-red); } .vc-grey { color: var(--vc-grey); } .vc-default { color: var(--accent); }
|
|
.swatches { display: flex; flex-wrap: wrap; gap: 8px; padding: 2px 0; }
|
|
.swatch { width: 24px; height: 24px; min-width: 0; padding: 0; border-radius: 50%; background: currentColor; border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--border); cursor: pointer; }
|
|
.swatch:hover { background: currentColor; }
|
|
.swatch.selected { box-shadow: 0 0 0 2px currentColor; }
|
|
.swatch:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
|
|
tr.selected td { background: var(--accent-soft); }
|
|
|
|
/* the page footer with the Jarl build */
|
|
.shell { min-height: 100vh; display: flex; flex-direction: column; }
|
|
.shell > main { flex: 1; width: 100%; }
|
|
footer.site { padding: 12px 16px 16px; text-align: center; font-size: 12px; color: var(--muted); }
|
|
footer.site a { color: var(--muted); text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 2px; }
|
|
footer.site a:hover { color: var(--accent); text-decoration-color: currentColor; }
|
|
|
|
/* a request in progress */
|
|
.spinner { display: inline-block; width: 11px; height: 11px; margin-right: 6px; vertical-align: -1px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; border-top-color: var(--border); background: var(--accent-soft); } }
|
|
|
|
/* the queries a name server check sends */
|
|
.query-log { list-style: none; margin: 6px 0; padding: 0; font-size: 13px; }
|
|
.query-log li { padding: 3px 0; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
|
|
.query-log code { background: var(--code-bg); padding: 1px 6px; border-radius: 4px; overflow-wrap: anywhere; }
|
|
.query-log li.pending code { opacity: 0.85; }
|
|
.err-text { color: var(--err); }
|
|
ul.plain { list-style: none; margin: 8px 0; padding: 0; }
|
|
.code { tab-size: 4; }
|
|
textarea.code { width: 100%; white-space: pre; overflow-wrap: normal; overflow-x: auto; }
|
|
details.view-picker { position: relative; }
|
|
details.view-picker summary { cursor: pointer; padding: 5px 8px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); min-width: 12em; max-width: 24em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
details.view-picker[open] .view-picker-list { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: 14em; max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
|
|
ul.plain li { padding: 3px 0; }
|
|
.query-details { margin: 8px 0; }
|
|
|
|
/* Internet exposure findings */
|
|
.findings { display: flex; flex-direction: column; gap: 8px; }
|
|
.finding { display: flex; gap: 12px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--line); border-left-width: 3px; border-radius: 6px; }
|
|
.finding.warning { border-left-color: var(--warn); }
|
|
.finding.error { border-left-color: var(--err); }
|
|
.finding.acked, .finding.info { border-left-color: var(--line); color: var(--muted); }
|
|
.finding .actions { flex-shrink: 0; }
|
|
.risk-confirm { margin-top: 8px; }
|
|
|
|
a.card.link { color: inherit; text-decoration: none; }
|
|
a.card.link:hover { border-color: var(--accent, var(--link)); }
|
|
|
|
/* An option's value as shown: laid out like the editor (no wrapping, a
|
|
long line scrolls; same padding and an invisible border), so the text
|
|
stays put when Edit opens it. */
|
|
pre.optval { padding: 5px 8px; border: 1px solid transparent; background: none; white-space: pre; overflow-x: auto; max-width: min(72ch, 50vw); }
|
|
/* ...and the editor in a table cell gets the same width, so the column
|
|
doesn't jump when Edit opens. */
|
|
td textarea.code { min-width: min(72ch, 50vw); }
|
|
|
|
/* Certificates: the "check the names" result under the ACME fields. */
|
|
.check-names { margin: 6px 0 14px; }
|
|
.check-names .table-wrap { margin-top: 8px; }
|
|
|
|
/* A list to pick rows from (linking zones): the whole row toggles. */
|
|
table.pick-list tr.pick { cursor: pointer; }
|
|
table.pick-list tr.pick td:first-child { width: 2em; }
|
|
table.pick-list tr.pick.selected { background: var(--accent-soft); }
|
|
|
|
/* Encrypted DNS: a protocol per line with its port. */
|
|
.enc-ports > div { margin: 2px 0; }
|
|
.enc-ports input[type=number] { display: inline-block; }
|
|
|
|
/* Statistics: views switched on and off. */
|
|
.filter-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
|
|
.filter-rows .filter-row { margin-bottom: 0; }
|
|
.filter-row .chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
.chip.toggle-chip { opacity: 0.55; font-size: 13px; padding: 3px 12px; }
|
|
.chip.toggle-chip.on { opacity: 1; text-decoration: none; background: var(--accent-soft); border-color: var(--accent); }
|
|
|
|
/* Drop-down menus in the top bar (Server, the user). */
|
|
details.menu { position: relative; }
|
|
details.menu > summary { list-style: none; cursor: pointer; padding: 6px 10px; border-radius: var(--radius); color: var(--text); white-space: nowrap; }
|
|
details.menu > summary::-webkit-details-marker { display: none; }
|
|
details.menu > summary:hover, details.menu[open] > summary { background: var(--info-soft); }
|
|
details.menu > summary.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
details.menu > summary .caret { margin-left: 4px; font-size: 0.8em; opacity: 0.7; }
|
|
details.menu > summary .user { color: inherit; }
|
|
details.menu .menu-list {
|
|
position: absolute; top: calc(100% + 4px); left: 0; z-index: 20; min-width: 190px;
|
|
display: flex; flex-direction: column; padding: 4px;
|
|
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
|
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
|
}
|
|
details.menu.right .menu-list { left: auto; right: 0; }
|
|
details.menu .menu-list a, details.menu .menu-list .menu-item {
|
|
display: block; padding: 7px 10px; border-radius: var(--radius); color: var(--text); text-align: left;
|
|
background: none; border: 0; font: inherit; cursor: pointer; white-space: nowrap;
|
|
}
|
|
details.menu .menu-list a:hover, details.menu .menu-list .menu-item:hover { background: var(--info-soft); text-decoration: none; }
|
|
details.menu .menu-list a.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
nav.main { align-items: center; }
|
|
header.top .header-right { display: flex; align-items: center; gap: 12px; margin-left: auto; }
|
|
@media (max-width: 1200px) {
|
|
header.top form.search input { width: 160px; }
|
|
header.top { gap: 10px; }
|
|
}
|
|
|
|
/* Encrypted DNS: the health of each listener. */
|
|
ul.probes { list-style: none; padding: 0; margin: 0 0 8px; }
|
|
ul.probes > li { padding: 6px 0; border-bottom: 1px solid var(--border); }
|
|
ul.probes > li .msg { margin: 6px 0 0; }
|
|
|
|
/* Comparing a zone across views. */
|
|
table.compare td { vertical-align: top; }
|
|
/* The same name and type columns in every section, so they line up. */
|
|
table.compare { table-layout: auto; width: 100%; }
|
|
table.compare tr > :nth-child(1) { width: 18ch; }
|
|
table.compare tr > :nth-child(2) { width: 8ch; }
|
|
table.compare td.compare-cell button { margin-top: 4px; }
|
|
/* One line per record: a long TXT record is cut, with the whole of it on hover. */
|
|
table.compare .oneline { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46ch; }
|
|
.compare-actions { margin-top: 4px; }
|
|
.compare-controls { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 12px; }
|
|
.compare-controls label { color: var(--muted); }
|
|
ul.compare-summary { margin: 0 0 12px; padding-left: 18px; }
|
|
details.compare-only { margin: 12px 0; }
|
|
details.compare-only summary { cursor: pointer; padding: 4px 0; }
|
|
table.compare tr.muted td { color: var(--muted); }
|
|
|
|
/* Text that wraps anywhere, e.g. long statements in the Added by Jarl table. */
|
|
pre.wrap { white-space: pre-wrap; word-break: break-all; }
|
|
|
|
/* New reverse zone: sizes and the networks the records use. */
|
|
.reverse-helper { margin-top: 6px; }
|
|
.form-grid > .grid-cell { min-width: 0; }
|
|
.size-chips { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }
|
|
.size-chips button.active { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
|
|
.cands { display: flex; flex-direction: column; gap: 3px; max-height: 260px; overflow-y: auto; }
|
|
.cands button.cand { flex-shrink: 0; text-align: left; font-weight: normal; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
/* Encrypted DNS: per listen address, the views that get its records. */
|
|
td.addr-views { width: 100%; }
|
|
.view-chips { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; }
|
|
.view-chips label.chip { white-space: nowrap; font-size: 0.9em; }
|
|
|
|
/* Explore: the filter chips with a ✕ each, and the latest answers. */
|
|
.filter-chips { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
.chip.filter-chip { display: inline-flex; gap: 2px; align-items: center; padding: 2px 6px 2px 10px; border-radius: 999px; background: var(--info-soft); border: 1px solid var(--border); font-size: 13px; }
|
|
.chip.filter-chip a.chip-x { text-decoration: none; color: var(--muted); padding: 0 4px; }
|
|
.chip.filter-chip a.chip-x:hover { color: var(--err, #c00); }
|
|
table.recent-queries td.oneline-cell { max-width: 40ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
/* Dashboard storage: a bar per disk. */
|
|
.disk { margin-top: 14px; }
|
|
.disk-head { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; margin-bottom: 4px; }
|
|
.disk-head .grow { flex: 1; }
|
|
.disk-bar { display: flex; height: 10px; border-radius: 5px; background: var(--info-soft); overflow: hidden; border: 1px solid var(--border); }
|
|
.disk-bar .seg.db { background: var(--accent); }
|
|
.disk-bar .seg.other { background: var(--muted); opacity: 0.45; }
|
|
.disk-bar .seg.other.full { background: var(--warn); opacity: 0.8; }
|
|
.swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: -1px; }
|
|
.swatch.db { background: var(--accent); }
|
|
.swatch.other { background: var(--muted); opacity: 0.45; }
|