diff --git a/plugins/docker/templates/docker/widget_resources.html b/plugins/docker/templates/docker/widget_resources.html index 33e0ba4..96f87e7 100644 --- a/plugins/docker/templates/docker/widget_resources.html +++ b/plugins/docker/templates/docker/widget_resources.html @@ -14,7 +14,7 @@ - + {% for r in rows %} {% set u = r.usage %} @@ -22,7 +22,7 @@ - + {% if u.cores is none %}{% else %}
ContainerHostCPUMemory
ContainerHostCPUMemory
{%- if session.user_id %}{{ r.label }}{% else %}{{ r.label }}{% endif -%} {{ r.host_name }}{{ r.host_name }}— @@ -37,5 +37,5 @@ {% endfor %}
-
CPU over the last {{ minutes }} minutes. Shading: share of the host's cores, and of the memory limit (or the host's memory when no limit is set).
+
CPU over the last {{ minutes }} min; shading is the share of the host.
{% endif %} diff --git a/steward/static/dash-layout.js b/steward/static/dash-layout.js index 36678e3..78b5787 100644 --- a/steward/static/dash-layout.js +++ b/steward/static/dash-layout.js @@ -7,9 +7,14 @@ 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, MIN_ROWS = 2; + 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; @@ -74,9 +79,9 @@ return h; } - /* Grid rows for that height: rowPx is one row plus the gap that follows it, - gapPx the space a cell loses to its margins. Capped at the saved height, - where the body scrolls instead. */ + /* 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)); @@ -86,7 +91,13 @@ 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, columnsFor: columnsFor, pack: pack, fitRows: fitRows, saved: saved, + MIN_COLS: MIN_COLS, ROW_SPLIT: ROW_SPLIT, columnsFor: columnsFor, pack: pack, + fitRows: fitRows, saved: saved, viewed: viewed, }; })(); diff --git a/steward/static/steward.css b/steward/static/steward.css index e61cdb6..24bce80 100644 --- a/steward/static/steward.css +++ b/steward/static/steward.css @@ -390,6 +390,7 @@ body.dash-editing .widget-drawer { transform:translateY(0); } .w-table th { text-align:right; font-weight:500; font-size:var(--fs-size-tiny); color:var(--text-dim); text-transform:uppercase; letter-spacing:var(--fs-tracking-tiny); padding:0 0 0.25rem 0.5rem; white-space:nowrap; } .w-table td { text-align:right; padding:0.2rem 0 0.2rem 0.5rem; border-top:1px solid var(--border); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .w-table th:first-child, .w-table td:first-child { text-align:left; padding-left:0; } +.w-table .w-text { text-align:left; } /* a text column after the name: reads from the left, like its header */ .w-table td.w-name { overflow:hidden; text-overflow:ellipsis; } .w-table td.w-name a { color:inherit; text-decoration:none; } .w-table td.w-name a:hover { text-decoration:underline; } diff --git a/steward/templates/dashboard/index.html b/steward/templates/dashboard/index.html index 7731738..762fd01 100644 --- a/steward/templates/dashboard/index.html +++ b/steward/templates/dashboard/index.html @@ -68,8 +68,9 @@ var L = window.StewardDashLayout; var CELL_PX = 70, MARGIN_PX = 8; + var VIEW_PX = CELL_PX / L.ROW_SPLIT; // view mode: finer rows, so heights fit closely var grid = GridStack.init({ - column: 12, cellHeight: CELL_PX, margin: MARGIN_PX, float: false, + column: 12, cellHeight: VIEW_PX, margin: MARGIN_PX, float: false, staticGrid: true, // view mode: positioned but inert handle: '.panel-drag', resizable: { handles: 'e, se, s' }, @@ -84,7 +85,7 @@ var cols = L.columnsFor(gridEl.clientWidth); var els = items(); var pos = L.pack(els.map(function (el) { - var s = L.saved(el); + var s = L.viewed(el); s.id = el.getAttribute('gs-id'); s.h = el._fitH || s.h; return s; @@ -100,7 +101,7 @@ function fit(el) { var box = el.querySelector('.grid-stack-item-content'); if (!box) return; - var rows = L.fitRows(box, CELL_PX, 2 * MARGIN_PX, L.saved(el).h); + var rows = L.fitRows(box, VIEW_PX, 2 * MARGIN_PX, L.viewed(el).h); if (rows !== el._fitH) { el._fitH = rows; relayoutSoon(); } } document.body.addEventListener('htmx:afterSettle', function (e) { @@ -135,6 +136,7 @@ // Edit mode shows the saved 12-column layout at saved heights, so a layout // edited on a wide screen is the same one a narrow screen starts from. function showSaved() { + grid.cellHeight(CELL_PX); grid.batchUpdate(); if (grid.getColumn() !== L.MIN_COLS) grid.column(L.MIN_COLS, 'none'); items().forEach(function (el) { grid.update(el, L.saved(el)); }); @@ -148,7 +150,7 @@ document.body.classList.toggle('dash-editing', on); var btn = document.getElementById('dash-edit-toggle'); if (btn) btn.textContent = on ? 'Done' : 'Edit'; - if (!on) { items().forEach(fit); relayout(); } + if (!on) { grid.cellHeight(VIEW_PX); items().forEach(fit); relayout(); } } var toggle = document.getElementById('dash-edit-toggle'); if (toggle) toggle.addEventListener('click', function () { setEditing(!editing); }); diff --git a/steward/templates/dashboard/share_view.html b/steward/templates/dashboard/share_view.html index aeacb08..277caea 100644 --- a/steward/templates/dashboard/share_view.html +++ b/steward/templates/dashboard/share_view.html @@ -19,10 +19,12 @@ .share-header { background: var(--bg-card); padding: 0 1.5rem; display: flex; align-items: center; justify-content: space-between; gap: 1rem; border-bottom: 1px solid var(--border-mid); min-height: 48px; } .share-name { font-family: var(--fs-font-display); font-weight: 500; font-size: 1.15rem; color: var(--gold); overflow-wrap: anywhere; } .share-main { max-width: 2400px; } - /* The same layout as the signed-in dashboard: rows 70px, gaps 1rem, and - static/dash-layout.js sets the columns and each cell's place and height. */ - .dash-grid { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-rows: 70px; gap: 1rem; } - .dash-grid > .dash-cell { margin-bottom: 0; min-width: 0; overflow: hidden; display: flex; flex-direction: column; } + /* The same layout as the signed-in dashboard: a saved row is 70px plus a + 1rem gap, split into ROW_SPLIT view rows (the gap is each cell's bottom + margin), and static/dash-layout.js sets the columns and each cell's + place and height. */ + .dash-grid { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-rows: calc((70px + 1rem) / 7); column-gap: 1rem; row-gap: 0; } + .dash-grid > .dash-cell { margin-bottom: 1rem; min-width: 0; overflow: hidden; display: flex; flex-direction: column; } .dash-cell .dash-cell-body { flex: 1; min-height: 0; overflow: auto; } @media (max-width: 820px) { .share-main { padding: 1rem; } @@ -69,12 +71,12 @@ document.addEventListener('DOMContentLoaded', function () { var L = window.StewardDashLayout, grid = document.querySelector('.dash-grid'); if (!L || !grid) return; - var ROW_PX = 70, GAP_PX = 16; + var GAP_PX = 16, VIEW_PX = (70 + GAP_PX) / L.ROW_SPLIT; function cells() { return Array.prototype.slice.call(grid.querySelectorAll('.dash-cell')); } function relayout() { var cols = L.columnsFor(grid.clientWidth); var pos = L.pack(cells().map(function (el) { - var s = L.saved(el); s.id = el.dataset.id; s.h = el._fitH || s.h; return s; + var s = L.viewed(el); s.id = el.dataset.id; s.h = el._fitH || s.h; return s; }), cols); grid.style.gridTemplateColumns = 'repeat(' + cols + ', 1fr)'; cells().forEach(function (el) { @@ -85,7 +87,7 @@ document.addEventListener('DOMContentLoaded', function () { } var pending = null; function fit(el) { - var rows = L.fitRows(el, ROW_PX + GAP_PX, GAP_PX, L.saved(el).h); + var rows = L.fitRows(el, VIEW_PX, GAP_PX, L.viewed(el).h); if (rows !== el._fitH) { el._fitH = rows; clearTimeout(pending); pending = setTimeout(relayout, 60); } } document.body.addEventListener('htmx:afterSettle', function (e) { diff --git a/steward/templates/hosts/overview_widget.html b/steward/templates/hosts/overview_widget.html index 1c42369..5005f3e 100644 --- a/steward/templates/hosts/overview_widget.html +++ b/steward/templates/hosts/overview_widget.html @@ -17,7 +17,7 @@ - + {% for r in rows %} {% set a = r.agent %}{% set fresh = a.get('fresh') %}
HostCPUMemDisk /Load
HostCPUMemDiskLoad