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>
106 lines
5.1 KiB
HTML
106 lines
5.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ dashboard.name }} — Steward</title>
|
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='logo/steward-mark.svg') }}">
|
|
<script src="{{ url_for('static', filename='vendor/htmx-2.0.4.min.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='vendor/chart-4.4.4.umd.min.js') }}"></script>
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='fonts/fonts.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='theme.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='steward.css') }}">
|
|
<script src="{{ url_for('static', filename='chart-colors.js') }}"></script>
|
|
<script defer src="{{ url_for('static', filename='dash-cells.js') }}"></script>
|
|
<script defer src="{{ url_for('static', filename='dash-layout.js') }}"></script>
|
|
{# The palette, type and widget classes come from steward.css like every
|
|
other page; only the read-only page's own frame and its grid live here. #}
|
|
<style>
|
|
.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: 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; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="share-header">
|
|
<span class="share-name">{{ dashboard.name }}</span>
|
|
<span style="font-size:0.78rem;color:var(--text-muted);">Steward · read-only view</span>
|
|
</header>
|
|
|
|
<main class="share-main">
|
|
{% if widgets %}
|
|
<div class="dash-grid">
|
|
{% for item in widgets %}
|
|
{% set gw = item.db %}
|
|
<div class="card ping-card dash-cell" data-id="{{ gw.id }}"
|
|
data-x="{{ gw.grid_x }}" data-y="{{ gw.grid_y }}" data-w="{{ gw.grid_w }}" data-h="{{ gw.grid_h }}"
|
|
style="grid-column:{{ gw.grid_x + 1 }} / span {{ gw.grid_w }};grid-row:{{ gw.grid_y + 1 }} / span {{ gw.grid_h }};">
|
|
<div class="widget-header">
|
|
<span class="widget-title" title="{{ item.title }}">{{ item.title }}</span>
|
|
<span class="widget-asof" aria-live="polite"></span>
|
|
</div>
|
|
{# Pass share token as &s= param so require_role allows through #}
|
|
<div class="dash-cell-body" id="widget-{{ gw.id }}"
|
|
hx-get="{{ item.hx_url }}&s={{ raw_token }}"
|
|
hx-trigger="load, retry{% if item.defn.poll %}, every {{ item.defn.poll_seconds or poll_interval }}s{% endif %}"
|
|
hx-request='{"timeout": 20000}'
|
|
data-poll="{{ '1' if item.defn.poll else '0' }}"
|
|
hx-swap="innerHTML"><div class="cell-state">Loading…</div></div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">This dashboard has no widgets.</div>
|
|
{% endif %}
|
|
</main>
|
|
|
|
<script>
|
|
/* Fit each cell to its content and place it for this screen's column count,
|
|
as on the signed-in dashboard (static/dash-layout.js). */
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var L = window.StewardDashLayout, grid = document.querySelector('.dash-grid');
|
|
if (!L || !grid) return;
|
|
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.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) {
|
|
var p = pos[el.dataset.id];
|
|
el.style.gridColumn = (p.x + 1) + ' / span ' + p.w;
|
|
el.style.gridRow = (p.y + 1) + ' / span ' + p.h;
|
|
});
|
|
}
|
|
var pending = null;
|
|
function fit(el) {
|
|
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) {
|
|
var el = e.detail.target && e.detail.target.closest && e.detail.target.closest('.dash-cell');
|
|
if (el) fit(el);
|
|
});
|
|
window.addEventListener('resize', function () {
|
|
clearTimeout(window._dashResize);
|
|
window._dashResize = setTimeout(function () { relayout(); cells().forEach(fit); }, 150);
|
|
});
|
|
relayout();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|