v0.24.5
This commit is contained in:
commit
5e23e9af02
240 files changed
+99957
No files matched your search
@@ -0,0 +1,170 @@
|
||||
"use strict";
|
||||
(() => {
|
||||
let loading = false, generation = 0, lastData = null;
|
||||
let liveChartScope = "", liveChartPrevious = null, liveChartPreviousAt = null;
|
||||
const liveChartSamples = { applied: [], cache_hits: [], skipped: [], errors: [] };
|
||||
const LIVE_CHART_LIMITS = new Set([60, 120, 300, 600]);
|
||||
const LIVE_CHART_STORAGE_KEY = "rustproxy.transform.liveChartSamples";
|
||||
let liveChartLimit = 60;
|
||||
const scope = () => $("transform-scope")?.value || "*";
|
||||
const number = value => fmt(Number(value) || 0);
|
||||
const percent = counters => {
|
||||
const original = Number(counters?.original_bytes) || 0;
|
||||
const saved = Number(counters?.saved_bytes) || 0;
|
||||
return original > 0 ? `${fmt(saved / original * 100, 1)}%` : "0.0%";
|
||||
};
|
||||
const averageMs = counters => {
|
||||
const examined = Number(counters?.examined) || 0;
|
||||
const duration = Number(counters?.duration_us) || 0;
|
||||
return examined ? `${fmt(duration / examined / 1000, 2)} ms` : "0.00 ms";
|
||||
};
|
||||
const emptyCounters = () => ({ examined: 0, applied: 0, skipped: 0, errors: 0, cache_hits: 0, original_bytes: 0, transformed_bytes: 0, saved_bytes: 0, duration_us: 0, by_kind: {}, reasons: {} });
|
||||
function resetLiveChart(selected = scope()) {
|
||||
liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null;
|
||||
for (const values of Object.values(liveChartSamples)) values.length = 0;
|
||||
for (const id of ["transform-live-applied-line", "transform-live-cache-line", "transform-live-skipped-line", "transform-live-error-line"]) $(id)?.setAttribute("points", "0,170 900,170");
|
||||
for (const id of ["transform-live-applied-rate", "transform-live-cache-rate", "transform-live-skipped-rate", "transform-live-error-rate"]) if ($(id)) $(id).textContent = "0.0/s";
|
||||
if ($("transform-live-chart-value")) $("transform-live-chart-value").textContent = "-";
|
||||
}
|
||||
function redrawLiveChart() {
|
||||
const peak = Math.max(1, ...liveChartSamples.applied, ...liveChartSamples.cache_hits, ...liveChartSamples.skipped, ...liveChartSamples.errors);
|
||||
$("transform-live-applied-line")?.setAttribute("points", sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak));
|
||||
$("transform-live-cache-line")?.setAttribute("points", sparklinePoints(liveChartSamples.cache_hits, 900, 180, 10, peak));
|
||||
$("transform-live-skipped-line")?.setAttribute("points", sparklinePoints(liveChartSamples.skipped, 900, 180, 10, peak));
|
||||
$("transform-live-error-line")?.setAttribute("points", sparklinePoints(liveChartSamples.errors, 900, 180, 10, peak));
|
||||
}
|
||||
function setLiveChartLimit(value, persist = true) {
|
||||
const parsed = Number(value);
|
||||
liveChartLimit = LIVE_CHART_LIMITS.has(parsed) ? parsed : 60;
|
||||
for (const key of Object.keys(liveChartSamples)) liveChartSamples[key] = liveChartSamples[key].slice(-liveChartLimit);
|
||||
if ($("transform-live-samples")) $("transform-live-samples").value = String(liveChartLimit);
|
||||
if (persist) { try { localStorage.setItem(LIVE_CHART_STORAGE_KEY, String(liveChartLimit)); } catch {} }
|
||||
redrawLiveChart();
|
||||
}
|
||||
function initializeLiveChartLimit() {
|
||||
let saved = 60;
|
||||
try { saved = Number(localStorage.getItem(LIVE_CHART_STORAGE_KEY) || 60); } catch {}
|
||||
setLiveChartLimit(saved, false);
|
||||
}
|
||||
function renderLiveChart(counters) {
|
||||
const selected = scope();
|
||||
if (liveChartScope !== selected) resetLiveChart(selected);
|
||||
const now = performance.now(), current = {
|
||||
applied: Math.max(0, Number(counters.applied) || 0),
|
||||
cache_hits: Math.max(0, Number(counters.cache_hits) || 0),
|
||||
skipped: Math.max(0, Number(counters.skipped) || 0),
|
||||
errors: Math.max(0, Number(counters.errors) || 0)
|
||||
}, rates = { applied: 0, cache_hits: 0, skipped: 0, errors: 0 };
|
||||
if (liveChartPrevious && liveChartPreviousAt != null) {
|
||||
const elapsed = Math.max(.001, (now - liveChartPreviousAt) / 1000);
|
||||
for (const key of Object.keys(rates)) if (current[key] >= liveChartPrevious[key]) rates[key] = (current[key] - liveChartPrevious[key]) / elapsed;
|
||||
}
|
||||
liveChartPrevious = current; liveChartPreviousAt = now;
|
||||
for (const key of Object.keys(rates)) { liveChartSamples[key].push(rates[key]); liveChartSamples[key] = liveChartSamples[key].slice(-liveChartLimit); }
|
||||
redrawLiveChart();
|
||||
$("transform-live-applied-rate").textContent = `${rates.applied.toFixed(1)}/s`;
|
||||
$("transform-live-cache-rate").textContent = `${rates.cache_hits.toFixed(1)}/s`;
|
||||
$("transform-live-skipped-rate").textContent = `${rates.skipped.toFixed(1)}/s`;
|
||||
$("transform-live-error-rate").textContent = `${rates.errors.toFixed(1)}/s`;
|
||||
$("transform-live-chart-value").textContent = `Σ ${(rates.applied + rates.cache_hits + rates.skipped + rates.errors).toFixed(1)}/s`;
|
||||
}
|
||||
function countersFor(data) {
|
||||
if (scope() === "*") return data?.metrics?.total || emptyCounters();
|
||||
return data?.metrics?.scopes?.find(row => row.vhost === scope()) || emptyCounters();
|
||||
}
|
||||
function configure(data) {
|
||||
const selected = scope(), labels = new Map();
|
||||
for (const vhost of data?.vhosts || []) {
|
||||
const hosts = Array.isArray(vhost.hosts) ? vhost.hosts.join(", ") : "";
|
||||
labels.set(vhost.id, hosts ? `${vhost.id} / ${hosts}` : vhost.id);
|
||||
}
|
||||
for (const row of data?.metrics?.scopes || []) if (!labels.has(row.vhost)) labels.set(row.vhost, row.vhost);
|
||||
const items = [el("option", t("logAllVhosts"))]; items[0].value = "*";
|
||||
for (const [id, label] of [...labels].sort((a, b) => a[0].localeCompare(b[0]))) {
|
||||
const option = el("option", label); option.value = id; items.push(option);
|
||||
}
|
||||
$("transform-scope").replaceChildren(...items);
|
||||
$("transform-scope").value = items.some(item => item.value === selected) ? selected : "*";
|
||||
}
|
||||
function renderTypes(counters) {
|
||||
const kinds = [
|
||||
["javascript", "JavaScript"], ["css", "CSS"], ["html", "HTML"], ["svg", "SVG"], ["json", "JSON"]
|
||||
];
|
||||
$("transform-type-rows").replaceChildren(...kinds.map(([key, label]) => {
|
||||
const tr = el("tr"); tr.append(el("td", label), el("td", number(counters?.by_kind?.[key] || 0))); return tr;
|
||||
}));
|
||||
}
|
||||
function renderReasons(counters) {
|
||||
const entries = Object.entries(counters?.reasons || {}).sort((a, b) => b[1] - a[1]);
|
||||
const target = $("transform-reason-list"); target.replaceChildren(...entries.map(([key, count]) => {
|
||||
const tag = el("span", `${key}: ${number(count)}`, "cache-reason"); tag.title = key; return tag;
|
||||
}));
|
||||
$("transform-reasons-empty").hidden = entries.length > 0;
|
||||
$("tp-skip-top").textContent = entries.length ? entries.slice(0, 2).map(([key, count]) => `${key}: ${number(count)}`).join(" · ") : t("transformNoSkipsShort");
|
||||
}
|
||||
function renderVhosts(data) {
|
||||
const wanted = scope();
|
||||
const rows = (data?.metrics?.scopes || []).filter(row => wanted === "*" || row.vhost === wanted);
|
||||
$("transform-vhost-rows").replaceChildren(...rows.map(row => {
|
||||
const tr = el("tr");
|
||||
for (const value of [
|
||||
row.vhost,
|
||||
number(row.applied),
|
||||
number(row.cache_hits),
|
||||
number(row.skipped),
|
||||
number(row.errors),
|
||||
fmtBytes(row.saved_bytes || 0),
|
||||
percent(row),
|
||||
averageMs(row)
|
||||
]) tr.append(el("td", value));
|
||||
return tr;
|
||||
}));
|
||||
$("transform-vhosts-empty").hidden = rows.length > 0;
|
||||
}
|
||||
function render(data) {
|
||||
lastData = data; configure(data);
|
||||
const counters = countersFor(data);
|
||||
renderLiveChart(counters);
|
||||
$("tp-applied").textContent = number(counters.applied);
|
||||
$("tp-examined").textContent = `${t("transformExamined")}: ${number(counters.examined)}`;
|
||||
$("tp-cache-hits").textContent = number(counters.cache_hits);
|
||||
$("tp-saved").textContent = fmtBytes(counters.saved_bytes || 0);
|
||||
$("tp-byte-flow").textContent = `${fmtBytes(counters.original_bytes || 0)} → ${fmtBytes(counters.transformed_bytes || 0)}`;
|
||||
$("tp-reduction").textContent = percent(counters);
|
||||
$("tp-skipped").textContent = number(counters.skipped);
|
||||
$("tp-errors").textContent = number(counters.errors);
|
||||
$("tp-duration").textContent = `${t("transformAvgTime")}: ${averageMs(counters)}`;
|
||||
renderTypes(counters); renderReasons(counters); renderVhosts(data);
|
||||
$("transform-panel-state").textContent = `${t("cacheUpdated")}: ${new Date().toLocaleTimeString(locale())}`;
|
||||
}
|
||||
async function refresh() {
|
||||
if (!authenticated || currentPanel !== "transform" || loading) return;
|
||||
loading = true; const epoch = authEpoch, token = generation;
|
||||
try {
|
||||
const data = await api("/api/transform");
|
||||
if (epoch !== authEpoch || token !== generation || !authenticated) return;
|
||||
render(data);
|
||||
} catch (error) {
|
||||
if (epoch === authEpoch) { $("transform-panel-state").textContent = error.message; if (error.status === 401) lock(); }
|
||||
} finally {
|
||||
loading = false;
|
||||
if (token !== generation && authenticated && currentPanel === "transform") void refresh();
|
||||
}
|
||||
}
|
||||
initializeLiveChartLimit();
|
||||
$("transform-live-samples")?.addEventListener("change", event => setLiveChartLimit(event.currentTarget.value));
|
||||
$("transform-scope")?.addEventListener("change", () => { resetLiveChart(); if (lastData) render(lastData); });
|
||||
$("transform-refresh")?.addEventListener("click", () => { generation++; void refresh(); });
|
||||
$("transform-panel-settings")?.addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("transform"); $("transform-form")?.scrollIntoView({ block: "start" }); });
|
||||
setInterval(() => { if (!document.hidden && $("transform-live")?.checked) void refresh(); }, 3000);
|
||||
window.rustProxyTransformPanel = {
|
||||
refresh,
|
||||
reset() {
|
||||
generation++; lastData = null; resetLiveChart();
|
||||
$("transform-type-rows")?.replaceChildren();
|
||||
$("transform-vhost-rows")?.replaceChildren();
|
||||
$("transform-reason-list")?.replaceChildren();
|
||||
if ($("transform-panel-state")) $("transform-panel-state").textContent = "";
|
||||
}
|
||||
};
|
||||
})();
|
||||
Reference in new issue
Block a user