v0.25.3
This commit is contained in:
1 parent
174389e5b1
commit
e6604e41fc
73 files changed
+1240
-201
No files matched your search
@@ -0,0 +1,107 @@
|
||||
let configHistoryEntries = [];
|
||||
function historyDate(value, options = {}) {
|
||||
try { return new Intl.DateTimeFormat(locale(), options).format(new Date(Number(value) * 1000)); }
|
||||
catch { return new Date(Number(value) * 1000).toLocaleString(); }
|
||||
}
|
||||
function historyDayKey(value) {
|
||||
const date = new Date(Number(value) * 1000);
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
function historyMonthKey(value) { return historyDayKey(value).slice(0, 7); }
|
||||
function historyValue(value) {
|
||||
if (value === undefined) return t("configHistoryMissing");
|
||||
const text = JSON.stringify(value, null, 2);
|
||||
return text.length > 6000 ? `${text.slice(0, 6000)}\n...` : text;
|
||||
}
|
||||
function configDiff(before, after, path = "$", output = [], depth = 0) {
|
||||
if (output.length >= 200) return output;
|
||||
if (Object.is(before, after)) return output;
|
||||
const beforeObject = before && typeof before === "object" && !Array.isArray(before);
|
||||
const afterObject = after && typeof after === "object" && !Array.isArray(after);
|
||||
if (beforeObject && afterObject && depth < 10) {
|
||||
const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();
|
||||
for (const key of keys) configDiff(before[key], after[key], `${path}.${key}`, output, depth + 1);
|
||||
return output;
|
||||
}
|
||||
if (JSON.stringify(before) !== JSON.stringify(after)) output.push({ path, before, after });
|
||||
return output;
|
||||
}
|
||||
function renderConfigRevisionDetail(detail) {
|
||||
const entry = detail?.revision;
|
||||
if (!entry) return;
|
||||
$("config-history-detail-title").textContent = `${t("configHistoryRevision")} ${entry.revision}`;
|
||||
$("config-history-detail-meta").textContent = `${historyDate(entry.time, { dateStyle: "medium", timeStyle: "short" })} · ${entry.actor}`;
|
||||
document.querySelectorAll(".config-history-revision").forEach(button => button.classList.toggle("active", Number(button.dataset.revision) === Number(entry.revision)));
|
||||
const diff = configDiff(entry.previous_config, entry.config);
|
||||
const box = $("config-history-diff"); box.replaceChildren();
|
||||
if (!entry.previous_config) { box.append(el("p", t("configHistoryBaseline"), "empty")); return; }
|
||||
if (!diff.length) { box.append(el("p", t("configHistoryNoChanges"), "empty")); return; }
|
||||
for (const change of diff) {
|
||||
const card = el("article", undefined, "config-history-change");
|
||||
card.append(el("strong", change.path, "config-history-change-path"));
|
||||
const values = el("div", undefined, "config-history-change-values");
|
||||
for (const [label, value] of [[t("configHistoryBefore"), change.before], [t("configHistoryAfter"), change.after]]) {
|
||||
const side = el("div"); side.append(el("span", label, "small-label"), el("pre", historyValue(value))); values.append(side);
|
||||
}
|
||||
card.append(values); box.append(card);
|
||||
}
|
||||
if (diff.length >= 200) box.append(el("p", t("configHistoryDiffLimit"), "fine"));
|
||||
}
|
||||
async function loadConfigRevision(revision) {
|
||||
try { renderConfigRevisionDetail(await api(`/api/config/history?revision=${encodeURIComponent(revision)}`)); }
|
||||
catch (error) { toast(error.message, true); }
|
||||
}
|
||||
function selectConfigHistoryDay(dayKey, entries) {
|
||||
document.querySelectorAll(".config-history-day").forEach(button => button.classList.toggle("is-selected", button.dataset.day === dayKey));
|
||||
const revisions = $("config-history-revisions"); revisions.replaceChildren();
|
||||
const sorted = [...entries].sort((a, b) => b.revision - a.revision);
|
||||
for (const entry of sorted) {
|
||||
const button = el("button", `${t("configHistoryRevisionShort")} ${entry.revision}`, "quiet config-history-revision");
|
||||
button.type = "button"; button.dataset.revision = entry.revision; button.title = `${entry.actor} · ${historyDate(entry.time, { dateStyle: "medium", timeStyle: "short" })}`;
|
||||
button.addEventListener("click", () => void loadConfigRevision(entry.revision)); revisions.append(button);
|
||||
}
|
||||
if (sorted[0]) void loadConfigRevision(sorted[0].revision);
|
||||
}
|
||||
function renderConfigHistory() {
|
||||
const calendar = $("config-history-calendar"); calendar.replaceChildren();
|
||||
const byMonth = new Map(), byDay = new Map();
|
||||
for (const entry of configHistoryEntries) {
|
||||
const month = historyMonthKey(entry.time), day = historyDayKey(entry.time);
|
||||
if (!byMonth.has(month)) byMonth.set(month, []); byMonth.get(month).push(entry);
|
||||
if (!byDay.has(day)) byDay.set(day, []); byDay.get(day).push(entry);
|
||||
}
|
||||
$("config-history-summary").textContent = configHistoryEntries.length ? t("configHistorySummary").replace("{count}", configHistoryEntries.length) : t("configHistoryEmpty");
|
||||
const months = [...byMonth.keys()].sort().reverse();
|
||||
for (const key of months) {
|
||||
const [yearText, monthText] = key.split("-"), year = Number(yearText), month = Number(monthText) - 1;
|
||||
const panel = el("section", undefined, "config-history-month");
|
||||
panel.append(el("h4", new Intl.DateTimeFormat(locale(), { month: "long", year: "numeric" }).format(new Date(year, month, 1))));
|
||||
const weekdays = el("div", undefined, "config-history-weekdays");
|
||||
for (let day = 0; day < 7; day++) weekdays.append(el("span", new Intl.DateTimeFormat(locale(), { weekday: "narrow" }).format(new Date(2024, 0, 7 + day))));
|
||||
panel.append(weekdays);
|
||||
const days = el("div", undefined, "config-history-days"), first = new Date(year, month, 1).getDay(), count = new Date(year, month + 1, 0).getDate();
|
||||
for (let blank = 0; blank < first; blank++) days.append(el("span", "", "config-history-day"));
|
||||
for (let day = 1; day <= count; day++) {
|
||||
const dayKey = `${yearText}-${monthText}-${String(day).padStart(2, "0")}`, entries = byDay.get(dayKey) || [];
|
||||
const node = el(entries.length ? "button" : "span", String(day), `config-history-day${entries.length ? " has-revisions" : ""}`);
|
||||
if (entries.length) {
|
||||
node.type = "button"; node.dataset.day = dayKey;
|
||||
node.append(el("small", `${entries.length} ${t("configHistoryEntries")}`, "config-history-day-count"));
|
||||
node.addEventListener("click", () => selectConfigHistoryDay(dayKey, entries));
|
||||
}
|
||||
days.append(node);
|
||||
}
|
||||
panel.append(days); calendar.append(panel);
|
||||
}
|
||||
if (!months.length) calendar.append(el("p", t("configHistoryEmpty"), "empty"));
|
||||
}
|
||||
async function loadConfigHistory() {
|
||||
$("config-history-summary").textContent = t("configHistoryLoading");
|
||||
try {
|
||||
const result = await api("/api/config/history?limit=512"); configHistoryEntries = result.revisions || []; renderConfigHistory();
|
||||
const latest = configHistoryEntries[0]; if (latest) selectConfigHistoryDay(historyDayKey(latest.time), configHistoryEntries.filter(entry => historyDayKey(entry.time) === historyDayKey(latest.time)));
|
||||
} catch (error) { $("config-history-summary").textContent = t("configHistoryFailed"); toast(error.message, true); }
|
||||
}
|
||||
$("config-history-open").addEventListener("click", () => { const dialog = $("config-history-dialog"); if (!dialog.open) dialog.showModal(); void loadConfigHistory(); });
|
||||
$("config-history-close").addEventListener("click", () => $("config-history-dialog").close());
|
||||
$("config-history-reload").addEventListener("click", () => void loadConfigHistory());
|
||||
@@ -6,7 +6,7 @@ function renderSettings() {
|
||||
if (!settingsDirty.has("response-form")) {
|
||||
const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {};
|
||||
$("expose-request-id").checked = !!diagnostics.request_id;
|
||||
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.2";
|
||||
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.3";
|
||||
for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name];
|
||||
$("compress-min").value = compression.min_bytes ?? 1024; syncServerMode();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user