swarm-ui: shared Hint, ActionRow, Toolbar, EmptyState, ExternalLink and ExpandableCard replace per-page copies
Hint, ActionRow, Toolbar, EmptyState and ExternalLink replace the muted text, button row, refresh bar, empty line and _blank anchor that each page restyled under its own class. ExpandableCard (with a loading twin) holds the builds and recent-runs cards, which had identical head and body CSS. The create form's raw checkbox becomes CheckboxField. The components page shows each new piece.
This commit is contained in:
parent
b74b5e77e4
commit
8a2e5203ad
69 changed files with 689 additions and 427 deletions
|
|
@ -221,7 +221,8 @@ commit, whether the run was a try-out or a requested review, how long ago it
|
||||||
started, how long it took, how many classifications ran, and the outcome as a
|
started, how long it took, how many classifications ran, and the outcome as a
|
||||||
badge. Select a card to open it: it shows the same result table as the
|
badge. Select a card to open it: it shows the same result table as the
|
||||||
[try-out](#review-try-out), the overall verdict, the reason when the outcome
|
[try-out](#review-try-out), the overall verdict, the reason when the outcome
|
||||||
has one, and a link to the posted review when there is one.
|
has one, and a link to the posted review when there is one. While the first
|
||||||
|
answer loads, the section shows three grey placeholder cards.
|
||||||
|
|
||||||
An outcome can be more than approve or request changes:
|
An outcome can be more than approve or request changes:
|
||||||
|
|
||||||
|
|
@ -589,7 +590,8 @@ config PR). Each card shows the repo and PR, or for a deploy's build the agent
|
||||||
and `internal/meta`, the first seven characters of the commit, how the build ended (**built**,
|
and `internal/meta`, the first seven characters of the commit, how the build ended (**built**,
|
||||||
**failed** or **error**), when it started and how long it took. Select a card to
|
**failed** or **error**), when it started and how long it took. Select a card to
|
||||||
read it: for a failed build, what `nix` printed; for a built one, the system
|
read it: for a failed build, what `nix` printed; for a built one, the system
|
||||||
closure; for an error, why no build came back.
|
closure; for an error, why no build came back. While the first answer loads, the
|
||||||
|
card shows three grey placeholder cards.
|
||||||
|
|
||||||
The **Details** link on a `swarm-builder` commit status opens the jobs tab as
|
The **Details** link on a `swarm-builder` commit status opens the jobs tab as
|
||||||
`/jobs?run=<id>`, with the builds card open and that build's card in view. Links made before the
|
`/jobs?run=<id>`, with the builds card open and that build's card in view. Links made before the
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,5 @@
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
.claude-login-ok {
|
.claude-login-ok {
|
||||||
margin-top: 1em;
|
margin-block: 1em;
|
||||||
color: var(--green);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||||
|
import { ExternalLink } from "../../ui/external-link/ExternalLink.js";
|
||||||
|
import { Hint } from "../../ui/hint/Hint.js";
|
||||||
import { Panel } from "../../ui/panel/Panel.js";
|
import { Panel } from "../../ui/panel/Panel.js";
|
||||||
import { TextField } from "../../ui/text-field/TextField.js";
|
import { TextField } from "../../ui/text-field/TextField.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -98,9 +100,7 @@ export function ClaudeLoginDialog({
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
Open{" "}
|
Open{" "}
|
||||||
<a href={step.url} target="_blank" rel="noopener noreferrer">
|
<ExternalLink href={step.url}>the claude login page</ExternalLink>
|
||||||
the claude login page
|
|
||||||
</a>
|
|
||||||
, sign in with this agent's account, and paste the code it shows.
|
, sign in with this agent's account, and paste the code it shows.
|
||||||
</p>
|
</p>
|
||||||
<form class="claude-login-form" onSubmit={submit}>
|
<form class="claude-login-form" onSubmit={submit}>
|
||||||
|
|
@ -122,10 +122,10 @@ export function ClaudeLoginDialog({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{step.step === "stored" && (
|
{step.step === "stored" && (
|
||||||
<p class="claude-login-ok">
|
<Hint tone="ok" class="claude-login-ok">
|
||||||
Stored. <strong>{agent}</strong> picks the login up within a minute
|
Stored. <strong>{agent}</strong> picks the login up within a minute
|
||||||
and resumes.
|
and resumes.
|
||||||
</p>
|
</Hint>
|
||||||
)}
|
)}
|
||||||
{step.step === "failed" && (
|
{step.step === "failed" && (
|
||||||
<ApiErrorPanel
|
<ApiErrorPanel
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,6 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.4em 1em;
|
gap: 0.4em 1em;
|
||||||
}
|
}
|
||||||
.ui-agent-detail-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The folded sections of an opened card sit one under another, each with the
|
/* The folded sections of an opened card sit one under another, each with the
|
||||||
shared disclosure look; the fields keep the grid above. */
|
shared disclosure look; the fields keep the grid above. */
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@
|
||||||
// change or a link made here shows wherever else the same row is drawn.
|
// change or a link made here shows wherever else the same row is drawn.
|
||||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
|
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
|
import { ExternalLink } from "../../ui/external-link/ExternalLink.js";
|
||||||
|
import { Hint } from "../../ui/hint/Hint.js";
|
||||||
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
|
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
|
||||||
import type { AgentActions } from "../roster/AgentActions.js";
|
import type { AgentActions } from "../roster/AgentActions.js";
|
||||||
import { AgentInference } from "./AgentInference.js";
|
import { AgentInference } from "./AgentInference.js";
|
||||||
|
|
@ -118,12 +121,10 @@ function OverviewRows({
|
||||||
<>
|
<>
|
||||||
<dt>forge</dt>
|
<dt>forge</dt>
|
||||||
<dd class="ui-agent-detail-links">
|
<dd class="ui-agent-detail-links">
|
||||||
<a href={forgeLinks.profile} target="_blank" rel="noreferrer">
|
<ExternalLink href={forgeLinks.profile}>profile</ExternalLink>
|
||||||
profile
|
<ExternalLink href={forgeLinks.configRepo}>
|
||||||
</a>
|
|
||||||
<a href={forgeLinks.configRepo} target="_blank" rel="noreferrer">
|
|
||||||
config repo
|
config repo
|
||||||
</a>
|
</ExternalLink>
|
||||||
</dd>
|
</dd>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -134,13 +135,9 @@ function OverviewRows({
|
||||||
tone="warning"
|
tone="warning"
|
||||||
value={
|
value={
|
||||||
row.config_pr.html_url ? (
|
row.config_pr.html_url ? (
|
||||||
<a
|
<ExternalLink href={row.config_pr.html_url}>
|
||||||
href={row.config_pr.html_url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
#{row.config_pr.pr_number}
|
#{row.config_pr.pr_number}
|
||||||
</a>
|
</ExternalLink>
|
||||||
) : (
|
) : (
|
||||||
`#${row.config_pr.pr_number}`
|
`#${row.config_pr.pr_number}`
|
||||||
)
|
)
|
||||||
|
|
@ -211,7 +208,7 @@ function GrantRows({ agent }: { agent: string }) {
|
||||||
<dt>permissions</dt>
|
<dt>permissions</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{g.grant === null ? (
|
{g.grant === null ? (
|
||||||
<span class="ui-agent-limits-muted">—</span>
|
<Hint as="span">—</Hint>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{g.readFailure}
|
{g.readFailure}
|
||||||
|
|
@ -257,13 +254,13 @@ function LinkAccountAction({
|
||||||
actions: AgentActions;
|
actions: AgentActions;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div class="ui-agent-detail-actions">
|
<ActionRow class="ui-agent-detail-actions">
|
||||||
<LinkAccountMenu
|
<LinkAccountMenu
|
||||||
agent={row.name}
|
agent={row.name}
|
||||||
hasHive={row.hive !== null}
|
hasHive={row.hive !== null}
|
||||||
onSelect={(kind) => actions.openLink(row, kind)}
|
onSelect={(kind) => actions.openLink(row, kind)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ActionRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
.ui-agent-inference-source {
|
.ui-agent-inference-source {
|
||||||
margin-left: -0.25em;
|
margin-left: -0.25em;
|
||||||
}
|
}
|
||||||
.ui-agent-inference-source,
|
.ui-agent-inference-source {
|
||||||
.ui-agent-inference-muted {
|
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
.ui-agent-inference-form {
|
.ui-agent-inference-form {
|
||||||
|
|
@ -28,12 +27,3 @@
|
||||||
gap: 0.75em;
|
gap: 0.75em;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
.ui-agent-inference-problem {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--red);
|
|
||||||
}
|
|
||||||
.ui-agent-inference-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import { useEffect, useState } from "preact/hooks";
|
||||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
|
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||||
|
import { Hint } from "../../ui/hint/Hint.js";
|
||||||
import { Panel } from "../../ui/panel/Panel.js";
|
import { Panel } from "../../ui/panel/Panel.js";
|
||||||
import { SelectField } from "../../ui/select-field/SelectField.js";
|
import { SelectField } from "../../ui/select-field/SelectField.js";
|
||||||
import { TextField } from "../../ui/text-field/TextField.js";
|
import { TextField } from "../../ui/text-field/TextField.js";
|
||||||
|
|
@ -171,7 +173,7 @@ export function InferenceFields({
|
||||||
pattern="[0-9]*"
|
pattern="[0-9]*"
|
||||||
onInput={(maxSubagents) => onChange({ ...draft, maxSubagents })}
|
onInput={(maxSubagents) => onChange({ ...draft, maxSubagents })}
|
||||||
/>
|
/>
|
||||||
{problem ? <p class="ui-agent-inference-problem">{problem}</p> : null}
|
{problem ? <Hint tone="error">{problem}</Hint> : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +217,7 @@ export function AgentInference({ agent }: { agent: string }) {
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
if (view === null) {
|
if (view === null) {
|
||||||
return errorPanel ?? <span class="ui-agent-inference-muted">…</span>;
|
return errorPanel ?? <Hint as="span">…</Hint>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { configured, effective } = view;
|
const { configured, effective } = view;
|
||||||
|
|
@ -368,7 +370,7 @@ function InferenceForm({
|
||||||
{problem ? (
|
{problem ? (
|
||||||
<ApiErrorPanel context="saving failed" problem={problem} />
|
<ApiErrorPanel context="saving failed" problem={problem} />
|
||||||
) : null}
|
) : null}
|
||||||
<div class="ui-agent-inference-actions">
|
<ActionRow>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
@ -382,7 +384,7 @@ function InferenceForm({
|
||||||
>
|
>
|
||||||
clear the swarm's setting
|
clear the swarm's setting
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</ActionRow>
|
||||||
</form>
|
</form>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,3 @@
|
||||||
border: 1px solid var(--red);
|
border: 1px solid var(--red);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.ui-agent-instructions-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
|
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||||
import { Panel } from "../../ui/panel/Panel.js";
|
import { Panel } from "../../ui/panel/Panel.js";
|
||||||
|
|
@ -235,9 +236,9 @@ function InstructionsForm({
|
||||||
context="loading AGENTS.md failed"
|
context="loading AGENTS.md failed"
|
||||||
problem={load.problem}
|
problem={load.problem}
|
||||||
/>
|
/>
|
||||||
<div class="ui-agent-instructions-actions">
|
<ActionRow class="ui-agent-instructions-actions">
|
||||||
<Button onClick={reload}>try again</Button>
|
<Button onClick={reload}>try again</Button>
|
||||||
</div>
|
</ActionRow>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{load.status === "ready" ? (
|
{load.status === "ready" ? (
|
||||||
|
|
@ -263,18 +264,18 @@ function InstructionsForm({
|
||||||
<pre class="ui-agent-instructions-theirs" tabIndex={0}>
|
<pre class="ui-agent-instructions-theirs" tabIndex={0}>
|
||||||
{theirs.content === "" ? "(empty)" : theirs.content}
|
{theirs.content === "" ? "(empty)" : theirs.content}
|
||||||
</pre>
|
</pre>
|
||||||
<div class="ui-agent-instructions-actions">
|
<ActionRow class="ui-agent-instructions-actions">
|
||||||
<Button onClick={keepMine}>
|
<Button onClick={keepMine}>
|
||||||
keep my text, replace {agent}'s changes
|
keep my text, replace {agent}'s changes
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={takeTheirs}>use {agent}'s version</Button>
|
<Button onClick={takeTheirs}>use {agent}'s version</Button>
|
||||||
</div>
|
</ActionRow>
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
{problem ? (
|
{problem ? (
|
||||||
<ApiErrorPanel context="saving failed" problem={problem} />
|
<ApiErrorPanel context="saving failed" problem={problem} />
|
||||||
) : null}
|
) : null}
|
||||||
<div class="ui-agent-instructions-actions">
|
<ActionRow class="ui-agent-instructions-actions">
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
@ -305,7 +306,7 @@ function InstructionsForm({
|
||||||
>
|
>
|
||||||
reload from {agent}
|
reload from {agent}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</ActionRow>
|
||||||
</form>
|
</form>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,4 @@
|
||||||
}
|
}
|
||||||
.ui-agent-limits-hint {
|
.ui-agent-limits-hint {
|
||||||
margin: 0 0 0.5em;
|
margin: 0 0 0.5em;
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
.ui-agent-limits-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75em;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@ import { useEffect, useState } from "preact/hooks";
|
||||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
|
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||||
|
import { Hint } from "../../ui/hint/Hint.js";
|
||||||
import { Panel } from "../../ui/panel/Panel.js";
|
import { Panel } from "../../ui/panel/Panel.js";
|
||||||
import { TextField } from "../../ui/text-field/TextField.js";
|
import { TextField } from "../../ui/text-field/TextField.js";
|
||||||
import { BadgeSkeleton } from "../../ui/skeleton/BadgeSkeleton.js";
|
import { BadgeSkeleton } from "../../ui/skeleton/BadgeSkeleton.js";
|
||||||
|
|
@ -71,9 +73,9 @@ export function LimitsFields({
|
||||||
placeholder="e.g. 400%"
|
placeholder="e.g. 400%"
|
||||||
onInput={onCpu}
|
onInput={onCpu}
|
||||||
/>
|
/>
|
||||||
<p class="ui-agent-limits-hint">
|
<Hint class="ui-agent-limits-hint">
|
||||||
A percentage of one core: 100% is one core, 400% is four.
|
A percentage of one core: 100% is one core, 400% is four.
|
||||||
</p>
|
</Hint>
|
||||||
<TextField
|
<TextField
|
||||||
id={`${idPrefix}-memory-max`}
|
id={`${idPrefix}-memory-max`}
|
||||||
label="memory max"
|
label="memory max"
|
||||||
|
|
@ -81,10 +83,10 @@ export function LimitsFields({
|
||||||
placeholder="e.g. 8G"
|
placeholder="e.g. 8G"
|
||||||
onInput={onMemory}
|
onInput={onMemory}
|
||||||
/>
|
/>
|
||||||
<p class="ui-agent-limits-hint">
|
<Hint class="ui-agent-limits-hint">
|
||||||
A size such as 512M or 8G, a percentage of the machine's memory such as
|
A size such as 512M or 8G, a percentage of the machine's memory such as
|
||||||
50%, or infinity.
|
50%, or infinity.
|
||||||
</p>
|
</Hint>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +241,7 @@ function LimitsForm({
|
||||||
{problem ? (
|
{problem ? (
|
||||||
<ApiErrorPanel context="the caps were not saved" problem={problem} />
|
<ApiErrorPanel context="the caps were not saved" problem={problem} />
|
||||||
) : null}
|
) : null}
|
||||||
<div class="ui-agent-limits-actions">
|
<ActionRow>
|
||||||
<Button variant="primary" type="submit" disabled={busy || empty}>
|
<Button variant="primary" type="submit" disabled={busy || empty}>
|
||||||
{busy ? "saving…" : "save"}
|
{busy ? "saving…" : "save"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -249,7 +251,7 @@ function LimitsForm({
|
||||||
>
|
>
|
||||||
clear the swarm's caps
|
clear the swarm's caps
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</ActionRow>
|
||||||
</form>
|
</form>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { Badge } from "@hive/shared/badge.js";
|
||||||
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
import { BadgeSkeleton } from "../../ui/skeleton/BadgeSkeleton.js";
|
import { BadgeSkeleton } from "../../ui/skeleton/BadgeSkeleton.js";
|
||||||
|
import { Hint } from "../../ui/hint/Hint.js";
|
||||||
import { MultiselectFilterOptions } from "../../ui/multiselect-filter/MultiselectFilter.js";
|
import { MultiselectFilterOptions } from "../../ui/multiselect-filter/MultiselectFilter.js";
|
||||||
import {
|
import {
|
||||||
requestPermissions,
|
requestPermissions,
|
||||||
|
|
@ -225,9 +226,9 @@ export function AgentPermissions({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span class="ui-agent-limits-muted" role="status">
|
<Hint as="span" role="status">
|
||||||
{catalogueError ? "could not load the permissions" : "loading…"}
|
{catalogueError ? "could not load the permissions" : "loading…"}
|
||||||
</span>
|
</Hint>
|
||||||
)}
|
)}
|
||||||
{saveError ? (
|
{saveError ? (
|
||||||
<ApiErrorPanel
|
<ApiErrorPanel
|
||||||
|
|
@ -287,7 +288,7 @@ export function AgentInternet({ agent, g }: { agent: string; g: GrantState }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span class="ui-agent-limits-muted">{meaning}</span>
|
<Hint as="span">{meaning}</Hint>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { useState } from "preact/hooks";
|
||||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
|
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||||
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
||||||
|
|
@ -232,7 +233,7 @@ export function AddForm({
|
||||||
{problem ? (
|
{problem ? (
|
||||||
<ApiErrorPanel context="the domain was not added" problem={problem} />
|
<ApiErrorPanel context="the domain was not added" problem={problem} />
|
||||||
) : null}
|
) : null}
|
||||||
<div class="ui-agent-limits-actions">
|
<ActionRow>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
@ -240,7 +241,7 @@ export function AddForm({
|
||||||
>
|
>
|
||||||
{busy ? "adding…" : "add"}
|
{busy ? "adding…" : "add"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</ActionRow>
|
||||||
</form>
|
</form>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.ui-linked-accounts-host,
|
.ui-linked-accounts-host {
|
||||||
.ui-linked-accounts-muted {
|
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
||||||
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
|
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
|
||||||
|
import { Hint } from "../../ui/hint/Hint.js";
|
||||||
import { accountKindLabel } from "./account-kind.js";
|
import { accountKindLabel } from "./account-kind.js";
|
||||||
import { deleteUrl, listUrl } from "./account-api.js";
|
import { deleteUrl, listUrl } from "./account-api.js";
|
||||||
import "./LinkedAccounts.css";
|
import "./LinkedAccounts.css";
|
||||||
|
|
@ -145,13 +146,13 @@ export function LinkedAccounts({
|
||||||
<ApiErrorPanel context="listing linked accounts failed" problem={error} />
|
<ApiErrorPanel context="listing linked accounts failed" problem={error} />
|
||||||
) : null;
|
) : null;
|
||||||
if (accounts === null) {
|
if (accounts === null) {
|
||||||
return errorPanel ?? <span class="ui-linked-accounts-muted">…</span>;
|
return errorPanel ?? <Hint as="span">…</Hint>;
|
||||||
}
|
}
|
||||||
if (accounts.length === 0) {
|
if (accounts.length === 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{errorPanel}
|
{errorPanel}
|
||||||
<span class="ui-linked-accounts-muted">none linked</span>
|
<Hint as="span">none linked</Hint>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useState } from "preact/hooks";
|
||||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||||
import { Button } from "../ui/button/Button.js";
|
import { Button } from "../ui/button/Button.js";
|
||||||
|
import { ExternalLink } from "../ui/external-link/ExternalLink.js";
|
||||||
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
|
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
|
||||||
import { RelativeTime } from "../ui/relative-time/RelativeTime.js";
|
import { RelativeTime } from "../ui/relative-time/RelativeTime.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -16,6 +17,7 @@ import {
|
||||||
import { fetchJson, usePolled } from "../ui/polled/usePolled.js";
|
import { fetchJson, usePolled } from "../ui/polled/usePolled.js";
|
||||||
import { Table, type TableColumn } from "../ui/table/Table.js";
|
import { Table, type TableColumn } from "../ui/table/Table.js";
|
||||||
import { TableSkeleton } from "../ui/table/TableSkeleton.js";
|
import { TableSkeleton } from "../ui/table/TableSkeleton.js";
|
||||||
|
import { Toolbar } from "../ui/toolbar/Toolbar.js";
|
||||||
import { activeSinceMs, alertKey, listedAlerts, type Alert } from "./alerts.js";
|
import { activeSinceMs, alertKey, listedAlerts, type Alert } from "./alerts.js";
|
||||||
|
|
||||||
const STATE_TONE: Record<string, BadgeTone> = {
|
const STATE_TONE: Record<string, BadgeTone> = {
|
||||||
|
|
@ -90,10 +92,10 @@ function AlertsBody() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="ui-alerts-bar">
|
<Toolbar>
|
||||||
<a href={GRAFANA_ALERTS_URL} target="_blank" rel="noopener noreferrer">
|
<ExternalLink href={GRAFANA_ALERTS_URL}>
|
||||||
Grafana's alert list
|
Grafana's alert list
|
||||||
</a>
|
</ExternalLink>
|
||||||
{showAll || hidden > 0 ? (
|
{showAll || hidden > 0 ? (
|
||||||
<Button onClick={() => setShowAll(!showAll)}>
|
<Button onClick={() => setShowAll(!showAll)}>
|
||||||
{showAll ? "only firing" : `show ${hidden} more`}
|
{showAll ? "only firing" : `show ${hidden} more`}
|
||||||
|
|
@ -105,7 +107,7 @@ function AlertsBody() {
|
||||||
onChange={setIntervalMs}
|
onChange={setIntervalMs}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Toolbar>
|
||||||
{error ? (
|
{error ? (
|
||||||
<ApiErrorPanel
|
<ApiErrorPanel
|
||||||
context="failed to load Grafana's alerts"
|
context="failed to load Grafana's alerts"
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,15 @@ import {
|
||||||
import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
|
import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
|
||||||
import { Button, type ButtonVariant } from "../ui/button/Button.js";
|
import { Button, type ButtonVariant } from "../ui/button/Button.js";
|
||||||
import { Dialog } from "../ui/dialog/Dialog.js";
|
import { Dialog } from "../ui/dialog/Dialog.js";
|
||||||
|
import { ActionRow } from "../ui/action-row/ActionRow.js";
|
||||||
|
import { EmptyState } from "../ui/empty-state/EmptyState.js";
|
||||||
|
import {
|
||||||
|
ExpandableCard,
|
||||||
|
ExpandableCardSkeleton,
|
||||||
|
} from "../ui/expandable-card/ExpandableCard.js";
|
||||||
|
import { ExternalLink } from "../ui/external-link/ExternalLink.js";
|
||||||
|
import { Hint, type HintTone } from "../ui/hint/Hint.js";
|
||||||
|
import { Toolbar } from "../ui/toolbar/Toolbar.js";
|
||||||
import { ConfirmDialog } from "../ui/confirm-dialog/ConfirmDialog.js";
|
import { ConfirmDialog } from "../ui/confirm-dialog/ConfirmDialog.js";
|
||||||
import { Popover } from "../ui/popover/Popover.js";
|
import { Popover } from "../ui/popover/Popover.js";
|
||||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||||
|
|
@ -41,6 +50,8 @@ import { GearIcon } from "@hive/shared/icons.js";
|
||||||
import { Dropdown, type DropdownOption } from "@hive/shared/dropdown.js";
|
import { Dropdown, type DropdownOption } from "@hive/shared/dropdown.js";
|
||||||
import "./ComponentsPage.css";
|
import "./ComponentsPage.css";
|
||||||
|
|
||||||
|
const HINT_TONES: HintTone[] = ["muted", "warn", "error", "ok"];
|
||||||
|
|
||||||
function Section({
|
function Section({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
|
|
@ -590,6 +601,62 @@ export function ComponentsPage() {
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Hint">
|
||||||
|
<div class="components-chip-row">
|
||||||
|
{HINT_TONES.map((tone) => (
|
||||||
|
<Sample key={tone} label={tone}>
|
||||||
|
<Hint tone={tone}>A line of supporting text.</Hint>
|
||||||
|
</Sample>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="ActionRow, Toolbar, EmptyState, ExternalLink">
|
||||||
|
<Sample label="ActionRow: the buttons that end a form">
|
||||||
|
<ActionRow>
|
||||||
|
<Button variant="primary">save</Button>
|
||||||
|
<Button>cancel</Button>
|
||||||
|
</ActionRow>
|
||||||
|
</Sample>
|
||||||
|
<Sample label="Toolbar: the bar above a list">
|
||||||
|
<Toolbar>
|
||||||
|
<Button>refresh</Button>
|
||||||
|
</Toolbar>
|
||||||
|
</Sample>
|
||||||
|
<Sample label="EmptyState">
|
||||||
|
<EmptyState>Nothing here yet.</EmptyState>
|
||||||
|
</Sample>
|
||||||
|
<Sample label="ExternalLink">
|
||||||
|
<ExternalLink href="https://example.com">example.com</ExternalLink>
|
||||||
|
</Sample>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="ExpandableCard">
|
||||||
|
<Sample label="closed, open, and the loading twin">
|
||||||
|
<ExpandableCard
|
||||||
|
id="components-expandable-closed"
|
||||||
|
open={false}
|
||||||
|
onToggle={() => {}}
|
||||||
|
title="build 1"
|
||||||
|
badge={<Badge value="ok" />}
|
||||||
|
meta="2 minutes ago"
|
||||||
|
>
|
||||||
|
body
|
||||||
|
</ExpandableCard>
|
||||||
|
<ExpandableCard
|
||||||
|
id="components-expandable-open"
|
||||||
|
open
|
||||||
|
onToggle={() => {}}
|
||||||
|
title="build 2"
|
||||||
|
badge={<Badge value="failed" />}
|
||||||
|
meta="an hour ago"
|
||||||
|
>
|
||||||
|
<Hint>The body appears while the card is open.</Hint>
|
||||||
|
</ExpandableCard>
|
||||||
|
<ExpandableCardSkeleton />
|
||||||
|
</Sample>
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section title="Dialog">
|
<Section title="Dialog">
|
||||||
<Sample label="modal, caller-owned content">
|
<Sample label="modal, caller-owned content">
|
||||||
<DialogSample />
|
<DialogSample />
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin-left: 0.25em;
|
margin-left: 0.25em;
|
||||||
color: var(--muted);
|
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
.create-agent-summary {
|
.create-agent-summary {
|
||||||
|
|
@ -118,14 +117,6 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
.create-agent-result-ok {
|
|
||||||
color: var(--green);
|
|
||||||
}
|
|
||||||
/* The controller's refusal of the AGENTS.md text, under its box. */
|
|
||||||
.create-agent-field-error {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--red);
|
|
||||||
}
|
|
||||||
/* The picked archetype's description, under its select. */
|
/* The picked archetype's description, under its select. */
|
||||||
.create-agent-archetype-description {
|
.create-agent-archetype-description {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,9 @@ import {
|
||||||
InferenceFields,
|
InferenceFields,
|
||||||
type InferenceDraft,
|
type InferenceDraft,
|
||||||
} from "../agent/detail/AgentInference.js";
|
} from "../agent/detail/AgentInference.js";
|
||||||
|
import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
|
||||||
import { FormField } from "../ui/form-field/FormField.js";
|
import { FormField } from "../ui/form-field/FormField.js";
|
||||||
|
import { Hint } from "../ui/hint/Hint.js";
|
||||||
import { MultiselectFilterOptions } from "../ui/multiselect-filter/MultiselectFilter.js";
|
import { MultiselectFilterOptions } from "../ui/multiselect-filter/MultiselectFilter.js";
|
||||||
import {
|
import {
|
||||||
initialSelection,
|
initialSelection,
|
||||||
|
|
@ -325,12 +327,12 @@ export function CreateAgentForm({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{done && (
|
{done && (
|
||||||
<div class="create-agent-result create-agent-result-ok">
|
<Hint tone="ok" as="div" class="create-agent-result">
|
||||||
<p>
|
<p>
|
||||||
Queued as job node #{result.nodeId} —{" "}
|
Queued as job node #{result.nodeId} —{" "}
|
||||||
<Link href="/jobs">watch it in jobs</Link>.
|
<Link href="/jobs">watch it in jobs</Link>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</Hint>
|
||||||
)}
|
)}
|
||||||
{!done && step === "identity" && (
|
{!done && step === "identity" && (
|
||||||
<div class="create-agent-fields">
|
<div class="create-agent-fields">
|
||||||
|
|
@ -409,7 +411,9 @@ export function CreateAgentForm({
|
||||||
onInput={setAgentsMd}
|
onInput={setAgentsMd}
|
||||||
/>
|
/>
|
||||||
{refusal !== null && (
|
{refusal !== null && (
|
||||||
<p class="create-agent-field-error">{refusal}</p>
|
<Hint tone="error" class="create-agent-field-error">
|
||||||
|
{refusal}
|
||||||
|
</Hint>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -438,14 +442,12 @@ export function CreateAgentForm({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</FormField>
|
</FormField>
|
||||||
<label>
|
<CheckboxField
|
||||||
<input
|
id="create-agent-internet"
|
||||||
type="checkbox"
|
label="Unrestricted internet"
|
||||||
checked={internet}
|
checked={internet}
|
||||||
onChange={() => setInternet((v) => !v)}
|
onChange={setInternet}
|
||||||
/>{" "}
|
/>
|
||||||
Unrestricted internet
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!done && step === "resources" && (
|
{!done && step === "resources" && (
|
||||||
|
|
@ -531,9 +533,9 @@ export function CreateAgentForm({
|
||||||
back
|
back
|
||||||
</Button>
|
</Button>
|
||||||
{problem !== null && (step !== "identity" || name !== "") && (
|
{problem !== null && (step !== "identity" || name !== "") && (
|
||||||
<span class="create-agent-hint" role="status">
|
<Hint as="span" role="status" class="create-agent-hint">
|
||||||
{problem}
|
{problem}
|
||||||
</span>
|
</Hint>
|
||||||
)}
|
)}
|
||||||
{step === "summary" ? (
|
{step === "summary" ? (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge } from "@hive/shared/badge.js";
|
import { Badge } from "@hive/shared/badge.js";
|
||||||
import { useLocalSetting } from "@hive/shared/settings-storage.js";
|
import { useLocalSetting } from "@hive/shared/settings-storage.js";
|
||||||
|
import { ExternalLink } from "../ui/external-link/ExternalLink.js";
|
||||||
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
|
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
|
||||||
import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
|
import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
|
||||||
import { SelectField } from "../ui/select-field/SelectField.js";
|
import { SelectField } from "../ui/select-field/SelectField.js";
|
||||||
|
|
@ -268,9 +269,7 @@ function IssueReportBody() {
|
||||||
ariaSort: ariaSortFor("number"),
|
ariaSort: ariaSortFor("number"),
|
||||||
render: (r) =>
|
render: (r) =>
|
||||||
r.html_url ? (
|
r.html_url ? (
|
||||||
<a href={r.html_url} target="_blank" rel="noreferrer">
|
<ExternalLink href={r.html_url}>#{r.number}</ExternalLink>
|
||||||
#{r.number}
|
|
||||||
</a>
|
|
||||||
) : (
|
) : (
|
||||||
`#${r.number}`
|
`#${r.number}`
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue