This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 06:20:03 +01:00
1 parent 5cb63f542c
commit 17e314e159
27 files changed
+1017 -291

No files matched your search

+10 -2
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.36.2",
"version": "0.36.3",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
},
"servers": [
@@ -3812,7 +3812,15 @@
"neon",
"monochrome",
"warm",
"cool"
"cool",
"sunset",
"aurora",
"ocean",
"forest",
"pastel",
"terminal",
"blueprint",
"colorblind"
],
"default": "default",
"description": "Account-scoped visual presentation preset applied consistently to administration charts."
+8
View File
@@ -303,6 +303,14 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
| "monochrome"
| "warm"
| "cool"
| "sunset"
| "aurora"
| "ocean"
| "forest"
| "pastel"
| "terminal"
| "blueprint"
| "colorblind"
) {
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
+2 -2
View File
@@ -115,7 +115,7 @@ window.rustProxyChartScale = (() => {
{ top = 0.08, bottom = 0.92, clipped = false, seriesScales = null } = {}) {
const node = typeof target === "string" ? document.getElementById(target) : target;
const svg = node?.tagName?.toLowerCase() === "svg" ? node : node?.ownerSVGElement;
if (!svg) return;
if (!svg || svg.classList.contains("ui-icon")) return;
svg.rustProxyAxisData = {
peak: Number.isFinite(peakValue) && peakValue > 0 ? peakValue : 1,
unit, count: Number.isInteger(count) && count > 0 ? count : 0,
@@ -127,7 +127,7 @@ window.rustProxyChartScale = (() => {
}
function refreshAxes() {
const show = document.documentElement.dataset.chartAxes === "true";
for (const svg of document.querySelectorAll("svg")) {
for (const svg of document.querySelectorAll("svg:not(.ui-icon)")) {
if (show && svg.rustProxyAxisData) renderAxes(svg);
else if (!show && svg.parentElement?.classList.contains("rp-chart-axis-host")) {
const host = svg.parentElement;
+1 -1
View File
@@ -168,7 +168,7 @@ function uiIconForTarget(target) {
if (manual && UI_ICON_SHAPES[manual]) return manual;
if (target.matches(".chart-style-choice")) return null;
if (target.matches(".overview-inline-grip, .overview-inline-narrow, .overview-inline-widen, .overview-inline-up, .overview-inline-down, .overview-inline-hide, .overview-layout-narrow, .overview-layout-widen, .overview-layout-up, .overview-layout-down, .overview-layout-hide")) return null;
if (target.matches("#tunnels-open, .vhost-backend-protocol-badge, .config-history-day, .draft-dock-toggle")) return null;
if (target.matches("#tunnels-open, .vhost-backend-protocol-badge, .config-history-day, .draft-dock-toggle, .mobile-menu-toggle")) return null;
if (UI_GLYPH_LABEL.test(String(target.textContent || "").trim())) return null;
if (UI_ID_ICONS[target.id]) return UI_ID_ICONS[target.id];
if (target.matches("#web-service-preset-apply, [data-cache-memory-preset], [data-cache-cold-preset], [data-transform-preset]")) return "wand";
+2 -2
View File
@@ -1,6 +1,6 @@
const ADMIN_THEMES = new Set(["dark", "ultra_dark", "light", "cream"]);
const ADMIN_TIME_FORMATS = new Set(["24h", "12h"]);
const ADMIN_CHART_STYLES = new Set(["default", "soft", "technical", "high_contrast", "neon", "monochrome", "warm", "cool"]);
const ADMIN_CHART_STYLES = new Set(["default", "soft", "technical", "high_contrast", "neon", "monochrome", "warm", "cool", "sunset", "aurora", "ocean", "forest", "pastel", "terminal", "blueprint", "colorblind"]);
const ADMIN_DENSITIES = new Set(["normal", "compact", "ultra"]);
const ADMIN_BUTTON_STYLES = new Set(["normal", "compact"]);
const ADMIN_CORNER_STYLES = new Set(["default", "square", "subtle", "rounded", "extra"]);
@@ -16,7 +16,7 @@ const ADMIN_APPEARANCE_DEFAULTS = Object.freeze({
chart_axes: false, interface_icons: false, logo_animation: false,
button_style: "normal", corner_style: "default", table_density: "normal", dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS], dashboard_widget_sizes: null,
});
const CHART_STYLE_KEYS = Object.freeze({ default: "chartStyleDefault", soft: "chartStyleSoft", technical: "chartStyleTechnical", high_contrast: "chartStyleHighContrast", neon: "chartStyleNeon", monochrome: "chartStyleMonochrome", warm: "chartStyleWarm", cool: "chartStyleCool" });
const CHART_STYLE_KEYS = Object.freeze({ default: "chartStyleDefault", soft: "chartStyleSoft", technical: "chartStyleTechnical", high_contrast: "chartStyleHighContrast", neon: "chartStyleNeon", monochrome: "chartStyleMonochrome", warm: "chartStyleWarm", cool: "chartStyleCool", sunset: "chartStyleSunset", aurora: "chartStyleAurora", ocean: "chartStyleOcean", forest: "chartStyleForest", pastel: "chartStylePastel", terminal: "chartStyleTerminal", blueprint: "chartStyleBlueprint", colorblind: "chartStyleColorblind" });
let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(), adminPreferencesUnavailable = false;
function normalizeAdminTheme(value) { return ADMIN_THEMES.has(value) ? value : "dark"; }
+33 -3
View File
@@ -282,6 +282,15 @@ function svgNode(tag, attrs = {}) {
function setChartText(node, value) {
const text = String(value);
// Controls decorated by the optional SVG icons hold [svg.ui-icon, text]; keep the icon
// and update only the text node so periodic refreshes do not rebuild the button.
const icon = node.querySelector(":scope > svg.ui-icon");
if (icon) {
const last = node.lastChild;
if (last?.nodeType === 3) { if (last.data !== text) last.data = text; }
else node.append(text);
return;
}
if (node.firstChild?.nodeType === 3 && node.firstChild === node.lastChild) {
if (node.firstChild.data !== text) node.firstChild.data = text;
} else if (node.textContent !== text) {
@@ -346,9 +355,12 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
setChartText(row.querySelector(".network-interface-hide"), t("hideMetric"));
row.querySelector(".network-rx-line").setAttribute("points", sparklinePoints(history.rx, 300, 60, 5, peak) || "0,55 300,55");
row.querySelector(".network-tx-line").setAttribute("points", sparklinePoints(history.tx, 300, 60, 5, peak) || "0,55 300,55");
window.rustProxyChartScale.axes(row.querySelector("svg"), peak, "bytes/s", history.rx.length,
// The Hide button precedes the chart and may carry an injected svg.ui-icon:
// address the chart explicitly so axes/labels never attach to the icon.
const chart = row.querySelector("svg:not(.ui-icon)");
window.rustProxyChartScale.axes(chart, peak, "bytes/s", history.rx.length,
{ top: 5 / 60, bottom: 55 / 60 });
row.querySelector("svg").setAttribute("aria-label", `${name} ${t("networkInterfacesHistory")}`);
chart.setAttribute("aria-label", `${name} ${t("networkInterfacesHistory")}`);
children.push(row);
}
const hidden = current.filter(item => item?.name && hiddenNetworkInterfaces.has(item.name));
@@ -395,15 +407,32 @@ function cacheOutcomeMix(totals) {
const counts = [hits, misses, bypasses, requests - cacheTotal];
return { counts, percentages: counts.map(value => value / requests * 100), requests };
}
// Percent labels live inside the bar segments; a label that does not fit is hidden
// (the legend below always carries the exact value).
function fitCacheMixLabels() {
const bar = $("cache-mix-bar");
if (!bar) return;
bar.querySelectorAll(":scope > span").forEach(segment => {
segment.classList.remove("is-tight");
segment.classList.toggle("is-tight", segment.textContent !== "" && segment.scrollWidth > segment.clientWidth);
});
if (!bar.dataset.fitObserved && typeof ResizeObserver === "function") {
bar.dataset.fitObserved = "1";
new ResizeObserver(() => fitCacheMixLabels()).observe(bar);
}
}
function renderCacheMix(mix, totalRequests) {
const names = ["hit", "miss", "bypass", "other"];
const unavailable = t(totalRequests === 0 ? "cacheMixWaiting" : "cacheMixInvalid");
names.forEach((name, i) => {
const pct = mix ? mix.percentages[i] : 0;
$(`cache-mix-${name}`).style.width = `${pct}%`;
const segment = $(`cache-mix-${name}`);
segment.style.width = `${pct}%`;
segment.textContent = mix && pct > 0 ? `${fmt(pct, pct < 10 ? 1 : 0)}%` : "";
$(`cache-mix-${name}-count`).textContent = mix ? fmt(mix.counts[i]) : "-";
$(`cache-mix-${name}-percent`).textContent = mix ? `${fmt(pct, 1)}%` : "-";
});
fitCacheMixLabels();
$("cache-mix-headline").textContent = mix ? `${fmt(mix.percentages[0], 1)}% HIT` : "-";
const caption = $("cache-mix-caption");
const validTotal = Number.isSafeInteger(totalRequests) && totalRequests >= 0;
@@ -424,6 +453,7 @@ function renderCacheMix(mix, totalRequests) {
function renderDiagnostics(status, appendSample = true, systemSample = appendSample) {
const d = status.system || {}, live = status.live_system || d, c = status.cache || {}, m = status.metrics || {};
$("metric-h3").textContent = fmt(m.http3_requests);
$("metric-h3-active").textContent = fmt(m.http3_active_connections || 0);
const cacheHits = c.hits == null ? null : Number(c.hits), cacheMisses = c.misses == null ? null : Number(c.misses);
const cacheEvents = cacheHits !== null && cacheMisses !== null ? cacheHits + cacheMisses : 0;
$("metric-cache-ratio").textContent = cacheEvents > 0 ? `${fmt(cacheHits / cacheEvents * 100, 1)}%` : "-";
+6 -1
View File
@@ -162,7 +162,12 @@
$("cp-expired").textContent = `${number(m.evictions)} / ${number(m.expired)}`;
$("cp-rejected").textContent = `${t("cacheRejected")}: ${number(m.rejected_fills)} · ${t("cacheMemoryPressureShort")}: ${number(m.memory_pressure_rejections)} · ${t("cacheNukeLimitShort")}: ${number(m.hot_eviction_limited)}`;
$("cache-domain-rows").replaceChildren(...shownGroups.map(row => {
const tr = el("tr"); for (const value of [row.vhost || t("cacheLegacy"), row.domain, number(row.hot_entries), number(row.cold_entries), fmtBytes(row.resident_bytes), fmtBytes(row.cold_bytes), `${number(row.hits)} / ${number(row.misses)} / ${number(row.bypasses)}`, `${fmt((row.hit_ratio || 0) * 100, 1)}%`]) tr.append(el("td", value));
const tr = el("tr"); for (const value of [row.vhost || t("cacheLegacy"), row.domain, number(row.hot_entries), number(row.cold_entries), fmtBytes(row.resident_bytes), fmtBytes(row.cold_bytes)]) tr.append(el("td", value));
// HIT / MISS / BYPASS: coloured counts that match the dashboard cache bar, plus a hit-ratio meter.
const outcomes = el("td", undefined, "cache-outcomes-cell");
[[row.hits, "cache-outcome-hit"], [row.misses, "cache-outcome-miss"], [row.bypasses, "cache-outcome-bypass"]].forEach(([count, cls], index) => { if (index) outcomes.append(el("span", "/", "cache-outcome-sep")); outcomes.append(el("span", number(count), cls)); });
const ratioPercent = Math.max(0, Math.min(100, (row.hit_ratio || 0) * 100)), ratio = el("td", undefined, "cache-ratio-cell"), meter = el("span", undefined, "cache-ratio-meter"), fill = el("i"); fill.style.width = `${ratioPercent}%`; meter.append(fill); ratio.append(el("span", `${fmt(ratioPercent, 1)}%`, "cache-ratio-value"), meter);
tr.append(outcomes, ratio);
const td = el("td", undefined, "cache-reasons"); for (const [key, count] of Object.entries(row.reasons || {}).sort((a, b) => b[1] - a[1])) { const tag = el("span", `${reason(key)}: ${number(count)}`, "cache-reason"); tag.title = key; td.append(tag); } tr.append(td); return tr;
}));
const domainEmpty = $("cache-domains-empty");
+208 -14
View File
@@ -257,20 +257,6 @@ html[data-theme=cream] {
--scrollbar-thumb-hover: #978a76;
}
body[data-density=dense] {
--ui-gap: 4px;
--ui-card: 7px;
--ui-font: 11.5px;
--ui-control: 25px;
}
body[data-density=comfortable] {
--ui-gap: 13px;
--ui-card: 17px;
--ui-font: 14px;
--ui-control: 36px;
}
html[data-button-style=compact] body {
--button-padding: 3px 7px;
--button-font-size: 10px;
@@ -333,6 +319,62 @@ html[data-button-style=compact] body {
--sw4: #70d7ff;
}
.chart-style-choice[data-chart-preset=sunset] {
--sw1: #ff7a59;
--sw2: #ffb347;
--sw3: #e8509b;
--sw4: #9b6bff;
}
.chart-style-choice[data-chart-preset=aurora] {
--sw1: #4ff0b0;
--sw2: #6ec8ff;
--sw3: #b78cff;
--sw4: #ff7ad9;
}
.chart-style-choice[data-chart-preset=ocean] {
--sw1: #3fa7ff;
--sw2: #2fd0c0;
--sw3: #6f8cff;
--sw4: #9be7ff;
}
.chart-style-choice[data-chart-preset=forest] {
--sw1: #7bc96f;
--sw2: #d6c45a;
--sw3: #4fae9a;
--sw4: #c98f5c;
}
.chart-style-choice[data-chart-preset=pastel] {
--sw1: #f4b6c2;
--sw2: #b5e0c2;
--sw3: #b4c8f5;
--sw4: #e3c8f0;
}
.chart-style-choice[data-chart-preset=terminal] {
--sw1: #6bff7a;
--sw2: #e5ff6b;
--sw3: #4fd0a0;
--sw4: #ffb347;
}
.chart-style-choice[data-chart-preset=blueprint] {
--sw1: #cfe6ff;
--sw2: #8fc4ff;
--sw3: #ffffff;
--sw4: #7aa8e0;
}
.chart-style-choice[data-chart-preset=colorblind] {
--sw1: #e69f00;
--sw2: #56b4e9;
--sw3: #009e73;
--sw4: #cc79a7;
}
html[data-chart-style=default] {
--chart-accent: var(--accent);
--chart-secondary: var(--green);
@@ -485,6 +527,158 @@ html[data-chart-style=warm] {
--chart-svg-filter: saturate(1.1) brightness(1.04);
}
html[data-chart-style=sunset] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #ff7a59;
--chart-secondary: #ffb347;
--chart-tertiary: #e8509b;
--chart-quaternary: #9b6bff;
--chart-quinary: #ffd479;
--chart-senary: #ff9fb2;
--chart-septenary: #c08ad8;
--chart-octonary: #8f6aa8;
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #3a1428 10%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #2e1022 8%);
--chart-line-width: 2.5;
--chart-grid-opacity: .36;
--chart-grid-dash: 1 0;
--chart-svg-filter: saturate(1.12) brightness(1.04);
}
html[data-chart-style=aurora] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #4ff0b0;
--chart-secondary: #6ec8ff;
--chart-tertiary: #b78cff;
--chart-quaternary: #ff7ad9;
--chart-quinary: #c8ff7a;
--chart-senary: #7affea;
--chart-septenary: #8fa0ff;
--chart-octonary: #d0a8ff;
--chart-surface: color-mix(in srgb, var(--panel2) 91%, #0a2630 9%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #082028 8%);
--chart-line-width: 2.4;
--chart-grid-opacity: .4;
--chart-grid-dash: 2 5;
--chart-svg-filter: saturate(1.2) brightness(1.08);
}
html[data-chart-style=ocean] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #3fa7ff;
--chart-secondary: #2fd0c0;
--chart-tertiary: #6f8cff;
--chart-quaternary: #9be7ff;
--chart-quinary: #52e0a8;
--chart-senary: #78b8ff;
--chart-septenary: #4a7fb5;
--chart-octonary: #5f9ea8;
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #061c33 10%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #05182b 8%);
--chart-line-width: 2.2;
--chart-grid-opacity: .4;
--chart-grid-dash: 2 5;
--chart-svg-filter: saturate(1.05);
}
html[data-chart-style=forest] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #7bc96f;
--chart-secondary: #d6c45a;
--chart-tertiary: #4fae9a;
--chart-quaternary: #c98f5c;
--chart-quinary: #a4d86a;
--chart-senary: #e0a84f;
--chart-septenary: #7aa86a;
--chart-octonary: #8c7a5a;
--chart-surface: color-mix(in srgb, var(--panel2) 91%, #11230f 9%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #0e1d0d 8%);
--chart-line-width: 2.3;
--chart-grid-opacity: .34;
--chart-grid-dash: 1 0;
--chart-svg-filter: saturate(.96);
}
html[data-chart-style=pastel] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #f4b6c2;
--chart-secondary: #b5e0c2;
--chart-tertiary: #b4c8f5;
--chart-quaternary: #e3c8f0;
--chart-quinary: #f6dba4;
--chart-senary: #a8dcdc;
--chart-septenary: #d2b8e8;
--chart-octonary: #b9c2cc;
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #fff 10%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 93%, #fff 7%);
--chart-line-width: 3;
--chart-grid-opacity: .44;
--chart-grid-dash: 1 0;
--chart-svg-filter: saturate(.82) brightness(1.08);
}
html[data-chart-style=terminal] {
--chart-linecap: square;
--chart-linejoin: miter;
--chart-accent: #6bff7a;
--chart-secondary: #e5ff6b;
--chart-tertiary: #4fd0a0;
--chart-quaternary: #ffb347;
--chart-quinary: #a6ff9a;
--chart-senary: #d4ff9a;
--chart-septenary: #58b868;
--chart-octonary: #7a8f58;
--chart-surface: color-mix(in srgb, var(--panel2) 88%, #031a07 12%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #021405 10%);
--chart-line-width: 1.6;
--chart-grid-opacity: .7;
--chart-grid-dash: 1 3;
--chart-svg-filter: saturate(1.1) contrast(1.1);
}
html[data-chart-style=blueprint] {
--chart-linecap: square;
--chart-linejoin: miter;
--chart-accent: #cfe6ff;
--chart-secondary: #8fc4ff;
--chart-tertiary: #ffffff;
--chart-quaternary: #7aa8e0;
--chart-quinary: #a8d0ff;
--chart-senary: #e8f2ff;
--chart-septenary: #6a90c0;
--chart-octonary: #506c94;
--chart-surface: color-mix(in srgb, var(--panel2) 84%, #0a2a5c 16%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 88%, #08224a 12%);
--chart-line-width: 1.3;
--chart-grid-opacity: .78;
--chart-grid-dash: 4 4;
--chart-svg-filter: saturate(.9) contrast(1.06);
}
html[data-chart-style=colorblind] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #e69f00;
--chart-secondary: #56b4e9;
--chart-tertiary: #009e73;
--chart-quaternary: #cc79a7;
--chart-quinary: #f0e442;
--chart-senary: #0072b2;
--chart-septenary: #d55e00;
--chart-octonary: #bdbdbd;
--chart-surface: color-mix(in srgb, var(--panel2) 94%, #000 6%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 94%, #000 6%);
--chart-line-width: 2.6;
--chart-grid-opacity: .7;
--chart-grid-dash: 3 4;
--chart-svg-filter: contrast(1.1);
}
html[data-chart-style] {
--traffic-h1: var(--chart-accent);
--traffic-h2: var(--chart-secondary);
+35 -43
View File
@@ -1067,43 +1067,10 @@ dialog .vh-form,
align-items: start;
}
body[data-density=compact] {
--ui-gap: 2px;
--ui-card: 5px;
--ui-font: 10.75px;
--ui-control: 22px;
line-height: 1.32;
}
body[data-density=compact] main {
padding: 0 10px;
}
body[data-density=compact] .card-heading {
margin-bottom: 5px;
}
body[data-density=compact] .card-heading h2,
html[data-dashboard-density=compact] #panel-overview .dashboard-overview-grid>.card .card-heading h2 {
font-size: 14px;
}
body[data-density=compact] .outline,
body[data-density=compact] .primary,
body[data-density=compact] .quiet,
body[data-density=compact] button,
body[data-density=compact] input:not([type=checkbox]):not([type=radio]),
body[data-density=compact] select,
body[data-density=compact] textarea {
padding: 3px 7px;
font-size: 10px;
}
body[data-density=compact] .page-footer {
padding: 7px 0;
margin-top: 4px;
}
.network-interface-row-head {
display: flex;
justify-content: space-between;
@@ -1151,7 +1118,7 @@ body[data-density=compact] .page-footer {
font: 10px var(--mono);
}
.network-interface-row svg {
.network-interface-row svg:not(.ui-icon) {
display: block;
width: 100%;
height: 60px;
@@ -1251,6 +1218,29 @@ body[data-density=compact] .page-footer {
min-height: 24px;
padding: 3px 7px;
font: 9px var(--mono);
display: inline-flex;
align-items: center;
white-space: nowrap;
}
/* Small controls: a smaller icon must not change the button height when the
optional SVG icons are toggled or re-applied during a refresh (Safari jumped). */
.network-interface-hide>.ui-icon,
.network-interface-hidden .quiet>.ui-icon {
width: 11px;
height: 11px;
margin-inline-end: 4px;
vertical-align: 0;
}
html[data-interface-icons="true"] .network-interface-hide>svg.ui-icon,
html[data-interface-icons="true"] .network-interface-hidden .quiet>svg.ui-icon {
width: 11px;
height: 11px;
}
.network-interface-charts {
overflow-anchor: none;
}
.network-interface-row+.network-interface-row,
@@ -1570,14 +1560,6 @@ html[data-table-density=ultra] :is(table th, table td) {
min-width: 0;
}
.path-permissions-commands {
max-height: 180px;
}
.path-permissions-actions {
margin-top: 0;
}
.path-permissions-options>summary {
cursor: pointer;
}
@@ -1599,6 +1581,7 @@ html[data-table-density=ultra] :is(table th, table td) {
}
.path-permissions-commands {
max-height: 180px;
margin-bottom: 0;
}
@@ -1608,4 +1591,13 @@ html[data-table-density=ultra] :is(table th, table td) {
.path-permissions-actions {
margin-top: 10px;
}
}
/* Safety net: whatever chart/card rule is added later, injected UI icons stay inline,
fixed-size and out of layout flow (they used to stretch inside chart cards on mobile Safari). */
html[data-interface-icons="true"] :is(button, a, summary)>svg.ui-icon {
display: inline-block;
position: static;
flex: 0 0 auto;
max-width: none;
overflow: visible;
}
-31
View File
@@ -604,37 +604,6 @@ main {
padding-top: 4px;
}
body[data-density=compact] .shell {
grid-template-columns: 164px minmax(0, 1fr);
}
body[data-density=compact] .sidebar {
padding: 14px 9px;
}
body[data-density=compact] .nav,
body[data-density=compact] .stat {
padding: 6px 7px;
}
body[data-density=compact] .topbar {
height: 42px;
}
body[data-density=compact] .page-intro {
padding: 7px 0 6px;
gap: 7px;
}
body[data-density=compact] .page-intro h1 {
font-size: 20px;
}
body[data-density=compact] .page-intro .lead {
font-size: 10px;
margin-top: 2px;
}
.top-log-menu>summary,
.backend-column-picker>summary,
.file-log-search-help>summary {
+84 -46
View File
@@ -280,7 +280,7 @@ html[data-theme=cream] :is(.card, .stat, dialog) {
text-align: right;
}
.mini-live-chart svg {
.mini-live-chart svg:not(.ui-icon) {
display: block;
width: 100%;
height: 60px;
@@ -361,11 +361,11 @@ html[data-theme=cream] :is(.card, .stat, dialog) {
font-size: 10px;
}
.activity-card svg {
.activity-card svg:not(.ui-icon) {
height: 105px;
}
.system-card svg {
.system-card svg:not(.ui-icon) {
height: 80px;
}
@@ -383,11 +383,6 @@ html[data-theme=cream] :is(.card, .stat, dialog) {
max-height: 205px;
}
body[data-density=dense] .stat strong {
font-size: 22px;
margin: 2px 0;
}
.traffic-chart-grid,
.logging-grid {
display: grid;
@@ -468,7 +463,7 @@ body[data-density=dense] .stat strong {
padding: 7px 10px 0;
}
.vhost-chart svg {
.vhost-chart svg:not(.ui-icon) {
display: block;
width: 100%;
height: 54px;
@@ -550,37 +545,12 @@ body[data-density=dense] .stat strong {
font-weight: 600;
}
body[data-density=compact] .stat strong {
font-size: 18px;
margin: 1px 0;
}
body[data-density=compact] .stat p,
body[data-density=compact] .stat small,
html[data-dashboard-density=normal] #panel-overview .stat small,
html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-header>span,
html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-caption {
font-size: 9px;
}
body[data-density=compact] .activity-card svg {
height: 84px;
}
body[data-density=compact] .system-card svg {
height: 66px;
}
body[data-density=compact] .pool-table td,
body[data-density=compact] .pool-table th {
padding: 3px 5px;
font-size: 10px;
}
body[data-density=compact] .pool-card .table-scroll {
max-height: 170px;
}
.network-interface-charts {
display: grid;
max-height: 260px;
@@ -664,7 +634,7 @@ body[data-density=compact] .pool-card .table-scroll {
margin-bottom: 3px;
}
.stat-chart svg {
.stat-chart svg:not(.ui-icon) {
display: block;
width: 100%;
height: 26px;
@@ -769,7 +739,7 @@ body[data-density=compact] .pool-card .table-scroll {
padding: 14px 15px 12px;
}
.function-live-chart svg {
.function-live-chart svg:not(.ui-icon) {
display: block;
width: 100%;
height: 180px;
@@ -851,7 +821,7 @@ body[data-density=compact] .pool-card .table-scroll {
font-weight: 400;
}
.connection-card svg {
.connection-card svg:not(.ui-icon) {
display: block;
width: 100%;
height: 100px;
@@ -1052,7 +1022,7 @@ body[data-density=compact] .pool-card .table-scroll {
border-radius: 0;
}
:is(.stat-chart, .activity-card, .system-card, .function-live-chart) svg,
:is(.stat-chart, .activity-card, .system-card, .function-live-chart) svg:not(.ui-icon),
:is(.traffic-donut, .traffic-bar-chart span) {
filter: var(--chart-svg-filter);
}
@@ -1127,15 +1097,15 @@ html[data-dashboard-density=ultra] #panel-overview .stat small {
line-height: 1.12;
}
html[data-dashboard-density=ultra] #panel-overview .stat-chart svg {
html[data-dashboard-density=ultra] #panel-overview .stat-chart svg:not(.ui-icon) {
height: 24px;
}
html[data-dashboard-density=normal] #panel-overview .stat-chart svg {
html[data-dashboard-density=normal] #panel-overview .stat-chart svg:not(.ui-icon) {
height: 34px;
}
html[data-dashboard-density=compact] #panel-overview .stat-chart svg {
html[data-dashboard-density=compact] #panel-overview .stat-chart svg:not(.ui-icon) {
height: 28px;
}
@@ -1737,7 +1707,7 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
border-radius: var(--rp-radius-surface) !important;
}
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(button:not(.nav):not(.file-log-tab):not(.vhost-backend-trigger), input:not([type=checkbox]):not([type=radio]):not([type=range]), select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .badge, .quiet, .outline, .primary, .code, .command, .dashboard-widget-toggle, .settings-tabs button, .overview-inline-restore),
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(button:not(.nav):not(.file-log-tab):not(.vhost-backend-trigger), input:not([type=checkbox]):not([type=radio]):not([type=range]), select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .quiet, .outline, .primary, .code, .command, .dashboard-widget-toggle, .settings-tabs button, .overview-inline-restore),
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(.nav, .file-log-tab, .vhost-backend-trigger, .backend-column-options, .docker-service-quick-actions a, .docker-service-quick-actions button, .docker-view-toggle, .provider-picker, .renewal-meta-item) {
border-radius: var(--rp-radius-control) !important;
}
@@ -2074,7 +2044,7 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
gap: 8px;
padding: 9px;
border: 1px solid var(--line);
border-radius: var(--rp-radius-surface, var(--radius, 8px));
border-radius: var(--rp-radius-surface, 8px);
background: var(--panel);
}
@@ -2213,8 +2183,8 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
#panel-overview .stat-cache-outcomes .cache-mix-bar {
display: flex;
width: 100%;
height: 12px;
margin: 7px 0 9px;
height: 20px;
margin: 3px 0 4px;
border-radius: var(--rp-progress-radius);
background: var(--rp-progress-track);
overflow: hidden;
@@ -2224,6 +2194,17 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
display: block;
height: 100%;
min-width: 0;
padding: 0 2px;
overflow: hidden;
color: #0c0e11;
font: 600 10px/20px var(--mono, ui-monospace, monospace);
text-align: center;
white-space: nowrap;
user-select: none;
}
#panel-overview .cache-mix-bar>span.is-tight {
color: transparent;
}
#panel-overview .cache-mix-other {
@@ -2322,4 +2303,61 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
/* WebSocket tunnels: "Established" sits to the right of the main value instead of a third text line. */
#panel-overview .stat .stat-value-row {
display: flex;
align-items: baseline;
flex-wrap: wrap;
justify-content: space-between;
column-gap: 8px;
min-width: 0;
}
#panel-overview .stat .stat-value-row>strong {
flex: 0 0 auto;
}
#panel-overview .stat .stat-value-row>.stat-side-value {
display: block;
margin: 0 0 0 auto;
color: var(--muted);
font-size: 10px;
line-height: 1.3;
text-align: right;
white-space: nowrap;
}
#panel-overview .stat .stat-value-row>.stat-side-value b {
color: var(--text);
font-family: var(--mono);
font-size: 13px;
font-weight: 450;
}
/* Compact stat cards: a card made only of title, value and one secondary line shows that line
beside the value (right-aligned) instead of on a line of its own. */
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small)) {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
column-gap: 10px;
align-items: baseline;
align-content: start;
}
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small))>span:first-child {
grid-column: 1 / -1;
}
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small))>strong {
grid-column: 1;
min-width: 0;
}
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small))>small {
grid-column: 2;
max-width: 14em;
margin: 0;
text-align: right;
overflow-wrap: anywhere;
}
+97 -18
View File
@@ -29,7 +29,7 @@
.traffic-chart-card,
#panel-overview .backend-mode-bar span,
#panel-overview .stats-grid>.stat,
#panel-overview .dashboard-overview-grid>.activity-card svg,
#panel-overview .dashboard-overview-grid>.activity-card svg:not(.ui-icon),
#panel-overview .dashboard-overview-grid>[data-dashboard-widget],
.dashboard-density-setting>.fine,
.dashboard-density-setting>select,
@@ -1140,7 +1140,7 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 11px;
background: var(--surface, transparent);
background: transparent;
}
.vh-backend-health-editor h3 {
@@ -1483,18 +1483,22 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
grid-row: 1;
}
/* Same geometry and typography as .pill so protocol indicators sit evenly in the
Vhost policy row; only the colour (muted = not observed, green = observed) differs. */
.vhost-backend-protocol-badge {
display: inline-flex;
gap: 4px;
align-items: baseline;
align-items: center;
max-width: 100%;
min-width: 0;
border: 1px solid var(--pill-line);
border-radius: var(--rp-radius-control, 4px);
background: var(--panel2);
border-radius: var(--rp-radius-control, 0);
background: transparent;
color: var(--muted);
padding: 3px 7px;
font: 10px var(--mono);
min-height: 0;
padding: 4px 8px;
font: 9px/10px var(--mono);
letter-spacing: 1px;
overflow-wrap: anywhere;
text-align: left;
}
@@ -1504,6 +1508,10 @@ html.app-loading,
background: var(--bg);
}
.vhost-policy-pills>.vhost-backend-protocol-badge {
align-self: center;
}
.vhost-backend-protocol-badge[data-observed=true] {
color: var(--green);
border-color: var(--pill-live-line);
@@ -1788,12 +1796,12 @@ html.app-loading,
display: inline-flex;
align-items: center;
max-width: 100%;
gap: 7px;
padding: 2px 4px 2px 9px;
border-radius: var(--rp-radius-control, 15px);
gap: 5px;
padding: 4px 5px 4px 8px;
border-radius: var(--rp-radius-control, 0);
border: 1px solid var(--pill-live-line);
background: var(--nav-selected-bg);
font-size: 11px;
background: var(--panel);
font: 10px var(--mono);
}
.acl-picker-tag>span {
@@ -1803,13 +1811,12 @@ html.app-loading,
.acl-picker-tag .acl-picker-remove {
border: none;
border-radius: var(--rp-radius-control, 50%);
border-radius: var(--rp-radius-control, 0);
background: transparent;
color: var(--muted);
min-width: 20px;
height: 20px;
font-size: 16px;
padding: 0;
font-size: 15px;
line-height: 1;
padding: 0 3px;
cursor: pointer;
}
@@ -1940,4 +1947,76 @@ html.app-loading,
.web-service-preset-toggles>.check {
margin: 0 !important;
min-width: 0;
}
}
/* Spacing review (0.36.2): descriptive text and blocks that used to touch the next
element. Rules only add space where the layout had none; grid/flex gaps are untouched. */
.fine+:is(.switch-grid, .settings-tabs, .table-scroll, .soft-card, .appearance-settings-grid, .runtime-information-grid, details),
.form-actions+.fine,
.table-scroll+:is(.empty, .fine),
.empty+.file-logs-pager {
margin-top: 10px;
}
:is(.card-heading, .window-heading)+.fine,
h2+.fine {
margin-top: 6px;
}
.location-help-content :is(p, .fine)+:is(p, .fine) {
margin-top: 8px;
}
.location-help-content :is(p, .fine, ul, ol, table, pre)+:is(h3, h4) {
margin-top: 16px;
}
.cache-toolbar+.function-live-chart,
.function-live-chart+.stats-grid,
.soft-card+.soft-card,
.field-grid+.field-grid {
margin-top: 12px;
}
.function-live-chart-summary+.function-live-chart-body {
margin-top: 8px;
}
.tls-pqc-control>:is(.fine, a) {
margin-block: 0;
}
.tls-pqc-control {
display: grid;
gap: 6px;
margin-top: 6px;
}
.tls-pqc-control>a {
justify-self: start;
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
font-size: 12px;
}
#settings-page-tls-policy .switch-grid {
margin-bottom: 12px;
}
#panel-settings .fine+.settings-tabs.sub-tabs,
.fine+.field-grid,
.vh-grid+.fine,
.logs-card+.log-footnote,
.overview-presets-content+.overview-layout-footer {
margin-top: 10px;
}
#settings-page-policies>.edit-form+div {
margin-top: 12px;
}
@media (min-width: 761px) {
#panel-settings .settings-tabs.sub-tabs+:is(div, form) {
margin-top: 12px;
}
}
+221 -36
View File
@@ -130,7 +130,6 @@
align-items: center;
}
body[data-density=compact] .sidebar nav,
.certificate-coverage {
margin-top: 14px;
}
@@ -688,40 +687,6 @@ body[data-density=compact] .sidebar nav,
margin-bottom: 12px;
}
body[data-density=compact] .docker-service-row {
padding: 6px 8px;
gap: 4px;
}
body[data-density=compact] .docker-service-head,
body[data-density=compact] .security-stack {
gap: 7px;
}
body[data-density=compact] .docker-agent-overview {
gap: 4px 12px;
margin: 4px 0 6px;
font-size: 10px;
}
body[data-density=compact] .docker-agent-details {
margin-bottom: 6px;
padding-top: 5px;
}
body[data-density=compact] .docker-service-editor {
padding-top: 5px;
}
body[data-density=compact] .docker-service-editor[open]>summary {
margin-bottom: 7px;
}
body[data-density=compact] .security-editor {
margin-top: 8px;
padding-top: 6px;
}
.docker-agent-grid>label {
display: grid;
align-content: start;
@@ -1294,4 +1259,224 @@ html[data-dashboard-density=normal] #panel-overview .stat>span,
.global-rate-fields {
grid-template-columns: 1fr;
}
}
}
/* Settings rows (0.36.3): label and help on the left, control on the right, one setting per row. */
#settings-page-appearance .appearance-section-heading .small-label {
display: none;
}
#settings-page-appearance .appearance-section-heading h3 {
margin: 0;
font-size: 14px;
}
#settings-page-appearance .appearance-settings-grid {
grid-template-columns: minmax(0, 1fr);
gap: 0;
}
#settings-page-appearance .appearance-settings-grid>label {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(170px, 260px);
grid-template-areas: "name control" "help control";
align-content: center;
align-items: center;
column-gap: 24px;
row-gap: 3px;
margin: 0;
padding: 11px 0;
border-top: 1px solid var(--line);
}
#settings-page-appearance .appearance-settings-grid>label:first-child {
border-top: 0;
padding-top: 0;
}
#settings-page-appearance .appearance-settings-grid>label>span:first-child {
grid-area: name;
align-self: end;
font-size: 12px;
font-weight: 600;
color: var(--text);
}
#settings-page-appearance .appearance-settings-grid>label>:is(select, input[type=range]) {
grid-area: control;
width: 100%;
min-width: 0;
}
#settings-page-appearance .appearance-settings-grid>label>small {
grid-area: help;
align-self: start;
margin: 0;
color: var(--muted);
font-size: 11px;
line-height: 1.45;
}
#settings-page-appearance .appearance-settings-grid>label:not(:has(>small), .check) {
grid-template-areas: "name control";
}
#settings-page-appearance .appearance-settings-grid>label:not(:has(>small), .check)>span:first-child {
align-self: center;
}
#settings-page-appearance .refresh-settings-grid>label.check>span,
#settings-page-appearance .dashboard-widget-settings-intro>span {
color: var(--text);
font-size: 12px;
font-weight: 600;
}
#settings-page-appearance .appearance-scale-control>span:first-child {
justify-content: flex-start;
}
/* Checkbox settings: text on the left, the checkbox on the right edge of the row
(.check is display:flex !important, so the row is reversed instead of using grid areas). */
#settings-page-appearance .appearance-settings-grid>label.check {
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
gap: 24px;
align-self: auto;
}
#settings-page-appearance .appearance-settings-grid>label.check>input {
flex: 0 0 auto;
margin: 0;
}
#settings-page-appearance .appearance-settings-grid>label.check>span {
grid-area: text;
display: grid;
gap: 3px;
flex: 1 1 auto;
min-width: 0;
align-self: center;
font-size: 12px;
font-weight: 400;
}
#settings-page-appearance .appearance-settings-grid>label.check>span b {
font-weight: 600;
color: var(--text);
}
#settings-page-appearance .appearance-settings-grid>label.check>span small {
color: var(--muted);
font-size: 11px;
line-height: 1.45;
}
#settings-page-appearance .appearance-chart-preview {
margin-top: 12px;
}
#settings-page-appearance .dashboard-appearance-grid>.dashboard-widget-settings {
margin-top: 4px;
padding-top: 12px;
border-top: 1px solid var(--line);
}
#settings-page-appearance .refresh-settings-grid>label.check {
grid-column: auto;
}
@media (max-width: 760px) {
#settings-page-appearance .appearance-settings-grid>label {
grid-template-columns: minmax(0, 1fr);
grid-template-areas: "name" "help" "control";
row-gap: 5px;
}
#settings-page-appearance .appearance-settings-grid>label>span:first-child {
align-self: start;
}
}
/* Settings navigation (0.36.3) */
@media (min-width: 761px) {
/* Sub-tabs are a horizontal tab bar; the vertical rail layout is for the main settings menu only. */
#panel-settings .settings-tabs.sub-tabs {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 4px;
padding: 5px;
margin-bottom: 14px;
}
#panel-settings .settings-tabs.sub-tabs button {
width: auto;
min-width: 0;
padding: 7px 14px;
text-align: center;
white-space: nowrap;
}
/* Visual groups in the main menu: appearance / traffic / rules / observability / system / security. */
#panel-settings #settings-tabs button:is([data-settings-tab=listeners], [data-settings-tab=policies], [data-settings-tab=logging], [data-settings-tab=administration], [data-settings-tab=certificate-api]) {
margin-top: 7px;
position: relative;
}
#panel-settings #settings-tabs button:is([data-settings-tab=listeners], [data-settings-tab=policies], [data-settings-tab=logging], [data-settings-tab=administration], [data-settings-tab=certificate-api])::before {
content: "";
position: absolute;
top: -5px;
right: 8px;
left: 8px;
border-top: 1px solid var(--line);
}
}
/* Cache and static transformation pages (0.36.3): grouped sections, help attached to its switch. */
#cache-form .cache-section-title {
margin-top: 20px;
padding-top: 14px;
border-top: 1px solid var(--line);
font-size: 13px;
}
#cache-form [data-cache-settings-page=general]>.cache-section-title:first-child {
margin-top: 2px;
padding-top: 0;
border-top: 0;
}
:is(#cache-form, #transform-form)>.check:first-of-type,
#cache-form [data-cache-settings-page=general]>.check:first-of-type {
font-weight: 600;
}
:is(#cache-form, #transform-form) .check+.fine {
margin: 3px 0 12px 24px;
}
:is(#cache-form, #transform-form) .cache-memory-sizing .card-heading {
justify-content: flex-start;
gap: 8px;
}
:is(#cache-form, #transform-form) .cache-memory-sizing .card-heading>h3 {
margin-right: auto;
}
:is(#cache-form, #transform-form) .field-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 210px), 1fr));
column-gap: 14px;
}
#transform-form .switch-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr));
}
#cache-form #cache-default-ttl {
display: block;
max-width: 320px;
}
+81 -8
View File
@@ -430,13 +430,12 @@ html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.pool
display: inline-flex;
align-items: center;
margin-left: 6px;
padding: 1px 5px;
padding: 2px 6px;
border: 1px solid var(--pill-line);
border-radius: var(--rp-radius-control, 3px);
border-radius: var(--rp-radius-control, 0);
color: var(--accent);
font: 9px var(--mono);
text-transform: uppercase;
letter-spacing: .35px;
letter-spacing: 1px;
}
.cache-toolbar-message {
@@ -574,8 +573,8 @@ html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.pool
button.config-history-day.has-revisions {
cursor: pointer;
color: var(--text);
border-color: var(--line-strong, var(--line));
background: var(--surface, var(--bg));
border-color: var(--line);
background: var(--bg);
}
button.config-history-day.has-revisions:hover,
@@ -834,7 +833,6 @@ html[data-table-density=ultra] .file-log-inline-detail {
padding: 5px 7px;
}
body[data-density=compact] .mini-metrics div,
.acl-policy-mode-card input,
.docker-service-certificate-bar>.check>input,
html[data-table-density=ultra] .file-log-field-grid {
@@ -1006,7 +1004,7 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
.file-log-advanced-parser {
margin: 0 0 1rem;
padding: .75rem 1rem;
border-radius: var(--rp-radius-surface, var(--radius, 12px));
border-radius: var(--rp-radius-surface, 12px);
}
.file-log-aggregate-form {
@@ -1074,3 +1072,78 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
max-height: 68dvh;
font: 11px/1.5 var(--mono);
}
/* Cache → Domains and effectiveness (0.36.3): numeric columns aligned, outcomes coloured like the dashboard bar. */
#cache-domain-scroll .cache-table :is(th, td):nth-child(n+3):nth-child(-n+8) {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
#cache-domain-scroll .cache-table td:first-child {
color: var(--muted);
}
#cache-domain-scroll .cache-table td:nth-child(2) {
color: var(--text);
font-weight: 600;
}
#cache-domain-scroll .cache-table tbody tr:hover {
background: var(--panel2);
}
#cache-domain-scroll .cache-table td {
vertical-align: middle;
}
.cache-outcomes-cell {
letter-spacing: 0;
}
.cache-outcome-hit {
color: var(--chart-accent);
}
.cache-outcome-miss {
color: var(--chart-secondary);
}
.cache-outcome-bypass {
color: var(--chart-tertiary);
}
.cache-outcome-sep {
margin: 0 5px;
color: var(--muted);
opacity: .6;
}
.cache-ratio-cell {
min-width: 96px;
}
.cache-ratio-value {
display: block;
font-weight: 600;
}
.cache-ratio-meter {
display: block;
height: 3px;
margin-top: 4px;
overflow: hidden;
background: var(--line);
}
.cache-ratio-meter>i {
display: block;
height: 100%;
background: var(--chart-accent);
}
#cache-domain-scroll .cache-reason {
border-color: transparent;
background: var(--panel2);
color: var(--muted);
}
+50 -22
View File
@@ -302,18 +302,6 @@
max-width: calc(100vw - 12px);
}
body[data-density=compact] .sidebar nav {
margin-top: 0;
}
body[data-density=compact] .sidebar {
padding: 8px 9px;
}
body[data-density=compact] .topbar {
height: 42px;
}
.sidebar .brand {
min-width: 0;
}
@@ -1293,16 +1281,6 @@
align-self: flex-start;
}
body[data-density=compact] .docker-service-row {
padding: 8px;
gap: 5px;
}
body[data-density=compact] .docker-service-head-actions .docker-runtime .pill {
top: 8px;
right: 8px;
}
.backend-column-options {
width: min(330px, calc(100vw - 32px));
min-width: 0;
@@ -1703,3 +1681,53 @@
--overview-widget-placement: 1/-1;
}
}
@media (max-width: 760px) {
#panel-overview .system-card .card-heading {
flex-wrap: wrap;
gap: 4px 12px;
}
#panel-overview .system-card .system-uptime-meta {
white-space: normal;
max-width: 100%;
margin-left: auto;
text-align: right;
}
}
/* Docker vhosts (0.36.3): compact service rows - switch | name and details | health and actions. */
@media (min-width:900px) {
.docker-service-head {
grid-template-columns: auto minmax(220px, 1fr) auto;
align-items: center;
gap: 6px 18px;
}
.docker-service-head-actions {
grid-column: auto;
justify-content: flex-end;
gap: 6px 10px;
}
.docker-service-head-actions .docker-runtime {
flex: 1 0 100%;
justify-content: flex-end;
}
.docker-service-meta {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1px 14px;
}
.docker-service-meta>b {
flex: 0 0 100%;
font-size: 13px;
}
.docker-service-meta>span {
font-size: 11px;
}
}
+23 -11
View File
@@ -1821,7 +1821,7 @@
<section class="appearance-section">
<div class="appearance-section-heading">
<div><span class="small-label" data-i18n="appearanceChartsSection">CHARTS</span>
<h3 data-i18n="chartStyle">Chart style</h3>
<h3 data-i18n="appearanceChartsTitle">Charts</h3>
</div><button class="quiet appearance-section-reset"
data-appearance-reset="charts" data-i18n="resetSection" type="button">Reset
section</button>
@@ -1840,6 +1840,14 @@
</option>
<option data-i18n="chartStyleWarm" value="warm">Warm</option>
<option data-i18n="chartStyleCool" value="cool">Cool</option>
<option data-i18n="chartStyleSunset" value="sunset">Sunset</option>
<option data-i18n="chartStyleAurora" value="aurora">Aurora</option>
<option data-i18n="chartStyleOcean" value="ocean">Ocean</option>
<option data-i18n="chartStyleForest" value="forest">Forest</option>
<option data-i18n="chartStylePastel" value="pastel">Pastel</option>
<option data-i18n="chartStyleTerminal" value="terminal">Terminal</option>
<option data-i18n="chartStyleBlueprint" value="blueprint">Blueprint</option>
<option data-i18n="chartStyleColorblind" value="colorblind">Colorblind safe</option>
</select></label>
<label class="check appearance-chart-axes-setting"><input id="ui-chart-axes"
type="checkbox" /><span><b data-i18n="chartAxes">Show X/Y scales on
@@ -1864,7 +1872,7 @@
<div class="appearance-section-heading">
<div><span class="small-label" data-i18n="appearanceDataSection">TABLES /
LOGS</span>
<h3 data-i18n="tableDensity">Tables and logs density</h3>
<h3 data-i18n="appearanceTablesTitle">Tables and logs</h3>
</div><button class="quiet appearance-section-reset"
data-appearance-reset="tables" data-i18n="resetSection" type="button">Reset
section</button>
@@ -2384,6 +2392,7 @@
data-i18n="cacheTabDiagnostics" type="button">Static & diagnostics</button>
</nav>
<div data-cache-settings-page="general">
<h3 class="subheading cache-section-title" data-i18n="cacheSectionBehavior">Behavior</h3>
<label class="check"><input id="cache-enabled" type="checkbox" /><span
data-i18n="enableCache">Enable shared cache</span></label>
<p class="fine" data-i18n="cacheSafety">Only complete public-safe GET responses.
@@ -2429,7 +2438,17 @@
<input id="cache-varnish-grace" type="number" min="0" max="604800" step="1"
required />
</div>
<div class="card soft-card cache-memory-sizing">
<label data-i18n="cacheDefaultTtl" for="cache-default-ttl">Fallback TTL for public
responses (0 = off)</label><input id="cache-default-ttl" max="604800" min="0"
required="" type="number" />
<label class="check"><input id="cache-query" type="checkbox" /><span
data-i18n="bypassQuery">Bypass URLs with a query string</span></label>
<label data-i18n="cachePrefixes" for="cache-prefixes">Bypass path prefixes, one per
line</label><textarea id="cache-prefixes" placeholder="/api/private" rows="3"
spellcheck="false"></textarea>
<h3 class="subheading cache-section-title" data-i18n="cacheSectionMemory">Memory and limits</h3>
<div class="card soft-card cache-memory-sizing">
<div class="card-heading">
<h3 data-i18n="cacheMemorySizingTitle">Memory sizing</h3>
<span class="pill" id="cache-memory-preset-selected">-</span>
@@ -2483,14 +2502,7 @@
<p class="fine" data-i18n="cacheAutoTrimHelp">When HOT memory is above the target,
only entries idle for the configured age are removed in bounded passes. Active
objects are not purged.</p>
<label data-i18n="cacheDefaultTtl" for="cache-default-ttl">Fallback TTL for public
responses (0 = off)</label><input id="cache-default-ttl" max="604800" min="0"
required="" type="number" />
<label class="check"><input id="cache-query" type="checkbox" /><span
data-i18n="bypassQuery">Bypass URLs with a query string</span></label>
<label data-i18n="cachePrefixes" for="cache-prefixes">Bypass path prefixes, one per
line</label><textarea id="cache-prefixes" placeholder="/api/private" rows="3"
spellcheck="false"></textarea>
</div>
<div data-cache-settings-page="resilience" hidden>
<h3 class="subheading" data-i18n="cacheCollapseTitle">Request collapsing</h3>
+10 -9
View File
@@ -62,18 +62,19 @@
</article>
<article class="stat stat-with-action" data-dashboard-widget="websocket"><span data-i18n="wsActive">WebSocket
tunnels</span><button class="pill stat-corner-action stat-corner-badge" data-i18n="tunnelsOpen"
data-ui-icon-name="none" id="tunnels-open" type="button">Active tunnels</button><strong
id="metric-ws">0</strong><small aria-label="Active WebSocket tunnels by frontend protocol"><span
data-i18n="wsH1">H1
Upgrade</span> <span id="metric-ws-h1">0</span> · <span data-i18n="wsH2Rfc8441">H2
RFC 8441</span> <span id="metric-ws-h2">0</span> · <span data-i18n="wsH3Rfc9220">H3
RFC 9220</span> <span id="metric-ws-h3">0</span><br><span data-i18n="wsEstablished">Established</span>:
<span id="metric-ws-total">0</span></small></article>
data-ui-icon-name="none" id="tunnels-open" type="button">Active tunnels</button><div class="stat-value-row"><strong
id="metric-ws">0</strong><small class="stat-side-value" title="Established"><span
data-i18n="wsEstablished">Established</span> <b id="metric-ws-total">0</b></small></div><small
aria-label="Active WebSocket tunnels by frontend protocol"><span data-i18n="wsH1" title="H1 Upgrade">H1</span>
<span id="metric-ws-h1">0</span> · <span data-i18n="wsH2Rfc8441" title="H2 RFC 8441">H2</span>
<span id="metric-ws-h2">0</span> · <span data-i18n="wsH3Rfc9220" title="H3 RFC 9220">H3</span>
<span id="metric-ws-h3">0</span></small></article>
<article class="stat" data-dashboard-widget="http2"><span data-i18n="h2Requests">HTTP/2
requests</span><strong id="metric-h2">0</strong><small><span data-i18n="h2ActiveConnections">Active H2
requests</span><strong id="metric-h2">0</strong><small><span data-i18n="h2ActiveConnections">H2
connections</span>: <span id="metric-h2-active">0</span></small></article>
<article class="stat" data-dashboard-widget="http3"><span data-i18n="h3Requests">HTTP/3
requests</span><strong id="metric-h3">0</strong></article>
requests</span><strong id="metric-h3">0</strong><small><span data-i18n="h3ActiveConnections">H3
connections</span>: <span id="metric-h3-active">0</span></small></article>
<article class="stat" data-dashboard-widget="tls"><span data-i18n="tlsFailures">TLS handshake
errors</span><strong id="metric-tls-errors">0</strong></article>
<article class="stat stat-chart" data-dashboard-widget="http_protocol"><span data-i18n="httpProtocolMix">HTTP protocol