v0.36.7
This commit is contained in:
1 parent
213332d0b5
commit
1ac94213e0
13 files changed
+68
-28
No files matched your search
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "RustProxy Administration API",
|
||||
"version": "0.36.6",
|
||||
"version": "0.36.7",
|
||||
"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": [
|
||||
|
||||
@@ -408,7 +408,7 @@ function cacheOutcomeMix(totals) {
|
||||
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 exact count and percentage stay in the segment and legend tooltips).
|
||||
// (the legend carries the request counts; tooltips give both).
|
||||
function fitCacheMixLabels() {
|
||||
const bar = $("cache-mix-bar");
|
||||
if (!bar) return;
|
||||
@@ -430,6 +430,7 @@ function renderCacheMix(mix, totalRequests) {
|
||||
segment.style.width = `${pct}%`;
|
||||
segment.textContent = mix && pct > 0 ? `${fmt(pct, pct < 10 ? 1 : 0)}%` : "";
|
||||
const detail = mix ? `${name.toUpperCase()}: ${fmt(mix.counts[i])} (${fmt(pct, 1)}%)` : "";
|
||||
$(`cache-mix-${name}-count`).textContent = mix ? fmt(mix.counts[i]) : "-";
|
||||
segment.title = detail; $(`cache-mix-${name}-key`).title = detail;
|
||||
});
|
||||
fitCacheMixLabels();
|
||||
|
||||
@@ -69,7 +69,9 @@
|
||||
const reason = value => { const key = `cacheReason_${value}`, text = t(key); return text === key ? value : text; };
|
||||
|
||||
// Decision counters: label + count + share bar; top three visible, the rest folded into a details toggle.
|
||||
function reasonsCell(reasons) {
|
||||
// Folded counters keep their open/closed state across auto-refresh re-renders.
|
||||
const openReasonRows = new Set();
|
||||
function reasonsCell(reasons, rowKey = "") {
|
||||
const td = el("td", undefined, "cache-reasons"), entries = Object.entries(reasons || {}).sort((a, b) => b[1] - a[1]);
|
||||
if (!entries.length) { td.append(el("span", "-", "cache-reason-none")); return td; }
|
||||
const max = entries[0][1] || 1, line = ([key, count]) => {
|
||||
@@ -78,7 +80,7 @@
|
||||
item.append(el("span", reason(key), "cache-reason-name"), el("b", number(count), "cache-reason-count"), track); item.title = key; return item;
|
||||
};
|
||||
entries.slice(0, 3).forEach(entry => td.append(line(entry)));
|
||||
if (entries.length > 3) { const more = el("details", undefined, "cache-reason-more"); more.append(el("summary", `+${entries.length - 3}`)); entries.slice(3).forEach(entry => more.append(line(entry))); td.append(more); }
|
||||
if (entries.length > 3) { const more = el("details", undefined, "cache-reason-more"); more.open = openReasonRows.has(rowKey); more.addEventListener("toggle", () => { if (more.open) openReasonRows.add(rowKey); else openReasonRows.delete(rowKey); }); more.append(el("summary", `+${entries.length - 3}`)); entries.slice(3).forEach(entry => more.append(line(entry))); td.append(more); }
|
||||
return td;
|
||||
}
|
||||
function resetLiveChart(selected = scope()) {
|
||||
@@ -182,7 +184,7 @@
|
||||
[[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);
|
||||
tr.append(reasonsCell(row.reasons)); return tr;
|
||||
tr.append(reasonsCell(row.reasons, `${row.vhost || ""}\u0000${row.domain || ""}`)); return tr;
|
||||
}));
|
||||
const domainEmpty = $("cache-domains-empty");
|
||||
domainEmpty.textContent = groups.length ? t("cacheNoDomainsFiltered") : t("cacheNoDomains");
|
||||
@@ -191,7 +193,12 @@
|
||||
const tr = el("tr"), resident = Number(row.resident_bytes) || 0, disk = Number(row.storage_bytes) || 0, body = Number(row.body_bytes) || 0, storage = resident || disk;
|
||||
tr.append(el("td", row.vhost || t("cacheLegacy")), el("td", row.domain));
|
||||
const targetValue = row.target || row.path || "-", target = makeCopyCell(targetValue);
|
||||
if (row.transformed) target.append(el("span", t("transformBadge"), "cache-transform-badge"));
|
||||
if (row.transformed) {
|
||||
// Keep the badge outside the ellipsised text so a long URL never clips it.
|
||||
const wrap = el("div", undefined, "cache-target-wrap");
|
||||
wrap.append(el("span", targetValue, "cache-target-text"), el("span", t("transformBadge"), "cache-transform-badge"));
|
||||
target.replaceChildren(wrap);
|
||||
}
|
||||
tr.append(target);
|
||||
const keyTags = `${row.cache_key || "-"}${row.tags?.length ? ` · ${row.tags.join(", ")}` : ""}`;
|
||||
tr.append(makeCopyCell(keyTags));
|
||||
|
||||
@@ -2246,6 +2246,12 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
|
||||
background: var(--chart-secondary);
|
||||
}
|
||||
|
||||
#panel-overview .cache-mix-legend b {
|
||||
margin-left: 1px;
|
||||
color: var(--text);
|
||||
font: 500 9px var(--mono);
|
||||
}
|
||||
|
||||
#panel-overview .system-card .system-uptime-meta,
|
||||
#panel-overview .connection-card .connection-backends-meta {
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -1184,3 +1184,21 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
|
||||
.transform-reason-list .cache-reason-line { margin: 0; }
|
||||
|
||||
.cache-ratio-meter, .cache-ratio-meter>i { border-radius: var(--rp-progress-radius, 0); }
|
||||
|
||||
/* Decision counters (0.36.7): name + count on one line, a thin share bar below (relative to the largest counter) */
|
||||
.cache-reasons .cache-reason-line { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; align-items: baseline; margin: 0 0 6px; white-space: nowrap; }
|
||||
.cache-reasons .cache-reason-name { overflow: hidden; text-overflow: ellipsis; }
|
||||
.cache-reasons .cache-reason-track { grid-column: 1 / -1; display: block; height: 2px; margin-top: 3px; background: var(--line); border-radius: var(--rp-progress-radius, 0); overflow: hidden; }
|
||||
.cache-reasons .cache-reason-track>i { display: block; height: 100%; background: var(--muted); opacity: .7; border-radius: var(--rp-progress-radius, 0); }
|
||||
.cache-reasons .cache-reason-more { cursor: default; }
|
||||
.cache-reasons .cache-reason-more>summary { cursor: pointer; display: inline-flex; align-items: center; gap: 5px; margin: 0 0 6px; padding: 1px 6px; border: 1px solid var(--line); border-radius: var(--rp-radius-control, 0); font: 10px/1.5 var(--mono); color: var(--muted); list-style: none; user-select: none; }
|
||||
.cache-reasons .cache-reason-more>summary::-webkit-details-marker { display: none; }
|
||||
.cache-reasons .cache-reason-more>summary::before { content: "›"; display: inline-block; font-size: 12px; line-height: 1; transition: transform .15s ease; }
|
||||
.cache-reasons .cache-reason-more[open]>summary::before { transform: rotate(90deg); }
|
||||
.cache-reasons .cache-reason-more>summary:hover { color: var(--text); border-color: var(--muted); }
|
||||
|
||||
/* Transformed badge (0.36.7): the URL ellipsises, the badge always stays visible */
|
||||
.cache-target-wrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
||||
.cache-target-text { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.cache-target-wrap .cache-transform-badge { flex: 0 0 auto; margin-left: 0; }
|
||||
|
||||
@@ -46,10 +46,10 @@
|
||||
<span class="cache-mix-other" id="cache-mix-other" style="width:0%"></span>
|
||||
</div>
|
||||
<small class="cache-mix-legend">
|
||||
<span class="cache-mix-hit-key" id="cache-mix-hit-key"><i></i>HIT</span>
|
||||
<span class="cache-mix-miss-key" id="cache-mix-miss-key"><i></i>MISS</span>
|
||||
<span class="cache-mix-bypass-key" id="cache-mix-bypass-key"><i></i>BYPASS</span>
|
||||
<span class="cache-mix-other-key" id="cache-mix-other-key"><i></i><span data-i18n="cacheMixOther">OTHER</span></span>
|
||||
<span class="cache-mix-hit-key" id="cache-mix-hit-key"><i></i>HIT <b id="cache-mix-hit-count">-</b></span>
|
||||
<span class="cache-mix-miss-key" id="cache-mix-miss-key"><i></i>MISS <b id="cache-mix-miss-count">-</b></span>
|
||||
<span class="cache-mix-bypass-key" id="cache-mix-bypass-key"><i></i>BYPASS <b id="cache-mix-bypass-count">-</b></span>
|
||||
<span class="cache-mix-other-key" id="cache-mix-other-key"><i></i><span data-i18n="cacheMixOther">OTHER</span> <b id="cache-mix-other-count">-</b></span>
|
||||
</small>
|
||||
</article>
|
||||
<article class="stat" data-dashboard-widget="memory"><span data-i18n="processMemory">Process
|
||||
|
||||
Reference in new issue
Block a user