swarm-ui: the broadcast roster and the egress lists show placeholders while they load
The broadcast grid showed nothing while the agents loaded, and the egress card a 'loading the … domains' line. They now show grey placeholders in the shape of what is coming: six agent chips with the real chip's height (160 px, measured), and three domain rows with their remove buttons.
This commit is contained in:
parent
a9dd276935
commit
8847e6390b
4 changed files with 57 additions and 5 deletions
|
|
@ -611,6 +611,7 @@ to more than one agent at once, and the messages show up in the list below as th
|
|||
go. Folding it again keeps what you typed and ticked. Pick the
|
||||
agents from a grid of their icons. **running** shows the agents that report
|
||||
themselves running and **all** shows every agent; the browser keeps the choice.
|
||||
While the agents load, the grid shows grey placeholder chips of the same size.
|
||||
**select all** ticks every agent the filter shows, and **clear** unticks them.
|
||||
The composer skips an agent you ticked and then hid with the filter. Type the
|
||||
message and press **send to N agents**. The text goes exactly as typed, as an
|
||||
|
|
@ -739,7 +740,8 @@ swarm. The tick is off by default, so an entry is for this agent alone.
|
|||
|
||||
The **egress** card on `/swarm` has two lists of the swarm-wide entries, one per line, with
|
||||
**remove** beside each and an **add** that opens the same dialog without the tick (the
|
||||
entry is always for every agent). **allowed** is the list above. **denied** is the swarm's
|
||||
entry is always for every agent). While a list loads, it shows grey placeholder
|
||||
rows. **allowed** is the list above. **denied** is the swarm's
|
||||
deny list: every egress mode refuses a denied name, even when an allowed entry, a hive's
|
||||
own `allowedDomains` or the unrestricted internet flag would let it through. The card says
|
||||
which hives haven't applied the current deny list yet; until they do, their proxies may still let
|
||||
|
|
|
|||
|
|
@ -157,3 +157,16 @@
|
|||
.ui-broadcast-fail .ui-broadcast-outcome-name {
|
||||
color: var(--fg);
|
||||
}
|
||||
/* The loading twin's chip: not a control, so no pointer. */
|
||||
.ui-broadcast-agent-loading {
|
||||
cursor: default;
|
||||
}
|
||||
/* Its name and state lines take the height of a line of text, so the chip is
|
||||
the height of a real one. */
|
||||
.ui-broadcast-agent-loading .ui-broadcast-name,
|
||||
.ui-broadcast-agent-loading .ui-broadcast-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 1.6em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
} from "../../ui/refresh-interval/RefreshInterval.js";
|
||||
import { EmptyState } from "../../ui/empty-state/EmptyState.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { Skeleton, SkeletonRegion } from "../../ui/skeleton/Skeleton.js";
|
||||
import { fetchJson, usePolled } from "../../ui/polled/usePolled.js";
|
||||
import { TextAreaField } from "../../ui/text-area-field/TextAreaField.js";
|
||||
import {
|
||||
|
|
@ -59,6 +60,30 @@ function storedFilter(): AgentFilter {
|
|||
}
|
||||
}
|
||||
|
||||
// The twin of the agent grid while the roster loads: the same grid and the
|
||||
// same chip (border, icon, name, state) with a placeholder in each slot.
|
||||
function RosterSkeleton({ count = 6 }: { count?: number }) {
|
||||
return (
|
||||
<SkeletonRegion label="the agents">
|
||||
<ul class="ui-broadcast-grid" aria-hidden="true">
|
||||
{Array.from({ length: count }, (_, i) => (
|
||||
<li key={i}>
|
||||
<div class="ui-broadcast-agent ui-broadcast-agent-loading">
|
||||
<Skeleton shape="block" width="5em" height="5em" />
|
||||
<span class="ui-broadcast-name">
|
||||
<Skeleton width="4em" />
|
||||
</span>
|
||||
<span class="ui-broadcast-state">
|
||||
<Skeleton width="3.5em" />
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SkeletonRegion>
|
||||
);
|
||||
}
|
||||
|
||||
export function BroadcastComposer() {
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
|
|
@ -183,7 +208,9 @@ export function BroadcastComposer() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{agents !== null && shown.length === 0 ? (
|
||||
{agents === null ? (
|
||||
<RosterSkeleton />
|
||||
) : shown.length === 0 ? (
|
||||
<EmptyState centered>
|
||||
{filter === "running" && all.length > 0
|
||||
? "no agent is running right now. Show all to pick a stopped one."
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js";
|
|||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { SectionHeading } from "../../ui/section-heading/SectionHeading.js";
|
||||
import { Skeleton, SkeletonRegion } from "../../ui/skeleton/Skeleton.js";
|
||||
import "../../agent/detail/AgentLimits.css";
|
||||
import "./SwarmPage.css";
|
||||
|
||||
|
|
@ -67,9 +68,18 @@ function DomainSection({
|
|||
{errorPanel}
|
||||
{entries === null ? (
|
||||
errorPanel === null ? (
|
||||
<Hint as="span" role="status">
|
||||
loading the {kind === "deny" ? "denied" : "allowed"} domains…
|
||||
</Hint>
|
||||
<SkeletonRegion
|
||||
label={`the ${kind === "deny" ? "denied" : "allowed"} domains`}
|
||||
>
|
||||
<ul class="ui-swarm-egress-list" aria-hidden="true">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<li key={i}>
|
||||
<Skeleton width="10em" />
|
||||
<Skeleton width="5em" height="2.75em" />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SkeletonRegion>
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue