This commit is contained in:
Mateusz Gruszczyński committed 2026-11-09 23:02:55 +01:00
1 parent fe945f51a5
commit 88728bdd5a
13 files changed
+320 -17

No files matched your search

+28 -5
View File
@@ -10,6 +10,7 @@ window.rustProxyInlineLayout = (() => {
const hiddenCount = $("overview-inline-hidden-count");
const modalButton = $("overview-layout-open");
const compactButton = $("overview-inline-compact");
const autoFitToggle = $("overview-inline-autofit");
let draft = null;
let original = null;
let opening = false;
@@ -29,7 +30,8 @@ window.rustProxyInlineLayout = (() => {
const dirty = () => draft && original && (draft.order.some((id, i) => id !== original.order[i])
|| [...draft.hidden].some(id => !original.hidden.has(id)) || [...original.hidden].some(id => !draft.hidden.has(id))
|| JSON.stringify(draft.sizes) !== JSON.stringify(original.sizes)
|| JSON.stringify(normalizeDashboardWidgetHeights(draft.heights)) !== JSON.stringify(normalizeDashboardWidgetHeights(original.heights)));
|| JSON.stringify(normalizeDashboardWidgetHeights(draft.heights)) !== JSON.stringify(normalizeDashboardWidgetHeights(original.heights))
|| draft.autoFit !== original.autoFit);
function renderHidden() {
if (!hiddenList || !draft) return;
@@ -67,7 +69,13 @@ window.rustProxyInlineLayout = (() => {
const span = dashboardWidgetWidth(node.dataset.dashboardWidget, draft.sizes);
const at = widths.indexOf(span);
const value = node.querySelector(".overview-inline-width-value");
if (value) value.textContent = `${span}/12`;
if (value) {
value.textContent = `${span}/12`;
// Auto-fit may show the card wider on this screen than its saved width.
const fitted = window.rustProxyOverviewRows?.fitFor?.(node.dataset.dashboardWidget);
value.classList.toggle("is-fitted", !!fitted && draft.autoFit !== false);
value.title = fitted && draft.autoFit !== false ? t("overviewLayoutAutoFitWidened").replace("{span}", String(fitted)) : t("overviewLayoutWidth");
}
const narrow = node.querySelector(".overview-inline-narrow");
const widen = node.querySelector(".overview-inline-widen");
if (narrow) narrow.disabled = at <= 0;
@@ -180,6 +188,8 @@ window.rustProxyInlineLayout = (() => {
hint.hidden = false;
hint.textContent = t("overviewLayoutInlineHint");
if (compactButton) compactButton.title = t("overviewLayoutInlineCompactHint");
const autoFitLabel = $("overview-inline-autofit-label");
if (autoFitLabel) autoFitLabel.title = t("overviewLayoutAutoFitHint");
if (hiddenShelf) { hiddenShelf.hidden = false; hiddenShelf.open = false; }
if (modalButton) modalButton.disabled = true;
for (const node of nodes()) toolbar(node);
@@ -198,7 +208,10 @@ window.rustProxyInlineLayout = (() => {
if (hiddenShelf) { hiddenShelf.open = false; hiddenShelf.hidden = true; }
if (hiddenList) hiddenList.replaceChildren();
if (modalButton) modalButton.disabled = false;
if (!preserveSaved && original) applyDashboardLayout(original.order, [...original.hidden], original.sizes, original.heights);
if (!preserveSaved && original) {
window.rustProxyOverviewRows?.setAutoFit?.(original.autoFit);
applyDashboardLayout(original.order, [...original.hidden], original.sizes, original.heights);
}
draft = original = null;
// Recompute from scratch so the saved view is exactly what was arranged.
window.rustProxyOverviewRows?.flush?.();
@@ -214,8 +227,10 @@ window.rustProxyInlineLayout = (() => {
const hidden = new Set(normalizeHiddenDashboardWidgets(saved.dashboard_hidden_widgets, saved.dashboard_widgets));
const sizes = normalizeDashboardWidgetSizes(saved.dashboard_widget_sizes);
const heights = normalizeDashboardWidgetHeights(saved.dashboard_widget_heights);
original = { order: [...order], hidden: new Set(hidden), sizes: sizes === null ? null : { ...sizes }, heights: heights ? { ...heights } : null };
draft = { order: [...order], hidden: new Set(hidden), sizes: sizes === null ? null : { ...sizes }, heights: heights ? { ...heights } : null };
const autoFit = saved.dashboard_auto_fit !== false;
original = { order: [...order], hidden: new Set(hidden), sizes: sizes === null ? null : { ...sizes }, heights: heights ? { ...heights } : null, autoFit };
draft = { order: [...order], hidden: new Set(hidden), sizes: sizes === null ? null : { ...sizes }, heights: heights ? { ...heights } : null, autoFit };
if (autoFitToggle) autoFitToggle.checked = autoFit;
showEditing();
} catch (error) { if (error?.status !== 401) toast(error.message, true); }
finally { opening = false; unlock.disabled = false; }
@@ -230,6 +245,7 @@ window.rustProxyInlineLayout = (() => {
dashboard_widgets: [...draft.order], dashboard_hidden_widgets: [...draft.hidden],
dashboard_widget_sizes: draft.sizes === null ? null : { ...draft.sizes },
dashboard_widget_heights: normalizeDashboardWidgetHeights(draft.heights),
dashboard_auto_fit: draft.autoFit !== false,
});
finish(true);
toast(t("overviewLayoutEditorSaved"));
@@ -417,6 +433,12 @@ window.rustProxyInlineLayout = (() => {
});
}
compactButton?.addEventListener("click", compact);
autoFitToggle?.addEventListener("change", () => {
if (!draft) return;
draft.autoFit = autoFitToggle.checked;
window.rustProxyOverviewRows?.setAutoFit?.(draft.autoFit);
paint();
});
unlock?.addEventListener("click", () => { if (draft) void save(); else void begin(); });
saveButton?.addEventListener("click", () => { void save(); });
cancelButton?.addEventListener("click", cancel);
@@ -426,6 +448,7 @@ window.rustProxyInlineLayout = (() => {
order: [...draft.order], hidden: [...draft.hidden],
sizes: draft.sizes === null ? null : { ...draft.sizes },
heights: draft.heights ? { ...draft.heights } : null,
autoFit: draft.autoFit !== false,
} : null,
cancel,
};
+201 -3
View File
@@ -5,14 +5,24 @@
// (dashboard_widget_heights) can only make it taller, and charts stretch into
// the extra space. Below the narrow breakpoint the grid falls back to content
// height (CSS sets --overview-row-units: off).
// With auto-fit on (the default) the layout is packed for the current screen:
// every card keeps its saved width (but at least a readable minimum for its type),
// cards are placed in saved order into the first free spot from the top, stat
// tiles of one row get one height, remaining holes are filled by widening a
// neighbouring card sideways, and small steps at the bottom are closed. The
// saved widths, heights and order are never changed.
window.rustProxyOverviewRows = (() => {
const grid = document.getElementById("dashboard-overview-grid");
// A compact stat card spans ROW_DIVISIONS rows; finer rows keep taller cards
// close to their natural height instead of rounding up by a whole stat card.
const MAX_ROWS = 32, ROW_DIVISIONS = 2, FALLBACK_STAT = 84, MIN_INTERVAL_MS = 1500, SHRINK_CONFIRMATIONS = 3;
const noop = { schedule() { }, flush() { }, setHeights() { }, rowsFor() { return null; } };
// Auto-fit (dashboard_auto_fit): the smallest readable width of a card by its
// registry display type, in CSS pixels. Cards narrower than this on the current
// screen get more columns; the saved widths are not changed.
const MIN_CARD_WIDTH = { value: 150, value_chart: 210, chart: 300, panel: 340, table: 640 };
const noop = { schedule() { }, flush() { }, setHeights() { }, setAutoFit() { }, reveal() { }, dataReady() { }, rowsFor() { return null; }, fitFor() { return null; } };
if (!grid) return noop;
let heights = null, frame = 0, lastRun = 0, lastWidth = 0;
let heights = null, frame = 0, lastRun = 0, lastWidth = 0, autoFit = true;
const state = new Map();
const shrinkVotes = new Map();
const cards = () => [...grid.children].filter(node => node.dataset.dashboardWidget);
@@ -25,8 +35,41 @@ window.rustProxyOverviewRows = (() => {
grid.classList.remove("has-row-units");
grid.style.removeProperty("--overview-row-unit");
for (const node of cards()) { node.style.removeProperty("--overview-rows"); delete node.dataset.dashboardRows; }
clearFit();
state.clear(); shrinkVotes.clear();
}
function clearFit() {
grid.classList.remove("has-auto-placement");
for (const node of cards()) {
for (const name of ["--overview-col", "--overview-cols", "--overview-row"]) node.style.removeProperty(name);
}
for (const node of cards()) {
if (node.dataset.dashboardFit === undefined) continue;
node.style.removeProperty("--overview-widget-placement");
delete node.dataset.dashboardFit;
}
}
function setSpan(node, span, columns) {
node.style.setProperty("--overview-widget-placement", span >= columns ? "1/-1" : `auto/span ${span}`);
node.dataset.dashboardFit = String(Math.min(span, columns));
}
const spanOf = card => card.last - card.first + 1;
function widgetType(node) {
return typeof dashboardWidgetType === "function" ? dashboardWidgetType(node.dataset.dashboardWidget) : "value";
}
// Widen cards that would be narrower than their readable minimum on this screen.
function widenToMinimum(items, geometry) {
const columns = geometry.starts.length;
const column = geometry.starts.length > 1 ? geometry.starts[1] - geometry.starts[0] : 0;
if (!column) return;
const columnGap = column - geometry.tracks[0];
for (const node of items) {
const card = place(node, geometry);
const minimum = MIN_CARD_WIDTH[widgetType(node)] ?? MIN_CARD_WIDTH.value;
const needed = Math.min(columns, Math.ceil((minimum + columnGap) / column - 0.02));
if (needed > spanOf(card)) setSpan(node, needed, columns);
}
}
function apply() {
frame = 0;
if (!grid.getClientRects().length || grid.classList.contains("is-overview-sorting")) return;
@@ -35,6 +78,7 @@ window.rustProxyOverviewRows = (() => {
lastWidth = grid.clientWidth;
const items = cards().filter(node => !node.hidden && node.getClientRects().length);
if (!items.length) return;
clearFit();
// Measure as the saved (locked) dashboard: editing styles move a few
// controls inside cards, which must not change the layout being edited.
const editing = grid.classList.contains("is-unlocked");
@@ -43,6 +87,10 @@ window.rustProxyOverviewRows = (() => {
// Bounding boxes include CSS zoom (Settings → Interface scale); grid tracks,
// gaps and the row unit are in CSS pixels, so divide measurements by it.
const zoom = zoomOf();
if (autoFit) {
const preliminary = gridGeometry(1, zoom);
if (preliminary) widenToMinimum(items, preliminary);
}
const natural = items.map(node => node.getBoundingClientRect().height / zoom);
grid.classList.remove("is-measuring-rows");
if (editing) grid.classList.add("is-unlocked");
@@ -76,9 +124,132 @@ window.rustProxyOverviewRows = (() => {
grid.classList.add("has-row-units");
const geometry = gridGeometry(unit + gap, zoom);
if (!geometry) return;
if (autoFit) { packCards(items, geometry, isStat); return; }
equalizeStatRows(items.filter(isStat), geometry);
alignBottom(items, geometry);
}
// Auto-fit packing on a model of the grid (columns × rows), then explicit
// placement through CSS variables. Pure arithmetic: no layout passes per card.
function packCards(items, geometry, isStat) {
const columns = geometry.starts.length;
const cardsModel = items.map(node => {
const flow = place(node, geometry);
return { node, stat: isStat(node), w: Math.min(columns, spanOf(flow)), design: spanOf(flow), h: rowsOf(node), r: 0, c: 0 };
});
let occupancy = [];
const free = (r, c, w, h) => {
for (let y = r; y < r + h; y++) {
const row = occupancy[y];
if (!row) continue;
for (let x = c; x < c + w; x++) if (row[x]) return false;
}
return true;
};
const mark = (r, c, w, h, value = 1) => {
for (let y = r; y < r + h; y++) {
const row = occupancy[y] || (occupancy[y] = new Uint8Array(columns));
for (let x = c; x < c + w; x++) row[x] = value;
}
};
const pack = () => {
occupancy = [];
for (const card of cardsModel) {
let placed = false;
for (let r = 0; !placed; r++) {
for (let c = 0; c + card.w <= columns; c++) {
if (free(r, c, card.w, card.h)) { card.r = r; card.c = c; mark(r, c, card.w, card.h); placed = true; break; }
}
}
}
};
// Stat tiles starting on the same row share the tallest height; re-pack until
// stable (raising a row can move later cards).
const settle = () => {
for (let pass = 0; pass < 6; pass++) {
pack();
const tallest = new Map();
for (const card of cardsModel) if (card.stat) tallest.set(card.r, Math.max(tallest.get(card.r) || 0, card.h));
let changed = false;
for (const card of cardsModel) {
const target = card.stat ? tallest.get(card.r) : card.h;
if (target !== card.h) { card.h = target; changed = true; }
}
if (!changed) break;
}
return Math.max(...cardsModel.map(card => card.r + card.h));
};
let total = settle();
// A card whose side neighbour ends slightly lower (up to one stat tile) is
// extended to that edge, so the cards below start on one line instead of a
// staggered notch. Each extension is kept only while the whole dashboard
// grows by at most two stat tiles in total, so it can never cascade.
const snap = ROW_DIVISIONS, budget = total + ROW_DIVISIONS * 2;
for (let attempt = 0; attempt < cardsModel.length; attempt++) {
const candidates = [];
for (const card of cardsModel) {
const end = card.r + card.h;
let best = end;
for (const other of cardsModel) {
const touching = other.c === card.c + card.w || other.c + other.w === card.c;
const otherEnd = other.r + other.h;
if (touching && other.r < end && otherEnd > end && otherEnd - end <= snap) best = Math.max(best, otherEnd);
}
if (best !== end && !card.snapTried) candidates.push([card, best - card.r]);
}
if (!candidates.length) break;
const [card, height] = candidates[0];
card.snapTried = true;
const saved = cardsModel.map(item => item.h), previous = card.h;
card.h = height;
const next = settle();
if (next > budget) { cardsModel.forEach((item, index) => { item.h = saved[index]; }); card.h = previous; total = settle(); }
else total = next;
}
// Holes beside a card (over its whole height) are taken by widening it,
// first to the right, then to the left.
const order = [...cardsModel].sort((a, b) => a.r - b.r || a.c - b.c);
const columnFree = (x, r, h) => {
for (let y = r; y < r + h; y++) if (occupancy[y]?.[x]) return false;
return true;
};
for (const card of order) {
while (card.c + card.w < columns && columnFree(card.c + card.w, card.r, card.h)) { mark(card.r, card.c + card.w, 1, card.h); card.w++; }
while (card.c > 0 && columnFree(card.c - 1, card.r, card.h)) { card.c--; card.w++; mark(card.r, card.c, 1, card.h); }
}
// Free cells directly above a card (left by a taller neighbour that started
// earlier) are taken by growing it upwards, within the same limit.
const rowFree = (y, c, w) => {
if (y < 0) return false;
for (let x = c; x < c + w; x++) if (occupancy[y]?.[x]) return false;
return true;
};
for (const card of order) {
let up = 0;
while (up < Math.max(ROW_DIVISIONS * 2, card.h) && rowFree(card.r - up - 1, card.c, card.w)) up++;
if (up) { mark(card.r - up, card.c, card.w, up); card.r -= up; card.h += up; }
}
// Steps below a card (up to two stat tiles or its own height) are closed by
// stretching it down to the next card or the end of the grid; charts and
// stacks inside grow into the extra height.
const bottom = Math.max(...cardsModel.map(card => card.r + card.h));
for (const card of order) {
const end = card.r + card.h;
let next = bottom;
for (const other of cardsModel) {
if (other !== card && other.c < card.c + card.w && other.c + other.w > card.c && other.r >= end) next = Math.min(next, other.r);
}
const extra = next - end;
if (extra > 0 && extra <= Math.max(ROW_DIVISIONS * 2, card.h)) { mark(end, card.c, card.w, extra); card.h += extra; }
}
for (const card of cardsModel) {
card.node.style.setProperty("--overview-col", String(card.c + 1));
card.node.style.setProperty("--overview-cols", String(card.w));
card.node.style.setProperty("--overview-row", String(card.r + 1));
setRows(card.node, card.h);
if (card.w !== card.design) card.node.dataset.dashboardFit = String(card.w);
}
grid.classList.add("has-auto-placement");
}
function setRows(node, rows) {
if (node.dataset.dashboardRows === String(rows)) return;
node.style.setProperty("--overview-rows", String(rows));
@@ -99,7 +270,7 @@ window.rustProxyOverviewRows = (() => {
const starts = [];
tracks.reduce((x, width) => { starts.push(x); return x + width + columnGap; }, 0);
return {
starts, step, zoom,
starts, step, zoom, tracks,
left: (parseFloat(style.borderLeftWidth) || 0) + (parseFloat(style.paddingLeft) || 0),
top: (parseFloat(style.borderTopWidth) || 0) + (parseFloat(style.paddingTop) || 0),
};
@@ -167,6 +338,26 @@ window.rustProxyOverviewRows = (() => {
heights = value && typeof value === "object" ? { ...value } : null;
schedule(true);
}
// The grid is shown (see is-layout-pending) once the saved layout has been
// applied (reveal) and the first live data has been rendered (dataReady), and
// the layout is computed from scratch at that moment, so nothing moves after.
let layoutReady = false, contentReady = false;
function show() {
if (!layoutReady || !contentReady || !grid.classList.contains("is-layout-pending")) return;
flush();
grid.classList.remove("is-layout-pending");
}
function reveal() { layoutReady = true; show(); }
function dataReady() { contentReady = true; show(); }
// Never leave the dashboard hidden if preferences cannot be loaded.
setTimeout(() => grid.classList.contains("is-layout-pending") && grid.classList.remove("is-layout-pending"), 4000);
function setAutoFit(value) {
const next = value !== false;
if (next === autoFit) return;
autoFit = next;
grid.dataset.overviewAutoFit = String(autoFit);
schedule(true);
}
if (typeof ResizeObserver === "function") {
new ResizeObserver(() => {
if (grid.clientWidth && grid.clientWidth !== lastWidth) { shrinkVotes.clear(); schedule(true); }
@@ -177,6 +368,13 @@ window.rustProxyOverviewRows = (() => {
schedule,
flush,
setHeights,
setAutoFit,
reveal,
dataReady,
fitFor(id) {
const node = cards().find(item => item.dataset.dashboardWidget === id);
return node?.dataset.dashboardFit ? Number(node.dataset.dashboardFit) : null;
},
rowsFor(id) {
return grid.classList.contains("has-row-units") ? state.get(id) || null : null;
},
+13 -6
View File
@@ -14,7 +14,7 @@ const ADMIN_DEFAULT_HIDDEN_WIDGETS = Object.freeze([...new Set([
const ADMIN_APPEARANCE_DEFAULTS = Object.freeze({
theme: "dark", language: "auto", ui_scale: 100, time_format: "24h", chart_style: "default",
chart_axes: false, interface_icons: false, logo_animation: false,
button_style: "normal", corner_style: "default", table_density: "normal", dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS], dashboard_widget_sizes: null, dashboard_widget_heights: null,
button_style: "normal", corner_style: "default", table_density: "normal", dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS], dashboard_widget_sizes: null, dashboard_widget_heights: null, dashboard_auto_fit: true,
});
const CHART_STYLE_KEYS = Object.freeze({ default: "chartStyleDefault", soft: "chartStyleSoft", technical: "chartStyleTechnical", high_contrast: "chartStyleHighContrast", neon: "chartStyleNeon", monochrome: "chartStyleMonochrome", warm: "chartStyleWarm", cool: "chartStyleCool", sunset: "chartStyleSunset", aurora: "chartStyleAurora", ocean: "chartStyleOcean", forest: "chartStyleForest", pastel: "chartStylePastel", terminal: "chartStyleTerminal", blueprint: "chartStyleBlueprint", colorblind: "chartStyleColorblind" });
let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(), adminPreferencesUnavailable = false;
@@ -295,6 +295,8 @@ function applyAdminPreferences(value, cache = true) {
const savedHidden = normalizeHiddenDashboardWidgets(value.dashboard_hidden_widgets, value.dashboard_widgets);
const savedSizes = normalizeDashboardWidgetSizes(value.dashboard_widget_sizes);
const savedHeights = normalizeDashboardWidgetHeights(value.dashboard_widget_heights);
const savedAutoFit = value.dashboard_auto_fit !== false;
window.rustProxyOverviewRows?.setAutoFit?.(editing ? editing.autoFit !== false : savedAutoFit);
if (editing) applyDashboardLayout(editing.order, editing.hidden, editing.sizes, editing.heights);
else applyDashboardLayout(savedOrder, savedHidden, savedSizes, savedHeights);
const normalized = {
@@ -302,7 +304,7 @@ function applyAdminPreferences(value, cache = true) {
refresh: normalizeRefreshPreferences(value.refresh),
theme: applyAdminTheme(value.theme, cache), language: applyAdminLanguage(value.language, cache, false), ui_scale: applyAdminUiScale(value.ui_scale, cache),
time_format: applyAdminTimeFormat(value.time_format), chart_style: applyAdminChartStyle(value.chart_style), chart_axes: applyAdminChartAxes(value.chart_axes), interface_icons: applyAdminInterfaceIcons(value.interface_icons), logo_animation: applyAdminLogoAnimation(value.logo_animation), button_style: applyAdminButtonStyle(value.button_style), corner_style: applyAdminCornerStyle(value.corner_style, cache), table_density: applyAdminTableDensity(value.table_density),
dashboard_density: applyAdminDashboardDensity(value.dashboard_density), dashboard_widgets: savedOrder, dashboard_hidden_widgets: savedHidden, dashboard_widget_sizes: savedSizes, dashboard_widget_heights: savedHeights,
dashboard_density: applyAdminDashboardDensity(value.dashboard_density), dashboard_widgets: savedOrder, dashboard_hidden_widgets: savedHidden, dashboard_widget_sizes: savedSizes, dashboard_widget_heights: savedHeights, dashboard_auto_fit: savedAutoFit,
};
adminPreferences = normalized;
window.rustProxyRefresh?.update(normalized.refresh);
@@ -313,11 +315,16 @@ function applyAdminPreferences(value, cache = true) {
window.rustProxyNotFound?.applyPreferences(normalized);
window.rustProxyStreams?.applyPreferences(normalized);
if (languagePacks.size) translate();
refreshAppearanceUi(); return normalized;
refreshAppearanceUi();
window.rustProxyOverviewRows?.reveal?.();
return normalized;
}
try { applyAdminTheme(localStorage.getItem("rp-theme") || "dark", false); } catch { applyAdminTheme("dark", false); }
try { applyAdminUiScale(localStorage.getItem("rp-ui-scale") || 100, false); } catch { applyAdminUiScale(100, false); }
try { applyAdminCornerStyle(localStorage.getItem("rp-corner-style") || "default", false); } catch { applyAdminCornerStyle("default", false); }
// No Overview layout is kept in the browser: the grid stays invisible until the
// account's layout from the server is applied (rustProxyOverviewRows.reveal).
try { localStorage.removeItem("rp-dashboard-layout"); } catch { }
async function loadAdminPreferences(force = false) {
if (!authenticated) return adminPreferences;
@@ -350,7 +357,7 @@ function updateAdminPreferences(patch) {
next.refresh = normalizeRefreshPreferences(next.refresh);
next.theme = normalizeAdminTheme(next.theme); next.language = normalizeAdminLanguage(next.language); next.ui_scale = normalizeAdminUiScale(next.ui_scale);
next.time_format = normalizeAdminTimeFormat(next.time_format); next.chart_style = normalizeAdminChartStyle(next.chart_style); next.chart_axes = next.chart_axes === true; next.interface_icons = next.interface_icons === true; next.logo_animation = next.logo_animation === true; next.button_style = normalizeAdminButtonStyle(next.button_style); next.corner_style = normalizeAdminCornerStyle(next.corner_style); next.table_density = normalizeAdminDensity(next.table_density);
next.dashboard_density = normalizeAdminDensity(next.dashboard_density, "compact"); next.dashboard_widgets = normalizeDashboardWidgets(next.dashboard_widgets); next.dashboard_hidden_widgets = normalizeHiddenDashboardWidgets(next.dashboard_hidden_widgets); next.dashboard_widget_sizes = normalizeDashboardWidgetSizes(next.dashboard_widget_sizes); next.dashboard_widget_heights = normalizeDashboardWidgetHeights(next.dashboard_widget_heights);
next.dashboard_density = normalizeAdminDensity(next.dashboard_density, "compact"); next.dashboard_widgets = normalizeDashboardWidgets(next.dashboard_widgets); next.dashboard_hidden_widgets = normalizeHiddenDashboardWidgets(next.dashboard_hidden_widgets); next.dashboard_widget_sizes = normalizeDashboardWidgetSizes(next.dashboard_widget_sizes); next.dashboard_widget_heights = normalizeDashboardWidgetHeights(next.dashboard_widget_heights); next.dashboard_auto_fit = next.dashboard_auto_fit !== false;
const saved = await api("/api/preferences", { method: "PUT", body: JSON.stringify(next) });
if (!authenticated || epoch !== authEpoch) return adminPreferences;
return applyAdminPreferences(saved);
@@ -481,9 +488,9 @@ async function resetAppearancePatch(patch, control, messageKey = "appearanceSect
document.querySelectorAll("[data-appearance-reset]").forEach(control => control.addEventListener("click", () => {
const patches = {
interface: { theme: "dark", language: "auto", ui_scale: 100, time_format: "24h", interface_icons: false, logo_animation: false, button_style: "normal", corner_style: "default" }, charts: { chart_style: "default", chart_axes: false }, tables: { table_density: "normal" },
dashboard: { dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets], dashboard_widget_sizes: null, dashboard_widget_heights: null },
dashboard: { dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets], dashboard_widget_sizes: null, dashboard_widget_heights: null, dashboard_auto_fit: true },
};
void resetAppearancePatch(patches[control.dataset.appearanceReset] || {}, control);
}));
$("appearance-reset")?.addEventListener("click", () => void resetAppearancePatch({ ...ADMIN_APPEARANCE_DEFAULTS, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets], dashboard_widget_sizes: null, dashboard_widget_heights: null }, $("appearance-reset"), "appearanceResetDone"));
$("appearance-reset")?.addEventListener("click", () => void resetAppearancePatch({ ...ADMIN_APPEARANCE_DEFAULTS, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets], dashboard_widget_sizes: null, dashboard_widget_heights: null, dashboard_auto_fit: true }, $("appearance-reset"), "appearanceResetDone"));
refreshAppearanceUi();
+1
View File
@@ -667,6 +667,7 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
renderProcessOverview(d, status, live, systemSample);
window.rustProxyMemoryBreakdown?.render(status, appendSample);
window.rustProxyOverviewRows?.schedule();
window.rustProxyOverviewRows?.dataReady?.();
updateCacheMemorySizing();
const networkRateSupported = live.network_rx_bytes_per_second != null && live.network_tx_bytes_per_second != null;
if (systemSample) setOptionalMetric("system-network", d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null, `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}`);