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
|
|
@ -160,12 +160,15 @@ the previous process's socket release resolves itself.
|
||||||
### Container row
|
### Container row
|
||||||
|
|
||||||
A full-height **square agent icon** on the left (the agent's
|
A full-height **square agent icon** on the left (the agent's
|
||||||
`/icon`, painted as a background-image div so its load state can
|
`/icon`, an `<img>` absolutely positioned inside a wrapper div so
|
||||||
never reflow the row), and the card body on the right with three
|
its load state can never reflow the row), and the card body on
|
||||||
stacked lines (`assets/app.js::renderContainers`). When the
|
the right with three stacked lines
|
||||||
container is stopped or mid-transient (its web server isn't
|
(`assets/app.js::renderContainers`). The `<img>` points straight
|
||||||
answering) the icon falls back to the dimmed hyperhive mark
|
at `<url>/icon`; if it actually fails to load (container stopped
|
||||||
(`/favicon.svg`) instead of an empty box.
|
or mid-transient, web server not answering) the `error` handler
|
||||||
|
falls it back to the dimmed hyperhive mark (`/favicon.svg`)
|
||||||
|
instead of an empty box — a real load-failure fallback, not a
|
||||||
|
guess from container state.
|
||||||
|
|
||||||
- Line 1: agent name (link → new tab), m1nd/ag3nt chip, status
|
- Line 1: agent name (link → new tab), m1nd/ag3nt chip, status
|
||||||
badges — `⊘ rate limited` (red, while the harness is parked
|
badges — `⊘ rate limited` (red, while the harness is parked
|
||||||
|
|
|
||||||
|
|
@ -578,20 +578,27 @@
|
||||||
const pending = transientsState.get(c.name)?.kind || null;
|
const pending = transientsState.get(c.name)?.kind || null;
|
||||||
const li = el('li', { class: 'container-row' + (pending ? ' pending' : '') });
|
const li = el('li', { class: 'container-row' + (pending ? ' pending' : '') });
|
||||||
|
|
||||||
// Full-height square agent icon, left of the card body. A
|
// Full-height square agent icon, left of the card body. The
|
||||||
// background-image on a div (not <img>) contributes no intrinsic
|
// icon is an <img> absolutely positioned inside a wrapper div:
|
||||||
// size, so loading or failing it can't shift the row layout —
|
// the div is the flex child and sizes itself via aspect-ratio +
|
||||||
// no broken-image glyph, no collapse. (issue #177)
|
// 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,
|
// The icon points straight at the agent's `<url>/icon`. We don't
|
||||||
// rebuilding…) its web server isn't answering, so `<url>/icon`
|
// guess whether the agent is reachable from the container row —
|
||||||
// would just fail to an empty box. Fall back to the dimmed
|
// 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
|
// hyperhive mark (`/favicon.svg`, served by the dashboard
|
||||||
// itself, always reachable) instead. (issue #195)
|
// itself, always reachable). (issues #195, #202)
|
||||||
const reachable = c.running && !pending;
|
const iconImg = el('img', { class: 'container-icon-img', src: `${url}icon`, alt: '' });
|
||||||
const icon = el('div', {
|
const icon = el('div', { class: 'container-icon' }, iconImg);
|
||||||
class: 'container-icon' + (reachable ? '' : ' icon-unreachable'),
|
iconImg.addEventListener('error', () => {
|
||||||
style: `background-image:url("${reachable ? `${url}icon` : '/favicon.svg'}")`,
|
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.
|
// Card body: the three stacked content lines, right of the icon.
|
||||||
const body = el('div', { class: 'card-body' });
|
const body = el('div', { class: 'card-body' });
|
||||||
|
|
|
||||||
|
|
@ -89,17 +89,26 @@ a:hover {
|
||||||
gap: 0.7em;
|
gap: 0.7em;
|
||||||
}
|
}
|
||||||
.container-row:not(.tombstone) > .container-icon {
|
.container-row:not(.tombstone) > .container-icon {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
flex: none;
|
flex: none;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background-color: rgba(17, 17, 27, 0.6);
|
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
|
/* The icon image fills the square wrapper and is taken out of flow
|
||||||
in for the unreachable agent icon (issue #195). */
|
(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 {
|
.container-row:not(.tombstone) > .container-icon.icon-unreachable {
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue