dashboard: icon fallback on real img load failure, not container-state guess
This commit is contained in:
parent
16f614f45d
commit
ab1f8d6e33
3 changed files with 42 additions and 23 deletions
|
|
@ -578,20 +578,27 @@
|
|||
const pending = transientsState.get(c.name)?.kind || null;
|
||||
const li = el('li', { class: 'container-row' + (pending ? ' pending' : '') });
|
||||
|
||||
// Full-height square agent icon, left of the card body. A
|
||||
// background-image on a div (not <img>) contributes no intrinsic
|
||||
// size, so loading or failing it can't shift the row layout —
|
||||
// no broken-image glyph, no collapse. (issue #177)
|
||||
// Full-height square agent icon, left of the card body. The
|
||||
// icon is an <img> absolutely positioned inside a wrapper div:
|
||||
// the div is the flex child and sizes itself via aspect-ratio +
|
||||
// stretch, the <img> is out of flow so its load state — pending,
|
||||
// loaded or broken — can never contribute intrinsic size or
|
||||
// reflow the row. (issue #177)
|
||||
//
|
||||
// When the container is stopped or mid-transient (restarting,
|
||||
// rebuilding…) its web server isn't answering, so `<url>/icon`
|
||||
// would just fail to an empty box. Fall back to the dimmed
|
||||
// The icon points straight at the agent's `<url>/icon`. We don't
|
||||
// guess whether the agent is reachable from the container row —
|
||||
// we just let the <img> try, and if it actually fails to load
|
||||
// (agent stopped, restarting, rebuilding — web server not
|
||||
// answering) the error handler falls it back to the dimmed
|
||||
// hyperhive mark (`/favicon.svg`, served by the dashboard
|
||||
// itself, always reachable) instead. (issue #195)
|
||||
const reachable = c.running && !pending;
|
||||
const icon = el('div', {
|
||||
class: 'container-icon' + (reachable ? '' : ' icon-unreachable'),
|
||||
style: `background-image:url("${reachable ? `${url}icon` : '/favicon.svg'}")`,
|
||||
// itself, always reachable). (issues #195, #202)
|
||||
const iconImg = el('img', { class: 'container-icon-img', src: `${url}icon`, alt: '' });
|
||||
const icon = el('div', { class: 'container-icon' }, iconImg);
|
||||
iconImg.addEventListener('error', () => {
|
||||
if (iconImg.dataset.fallback) return; // guard: don't loop if the favicon itself 404s
|
||||
iconImg.dataset.fallback = '1';
|
||||
icon.classList.add('icon-unreachable');
|
||||
iconImg.src = '/favicon.svg';
|
||||
});
|
||||
// Card body: the three stacked content lines, right of the icon.
|
||||
const body = el('div', { class: 'card-body' });
|
||||
|
|
|
|||
|
|
@ -89,17 +89,26 @@ a:hover {
|
|||
gap: 0.7em;
|
||||
}
|
||||
.container-row:not(.tombstone) > .container-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(17, 17, 27, 0.6);
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
/* Stopped / mid-transient container: the dimmed hyperhive mark stands
|
||||
in for the unreachable agent icon (issue #195). */
|
||||
/* The icon image fills the square wrapper and is taken out of flow
|
||||
(absolute) so its load state — pending, loaded, broken — can never
|
||||
contribute intrinsic size or reflow the row. (issue #177) */
|
||||
.container-row:not(.tombstone) > .container-icon > .container-icon-img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
/* When the <img> fails to load it falls back to the dimmed hyperhive
|
||||
mark, standing in for the unreachable agent icon (issues #195, #202). */
|
||||
.container-row:not(.tombstone) > .container-icon.icon-unreachable {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.4;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue