Files
FabledSteward/steward/static/dash-layout.js
T
bvandeusenandClaude Opus 5.5 8eedae365b
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m1s
CI / unit (push) Failing after 1m19s
CI / publish (push) Skipped
fix(dashboard): heights fit content to 10px, not a whole 70px row; text columns read from the left (#5214)
View mode splits each saved row into seven, so a widget no longer carries up
to 69px of empty space below its content (Overview, SNMP). Edit mode and the
saved layout keep 70px rows. The Busiest Containers host column aligns left
like its header, its note fits one line, and the Hosts column reads Disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:54:24 -04:00

104 lines
4.2 KiB
JavaScript

/* Dashboard layout (#5249): each widget as tall as its content, never taller
than the height saved for it, and extra grid columns on wide screens.
The layout is saved in 12 columns. A viewer on a wider screen gets more
columns (one per COL_PX of grid width, up to MAX_COLS), and the widgets are
placed again in reading order at their saved widths, so more of them sit
side by side; none is stretched. Edit mode always shows the saved 12-column
layout, so what is saved never depends on the screen it was edited on.
Outside edit mode each saved row is split into ROW_SPLIT finer rows, so a
widget's height fits its content to within 10px rather than to a whole
70px row (#5214). viewed() gives the saved place in those rows.
Used by the dashboard (Gridstack) and the share view (CSS grid). */
(function () {
var COL_PX = 110, MIN_COLS = 12, MAX_COLS = 24, NARROW_PX = 700;
var ROW_SPLIT = 7, MIN_ROWS = ROW_SPLIT; // view rows: one saved row is the least
function columnsFor(width) {
if (width < NARROW_PX) return 1;
return Math.max(MIN_COLS, Math.min(MAX_COLS, Math.floor(width / COL_PX)));
}
/* items: [{id, x, y, w, h}] from the saved layout; h is the height to place
(the content-fitted one). Returns {id: {x, y, w, h}}.
Reading order (y, then x) is kept: nothing is placed above the item before
it. At 12 columns an item keeps its saved column and floats up, which is the
saved layout minus the space short widgets no longer need. Narrower or wider,
it takes the first free spot. */
function pack(items, cols) {
var taken = [];
function free(x, y, w, h) {
for (var r = y; r < y + h; r++) {
if (!taken[r]) continue;
for (var c = x; c < x + w; c++) if (taken[r][c]) return false;
}
return true;
}
function take(x, y, w, h) {
for (var r = y; r < y + h; r++) {
taken[r] = taken[r] || [];
for (var c = x; c < x + w; c++) taken[r][c] = true;
}
}
var out = {}, minY = 0;
items.slice().sort(function (a, b) { return a.y - b.y || a.x - b.x; }).forEach(function (it) {
var w = Math.min(it.w, cols), h = it.h, x = 0, y;
if (cols === MIN_COLS) {
x = Math.min(it.x, cols - w);
for (y = 0; !free(x, y, w, h); y++) { /* float up as far as it fits */ }
} else {
search:
for (y = minY; ; y++) {
for (x = 0; x + w <= cols; x++) if (free(x, y, w, h)) break search;
}
}
take(x, y, w, h);
minY = y;
out[it.id] = { x: x, y: y, w: w, h: h };
});
return out;
}
/* The height a widget's box needs to show everything in it: its padding and
border, plus each child (header, body) at its natural height. The body
normally fills the box and scrolls, so it is measured unstretched. */
function naturalHeight(box) {
var body = box.querySelector('.dash-cell-body');
var cs = getComputedStyle(box);
var h = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom)
+ parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
if (body) { body.style.flex = '0 0 auto'; body.style.overflow = 'visible'; }
Array.prototype.forEach.call(box.children, function (child) {
var s = getComputedStyle(child);
h += child.offsetHeight + parseFloat(s.marginTop) + parseFloat(s.marginBottom);
});
if (body) { body.style.flex = ''; body.style.overflow = ''; }
return h;
}
/* View rows for that height: rowPx is one view row (with its share of the
gap), gapPx the space a cell loses to its margins. Capped at maxRows, the
saved height in view rows, where the body scrolls instead. */
function fitRows(box, rowPx, gapPx, maxRows) {
var rows = Math.ceil((naturalHeight(box) + gapPx) / rowPx);
return Math.max(MIN_ROWS, Math.min(maxRows, rows));
}
function saved(el) {
return { x: +el.dataset.x, y: +el.dataset.y, w: +el.dataset.w, h: +el.dataset.h };
}
function viewed(el) {
var s = saved(el);
return { x: s.x, y: s.y * ROW_SPLIT, w: s.w, h: s.h * ROW_SPLIT };
}
window.StewardDashLayout = {
MIN_COLS: MIN_COLS, ROW_SPLIT: ROW_SPLIT, columnsFor: columnsFor, pack: pack,
fitRows: fitRows, saved: saved, viewed: viewed,
};
})();