This commit is contained in:
Mateusz Gruszczyński committed 2026-10-16 13:02:53 +02:00
1 parent 162e986d37
commit 6b937aaf85
54 files changed
+3527 -1325

No files matched your search

+1 -1
View File
@@ -162,7 +162,7 @@ window.rustProxyInlineLayout = (() => {
draft = original = null;
}
async function begin() {
if (draft || opening || saving || $("overview-layout-dialog")?.open) return;
if (draft || opening || saving || $("overview-presets-dialog")?.open) return;
opening = true;
unlock.disabled = true;
try {
+144
View File
@@ -0,0 +1,144 @@
window.rustProxyDashboardLayout = (() => {
const dialog = $("overview-presets-dialog");
const list = $("overview-presets-list");
const input = $("overview-presets-name");
const message = $("overview-presets-message");
const save = $("overview-presets-save");
const factory = $("overview-presets-factory");
const close = $("overview-presets-close");
let presets = [];
let busy = false;
const presetPath = name => `/api/dashboard-presets/${encodeURIComponent(name)}`;
const nameValid = name => name.length > 0 && new TextEncoder().encode(name).length <= 64
&& !/[\x00-\x1f\x7f/]/.test(name);
const snapshot = () => {
const current = window.rustProxyPreferences.current() || ADMIN_APPEARANCE_DEFAULTS;
return {
dashboard_widgets: [...normalizeDashboardWidgets(current.dashboard_widgets)],
dashboard_hidden_widgets: [...normalizeHiddenDashboardWidgets(current.dashboard_hidden_widgets)],
dashboard_widget_sizes: normalizeDashboardWidgetSizes(current.dashboard_widget_sizes),
dashboard_density: normalizeAdminDensity(current.dashboard_density, "compact"),
overview_samples: window.rustProxyOverviewSamples.getLimit(),
};
};
const translateName = (key, name) => t(key).replace("{name}", name);
function notify(key, isError = false) {
message.textContent = isError ? key : t(key);
message.classList.toggle("error", isError);
}
function lock(value) {
busy = value;
for (const control of [save, factory, close, input, ...list.querySelectorAll("button")])
if (control) control.disabled = value;
}
async function run(fn) {
if (busy) return;
lock(true);
try { await fn(); }
catch (error) {
const message = error.status === 409
? (String(error.code || "").includes("limit (16)")
? t("dashboardPresetsLimit") : t("dashboardPresetsConflict"))
: (error.message || String(error));
notify(message, true);
}
finally { lock(false); }
}
async function reload() {
presets = await api("/api/dashboard-presets");
render();
}
function entryButton(key, className, fn, presetName) {
const button = el("button", t(key), className);
button.type = "button";
button.setAttribute("aria-label", `${t(key)}: ${presetName}`);
button.dataset.presetAction = key;
button.addEventListener("click", () => void run(fn));
return button;
}
function render() {
const elements = presets.map(preset => {
const row = el("div", undefined, "overview-presets-row");
row.setAttribute("role", "listitem");
const actions = el("div", undefined, "overview-presets-actions");
actions.append(
entryButton("dashboardPresetsLoad", "primary", async () => {
await apply(preset);
dialog.close();
toast(t("dashboardPresetsLoaded"));
}, preset.name),
entryButton("dashboardPresetsOverwrite", "outline", async () => {
if (!(await appConfirm(translateName("dashboardPresetsOverwriteConfirm", preset.name)))) return;
await api(presetPath(preset.name), { method: "PUT", body: JSON.stringify(snapshot()) });
await reload(); notify("dashboardPresetsSaved");
}, preset.name),
entryButton("dashboardPresetsRename", "quiet", async () => {
const name = input.value.trim();
if (!nameValid(name)) { notify("dashboardPresetsNameRequired"); return; }
if (name === preset.name) return;
await api(presetPath(preset.name), { method: "PATCH", body: JSON.stringify({ name }) });
input.value = "";
await reload(); notify("dashboardPresetsRenamed");
}, preset.name),
entryButton("dashboardPresetsDelete", "quiet", async () => {
if (!(await appConfirm(translateName("dashboardPresetsDeleteConfirm", preset.name), { danger: true }))) return;
await api(presetPath(preset.name), { method: "DELETE" });
await reload(); notify("dashboardPresetsDeleted");
}, preset.name),
);
row.append(el("strong", preset.name), actions);
return row;
});
list.replaceChildren(...(elements.length ? elements : [el("p", t("dashboardPresetsEmpty"), "fine")]));
}
async function apply(layout) {
await window.rustProxyPreferences.update({
dashboard_widgets: [...layout.dashboard_widgets],
dashboard_hidden_widgets: [...layout.dashboard_hidden_widgets],
dashboard_widget_sizes: layout.dashboard_widget_sizes === null ? null : { ...layout.dashboard_widget_sizes },
dashboard_density: layout.dashboard_density,
});
window.rustProxyOverviewSamples.setLimit(layout.overview_samples, true);
}
async function open() {
if (dialog?.open || busy || window.rustProxyInlineLayout?.isEditing?.()) return;
await run(async () => {
await window.rustProxyPreferences.load();
await reload();
input.value = "";
notify("");
dialog.showModal();
input.focus();
});
}
save?.addEventListener("click", () => void run(async () => {
const name = input.value.trim();
if (!nameValid(name)) { notify("dashboardPresetsNameRequired"); return; }
if (presets.length >= 16 && !presets.some(preset => preset.name === name)) {
notify("dashboardPresetsLimit"); return;
}
if (presets.some(preset => preset.name === name)
&& !(await appConfirm(translateName("dashboardPresetsOverwriteConfirm", name)))) return;
await api(presetPath(name), { method: "PUT", body: JSON.stringify(snapshot()) });
input.value = "";
await reload(); notify("dashboardPresetsSaved");
}));
factory?.addEventListener("click", () => void run(async () => {
if (!(await appConfirm(t("dashboardPresetsResetConfirm")))) return;
await apply({
dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS],
dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS],
dashboard_widget_sizes: null,
dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density,
overview_samples: ADMIN_DASHBOARD_DEFAULTS.overview_samples,
});
dialog.close();
toast(t("dashboardPresetsRestored"));
}));
close?.addEventListener("click", () => { if (!busy) dialog.close(); });
dialog?.addEventListener("cancel", event => { if (busy) event.preventDefault(); });
for (const id of ["overview-layout-open", "overview-layout-open-settings"])
$(id)?.addEventListener("click", () => { void open(); });
return { open, refresh: () => { if (dialog?.open) render(); } };
})();