v0.25.1
This commit is contained in:
1 parent
87e571c170
commit
fa7abc9e53
308 files changed
+49746
-50237
No files matched your search
@@ -1,135 +1,127 @@
|
||||
"use strict";
|
||||
(() => {
|
||||
let loading = false, timer = null, lastData = null;
|
||||
let loading = false, timer = null, lastData = null, activeTab = "overview";
|
||||
const chartPalette = ["var(--accent)", "var(--green)", "var(--blue)", "var(--transform-chart)", "var(--red)", "var(--notice-text)", "var(--border-hover)", "var(--separator)"];
|
||||
const scope = () => $("traffic-scope")?.value || "*";
|
||||
const number = value => fmt(Number(value) || 0);
|
||||
|
||||
function selectTab(name) {
|
||||
activeTab = name;
|
||||
document.querySelectorAll("[data-traffic-tab]").forEach(button => {
|
||||
const active = button.dataset.trafficTab === name;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
button.tabIndex = active ? 0 : -1;
|
||||
});
|
||||
document.querySelectorAll("[data-traffic-page]").forEach(page => { page.hidden = page.dataset.trafficPage !== name; });
|
||||
}
|
||||
|
||||
function configure(data) {
|
||||
const selected = scope(), options = [el("option", t("logAllVhosts"))];
|
||||
options[0].value = "*";
|
||||
for (const vhost of data.vhosts || []) {
|
||||
const option = el("option", `${vhost.id} / ${(vhost.hosts || []).join(", ")}`);
|
||||
option.value = vhost.id;
|
||||
options.push(option);
|
||||
option.value = vhost.id; options.push(option);
|
||||
}
|
||||
$("traffic-scope").replaceChildren(...options);
|
||||
$("traffic-scope").value = options.some(option => option.value === selected) ? selected : "*";
|
||||
}
|
||||
function renderTop(id, entries, enabled = true) {
|
||||
const body = $(`traffic-${id}`), empty = $(`traffic-${id}-empty`);
|
||||
const values = enabled ? (entries || []) : [];
|
||||
body.replaceChildren(...values.map(item => {
|
||||
const row = el("tr");
|
||||
const value = el("td", item.value || "-"); value.title = item.value || "-";
|
||||
row.append(value, el("td", number(item.count)));
|
||||
return row;
|
||||
}));
|
||||
empty.hidden = values.length > 0;
|
||||
|
||||
function chartEntries(entries, enabled = true) {
|
||||
return (enabled ? (entries || []) : []).filter(item => Number(item.count) > 0);
|
||||
}
|
||||
|
||||
function renderDonut(id, entries, enabled = true) {
|
||||
const chart = $(`traffic-chart-${id}`), legend = $(`traffic-chart-${id}-legend`), totalNode = $(`traffic-chart-${id}-total`);
|
||||
if (!chart || !legend || !totalNode) return;
|
||||
const values = (enabled ? (entries || []) : []).filter(item => Number(item.count) > 0);
|
||||
const empty = $(`traffic-chart-${id}-empty`), values = chartEntries(entries, enabled);
|
||||
const total = values.reduce((sum, item) => sum + Number(item.count || 0), 0);
|
||||
totalNode.textContent = number(total);
|
||||
totalNode.textContent = number(total); if (empty) empty.hidden = total > 0;
|
||||
if (!total) {
|
||||
chart.style.background = "var(--panel2)";
|
||||
chart.setAttribute("aria-label", t("trafficNoData"));
|
||||
legend.replaceChildren(el("li", t("trafficNoData"), "fine"));
|
||||
return;
|
||||
chart.style.background = "var(--panel2)"; chart.setAttribute("aria-label", t("trafficNoData"));
|
||||
legend.replaceChildren(el("li", t("trafficNoData"), "fine")); return;
|
||||
}
|
||||
const shown = values.slice(0, 7).map(item => ({ value: item.value || "-", count: Number(item.count) || 0 }));
|
||||
if (values.length > shown.length) {
|
||||
shown.push({ value: t("trafficOther"), count: values.slice(shown.length).reduce((sum, item) => sum + Number(item.count || 0), 0) });
|
||||
}
|
||||
if (values.length > shown.length) shown.push({ value: t("trafficOther"), count: values.slice(shown.length).reduce((sum, item) => sum + Number(item.count || 0), 0) });
|
||||
let cursor = 0;
|
||||
const segments = shown.map((item, index) => {
|
||||
const start = cursor / total * 100;
|
||||
cursor += item.count;
|
||||
const end = cursor / total * 100;
|
||||
chart.style.background = `conic-gradient(${shown.map((item, index) => {
|
||||
const start = cursor / total * 100; cursor += item.count; const end = cursor / total * 100;
|
||||
return `${chartPalette[index % chartPalette.length]} ${start.toFixed(4)}% ${end.toFixed(4)}%`;
|
||||
});
|
||||
chart.style.background = `conic-gradient(${segments.join(", ")})`;
|
||||
}).join(", ")})`;
|
||||
const heading = chart.closest(".traffic-chart-card")?.querySelector("h3")?.textContent || t("trafficDimensionsTitle");
|
||||
chart.setAttribute("aria-label", `${heading}: ${shown.map(item => `${item.value} ${Math.round(item.count / total * 100)}%`).join(", ")}`);
|
||||
legend.replaceChildren(...shown.map((item, index) => {
|
||||
const row = el("li"), label = el("span"), swatch = el("i", undefined, "traffic-chart-swatch");
|
||||
swatch.style.background = chartPalette[index % chartPalette.length];
|
||||
const text = el("span", item.value || "-", "traffic-chart-label"); text.title = item.value || "-";
|
||||
label.append(swatch, text);
|
||||
row.append(label, el("b", `${(item.count / total * 100).toFixed(1)}%`));
|
||||
return row;
|
||||
label.append(swatch, text); row.append(label, el("b", `${(item.count / total * 100).toFixed(1)}%`)); return row;
|
||||
}));
|
||||
}
|
||||
|
||||
function renderBars(id, entries, enabled = true, formatter = number) {
|
||||
const chart = $(`traffic-chart-${id}`); if (!chart) return;
|
||||
const empty = $(`traffic-chart-${id}-empty`), values = chartEntries(entries, enabled).slice(0, 12);
|
||||
const total = values.reduce((sum, item) => sum + Number(item.count || 0), 0), peak = Math.max(1, ...values.map(item => Number(item.count) || 0));
|
||||
if (empty) empty.hidden = values.length > 0;
|
||||
chart.replaceChildren(...values.map(item => {
|
||||
const count = Number(item.count) || 0, row = el("div", undefined, "traffic-bar-row");
|
||||
const label = el("span", item.value || "-", "traffic-bar-label"); label.title = item.value || "-";
|
||||
const track = el("span", undefined, "traffic-bar-track"), fill = el("i", undefined, "traffic-bar-fill"); fill.style.width = `${Math.max(1.5, count / peak * 100)}%`; track.append(fill);
|
||||
const value = el("span", formatter(count), "traffic-bar-value"); value.title = total ? `${(count / total * 100).toFixed(1)}%` : "";
|
||||
row.append(label, track, value); return row;
|
||||
}));
|
||||
chart.setAttribute("aria-label", values.length ? values.map(item => `${item.value}: ${formatter(Number(item.count) || 0)}`).join(", ") : t("trafficNoData"));
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
lastData = data;
|
||||
configure(data);
|
||||
lastData = data; configure(data);
|
||||
const analytics = data.analytics || {}, stats = analytics.data || {}, settings = data.settings || {};
|
||||
$("traffic-requests").textContent = number(stats.requests);
|
||||
$("traffic-errors").textContent = number(stats.errors);
|
||||
$("traffic-requests").textContent = number(stats.requests); $("traffic-errors").textContent = number(stats.errors);
|
||||
const requests = Number(stats.requests) || 0, errors = Number(stats.errors) || 0;
|
||||
$("traffic-error-ratio").textContent = `${requests ? (errors / requests * 100).toFixed(2) : "0.00"}%`;
|
||||
$("traffic-response-bytes").textContent = fmtBytes(Number(stats.response_bytes) || 0);
|
||||
$("traffic-request-bytes").textContent = `${t("trafficRequestBytes")}: ${fmtBytes(Number(stats.request_bytes) || 0)}`;
|
||||
$("traffic-latency").textContent = stats.mean_duration_ms == null ? "-" : `${fmt(stats.mean_duration_ms, 2)} ms`;
|
||||
$("traffic-dropped").textContent = `${t("trafficDroppedSamples")}: ${number(analytics.queue_dropped)}`;
|
||||
const capture = [];
|
||||
capture.push(settings.capture_user_agent ? t("trafficUserAgentEnabled") : t("trafficUserAgentDisabled"));
|
||||
const capture = [settings.capture_user_agent ? t("trafficUserAgentEnabled") : t("trafficUserAgentDisabled")];
|
||||
capture.push(settings.capture_country ? (data.geoip?.loaded ? t("trafficCountryEnabled") : t("trafficCountryNoDb")) : t("trafficCountryDisabled"));
|
||||
$("traffic-capture-status").textContent = capture.join(" · ");
|
||||
renderTop("countries", stats.countries, !!settings.capture_country);
|
||||
renderTop("user-agents", stats.user_agents, !!settings.capture_user_agent);
|
||||
renderTop("client-ips", stats.client_ips);
|
||||
renderTop("paths", stats.paths);
|
||||
renderTop("routes", stats.routes);
|
||||
renderTop("methods", stats.methods);
|
||||
renderTop("statuses", stats.statuses);
|
||||
renderTop("cache", stats.cache);
|
||||
|
||||
renderDonut("countries", stats.countries, !!settings.capture_country);
|
||||
renderDonut("user-agents", stats.user_agents, !!settings.capture_user_agent);
|
||||
renderDonut("client-ips", stats.client_ips);
|
||||
renderDonut("methods", stats.methods);
|
||||
renderDonut("statuses", stats.statuses);
|
||||
renderDonut("cache", stats.cache);
|
||||
renderBars("user-agents", stats.user_agents, !!settings.capture_user_agent);
|
||||
renderBars("client-ips", stats.client_ips);
|
||||
renderBars("paths", stats.paths); renderBars("routes", stats.routes);
|
||||
renderDonut("methods", stats.methods); renderDonut("methods-detail", stats.methods);
|
||||
renderDonut("statuses", stats.statuses); renderDonut("statuses-detail", stats.statuses);
|
||||
renderDonut("cache", stats.cache); renderDonut("cache-detail", stats.cache);
|
||||
renderBars("volume", [
|
||||
{ value: t("trafficRequestBytes"), count: Number(stats.request_bytes) || 0 },
|
||||
{ value: t("trafficResponseBytes"), count: Number(stats.response_bytes) || 0 },
|
||||
], true, fmtBytes);
|
||||
$("traffic-panel-state").textContent = `${t("trafficUpdated")}: ${new Date().toLocaleTimeString(locale())}`;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (!authenticated || loading) return;
|
||||
loading = true;
|
||||
try {
|
||||
const selected = scope();
|
||||
const data = await api(`/api/traffic?vhost=${encodeURIComponent(selected)}`);
|
||||
render(data);
|
||||
} catch (error) {
|
||||
$("traffic-panel-state").textContent = error.message;
|
||||
} finally { loading = false; }
|
||||
if (!authenticated || loading) return; loading = true;
|
||||
try { render(await api(`/api/traffic?vhost=${encodeURIComponent(scope())}`)); }
|
||||
catch (error) { $("traffic-panel-state").textContent = error.message; }
|
||||
finally { loading = false; }
|
||||
}
|
||||
|
||||
function reset() {
|
||||
lastData = null;
|
||||
if ($("traffic-panel-state")) $("traffic-panel-state").textContent = "";
|
||||
for (const id of ["countries", "user-agents", "client-ips", "paths", "routes", "methods", "statuses", "cache"]) {
|
||||
$(`traffic-${id}`)?.replaceChildren();
|
||||
}
|
||||
for (const id of ["countries", "user-agents", "client-ips", "methods", "statuses", "cache"]) {
|
||||
lastData = null; if ($("traffic-panel-state")) $("traffic-panel-state").textContent = "";
|
||||
document.querySelectorAll(".traffic-bar-chart").forEach(chart => chart.replaceChildren());
|
||||
for (const id of ["countries", "methods", "methods-detail", "statuses", "statuses-detail", "cache", "cache-detail"]) {
|
||||
const chart = $(`traffic-chart-${id}`), legend = $(`traffic-chart-${id}-legend`), total = $(`traffic-chart-${id}-total`);
|
||||
if (chart) chart.style.background = "var(--panel2)";
|
||||
legend?.replaceChildren();
|
||||
if (total) total.textContent = "-";
|
||||
if (chart) chart.style.background = "var(--panel2)"; legend?.replaceChildren(); if (total) total.textContent = "-";
|
||||
}
|
||||
selectTab("overview");
|
||||
}
|
||||
function startTimer() {
|
||||
if (timer) return;
|
||||
timer = setInterval(() => {
|
||||
if (!authenticated || !$("traffic-live")?.checked || $("panel-traffic")?.hidden) return;
|
||||
void refresh();
|
||||
}, 3000);
|
||||
}
|
||||
$("traffic-refresh")?.addEventListener("click", () => void refresh());
|
||||
$("traffic-scope")?.addEventListener("change", () => void refresh());
|
||||
$("traffic-settings")?.addEventListener("click", () => {
|
||||
setPanel("settings");
|
||||
window.rustProxySettings?.select("logging");
|
||||
});
|
||||
startTimer();
|
||||
window.rustProxyTrafficPanel = { refresh, reset, rerender: () => lastData && render(lastData) };
|
||||
|
||||
document.querySelectorAll("[data-traffic-tab]").forEach(button => button.addEventListener("click", () => selectTab(button.dataset.trafficTab)));
|
||||
$("traffic-refresh")?.addEventListener("click", () => void refresh()); $("traffic-scope")?.addEventListener("change", () => void refresh());
|
||||
$("traffic-settings")?.addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("logging"); });
|
||||
timer = setInterval(() => { if (authenticated && $("traffic-live")?.checked && !$("panel-traffic")?.hidden) void refresh(); }, 3000);
|
||||
selectTab(activeTab);
|
||||
window.rustProxyTrafficPanel = { refresh, reset, rerender: () => lastData && render(lastData), select: selectTab };
|
||||
})();
|
||||
Reference in new issue
Block a user