mara on #5170: "dont split caps and perms. its all perms. / dont include the now empty tool groups / nothing set -> default / also add perms to agent creation screen". - hive-sh4re: one `Permission` enum (messaging, meta, inbox, execution, web_tools, manage_root_agent) replaces `ToolGroup` and `Capability`; `lifecycle`/`forge` are gone. `Permission::default_for` is the default for an agent the swarm sets nothing for. - The record is `{ "permissions": [...] }`. Missing key, unset field or no successful read yet = default; `[]` = none; a failed read after a good one keeps the last value. - hive-agent reads its own key every 10s and builds each turn's --tools, --allowedTools and MCP config (rewritten before every turn) from it; the subagent daemon reads it on each start/continue. Agents get a read-only grant on their own key. - hive-c0re restarts an agent only when its manage_root_agent changes. The import now merges meta/capabilities.json and meta/tool-groups.json into one list per agent and deletes both; the "import pending, don't read" decision is `ImportOutcome::read_allowed`, table-tested. - swarm-controller: `GET /api/permissions` (names + default) and an optional `permissions` on `POST /api/agents`, stored before the job is queued. swarm-ui's create-agent form gets a permissions field. - Deleted: HIVE_TOOL_GROUPS and its forward to the subagent daemon, tool_groups.rs, ruth's tool-group seed, the permissions rebuild job (PermPayload/WritePermFile/perm_change), the dashboard permissions tab and its stale-entries panel. Part of #5170.
748 lines
19 KiB
CSS
748 lines
19 KiB
CSS
/* Dashboard-specific styles. common.css must be loaded first —
|
|
it defines the CSS custom properties, typography, and shared
|
|
component rules that this file depends on. */
|
|
|
|
/* SYST3M panel rules (rebuild queue / meta inputs / kept state)
|
|
live in system-sections.css, shared with /core.html. A few are still
|
|
referenced from the dashboard (`.rebuild-queue`), so the dashboard
|
|
pulls them in here. */
|
|
@import "./system-sections.css";
|
|
@import "@hive/shared/jobq-rollup.css";
|
|
@import "@hive/shared/jobq-graph.css";
|
|
|
|
/* ─── tabbed dashboard chrome ──────────────────────────────────────
|
|
Top-of-page sticky header with banner + tab strip. SSE stays
|
|
alive across tab switches so count pills update live on inactive
|
|
tabs. See docs/web-ui/dashboard.md::Chrome header + Tab strip for the
|
|
routing model. */
|
|
|
|
body.dashboard-shell {
|
|
/* Full-bleed body (no horizontal gutter). The 1.5em side gutter lives
|
|
on the inner `.page-content` wrapper (see common.css), so the sticky
|
|
chrome bar, the server-warnings banner, and the footer span the full
|
|
width edge-to-edge with no negative-margin breakout. `padding-bottom`
|
|
keeps a little breathing room above the viewport edge (overridden to
|
|
clear the selection bar via `.has-selection` below). No max-width cap
|
|
— wide screens use the full width rather than empty side margins. */
|
|
margin: 0;
|
|
padding-bottom: 1.5em;
|
|
}
|
|
|
|
.dashboard-chrome {
|
|
/* Home back-link + tab strip share one row (flex). The chrome owns the
|
|
horizontal gutter and the single full-width bottom divider; its
|
|
children drop their own side padding / border so they sit inline. */
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75em;
|
|
/* Stickiness lives on the `.sticky-top` wrapper (common.css) that
|
|
common.js wraps this chrome in, alongside the server-warning banner,
|
|
so they stack in one sticky context instead of colliding. */
|
|
background: color-mix(in srgb, var(--bg) 86%, transparent);
|
|
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
|
backdrop-filter: blur(8px) saturate(120%);
|
|
border-bottom: 1px solid var(--purple-dim);
|
|
padding: 0.3em 1em;
|
|
margin: 0 0 1em;
|
|
}
|
|
/* ← home back-link to the H0M3 hub. Small purple link top-left of the
|
|
chrome, mirroring the .page-back treatment the standalone pages use
|
|
(now shared from @hive/shared/chrome.css) so the navigation reads
|
|
consistently. The dashboard keeps this richer sticky chrome of its
|
|
own rather than folding into the simple page header. */
|
|
.dash-home-back {
|
|
flex: none;
|
|
color: var(--purple);
|
|
text-decoration: none;
|
|
font-size: 0.82em;
|
|
white-space: nowrap;
|
|
}
|
|
.dash-home-back:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.banner-thin {
|
|
text-align: center;
|
|
margin: 0;
|
|
padding: 0 1em;
|
|
font-size: 0.78em;
|
|
color: var(--purple-dim);
|
|
letter-spacing: 0.15em;
|
|
white-space: pre;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.tabbar {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
align-items: center;
|
|
gap: 0.2em;
|
|
/* On one row with the home link now; the chrome owns the gutter +
|
|
the bottom divider, so no outer padding / border here. */
|
|
}
|
|
/* Shares the flat-pill aesthetic of the shared `.hive-tab` (sub-tabs on
|
|
the standalone pages) so the chrome reads as one family. Kept a touch
|
|
weightier than bare `.hive-tab` — larger font, bold labels, count
|
|
pills — since this is the dashboard's primary nav, not a sub-strip. */
|
|
.tabbar .tab {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5em;
|
|
flex-shrink: 0;
|
|
padding: 0.4em 0.95em;
|
|
color: var(--muted);
|
|
font-family: inherit;
|
|
font-size: 0.92em;
|
|
letter-spacing: 0.06em;
|
|
text-decoration: none;
|
|
border: 0;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition:
|
|
color 0.15s ease,
|
|
background 0.15s ease;
|
|
}
|
|
.tabbar .tab:hover {
|
|
color: var(--fg);
|
|
background: var(--border);
|
|
}
|
|
.tabbar .tab.hive-tab--active {
|
|
color: var(--purple);
|
|
background: var(--border);
|
|
}
|
|
.tab-label {
|
|
font-weight: bold;
|
|
white-space: nowrap;
|
|
}
|
|
.tab-count {
|
|
display: inline-block;
|
|
background: var(--purple-dim);
|
|
color: var(--purple);
|
|
border-radius: 999px;
|
|
padding: 0 0.55em;
|
|
min-width: 1.6em;
|
|
text-align: center;
|
|
font-size: 0.82em;
|
|
font-variant-numeric: tabular-nums;
|
|
font-weight: bold;
|
|
}
|
|
/* `display` above beats the UA's `[hidden]` rule, so an empty count (the
|
|
4G3NTS and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
|
|
.tab-count[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* Tab pane visibility — createTabStrip (@hive/shared/tabs.js) sets the
|
|
`hidden` attribute on every inactive pane (resolved from the URL hash;
|
|
default — no hash — is 4G3NTS). Only the active pane stays visible. */
|
|
.tab-pane[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* FL0W is a separate page (`/flow.html`) — its full-viewport
|
|
styling lives in flow.css. */
|
|
.banner {
|
|
text-align: center;
|
|
margin: 0 0 1em 0;
|
|
font-size: 0.95em;
|
|
overflow-x: auto;
|
|
background: linear-gradient(
|
|
90deg,
|
|
var(--purple-dim) 0%,
|
|
var(--purple) 50%,
|
|
var(--purple-dim) 100%
|
|
);
|
|
background-size: 200% 100%;
|
|
background-position: 50% 0;
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
filter: drop-shadow(
|
|
0 0 6px color-mix(in srgb, var(--purple) 45%, transparent)
|
|
);
|
|
}
|
|
.banner.active {
|
|
animation: banner-shimmer 1.8s linear infinite;
|
|
}
|
|
@keyframes banner-shimmer {
|
|
from {
|
|
background-position: 200% 0;
|
|
}
|
|
to {
|
|
background-position: -100% 0;
|
|
}
|
|
}
|
|
|
|
.role {
|
|
display: inline-block;
|
|
margin-left: 0.4em;
|
|
padding: 0.05em 0.5em;
|
|
border: 1px solid;
|
|
border-radius: 2px;
|
|
font-size: 0.8em;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* Container rows: a full-height square agent icon on the left, the
|
|
identity / actions / drill-in lines stacked in the card body on the
|
|
right. Pending rows dim everything except the pending indicator. */
|
|
.containers {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4em;
|
|
}
|
|
.container-row {
|
|
padding: 0.6em 0.8em;
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
background: color-mix(in srgb, var(--bg-elev) 55%, transparent);
|
|
transition:
|
|
opacity 200ms ease,
|
|
border-color 200ms ease;
|
|
}
|
|
.container-row:not(.tombstone) {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 0.7em;
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon {
|
|
position: relative;
|
|
overflow: hidden;
|
|
flex: none;
|
|
width: 5em;
|
|
aspect-ratio: 1;
|
|
border-radius: 6px;
|
|
background-color: color-mix(in srgb, var(--crust) 60%, transparent);
|
|
cursor: pointer;
|
|
transition:
|
|
box-shadow 120ms ease,
|
|
transform 120ms ease;
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon:hover {
|
|
box-shadow: 0 0 0 2px var(--purple);
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon:focus-visible {
|
|
outline: 2px solid var(--purple);
|
|
outline-offset: 2px;
|
|
}
|
|
.container-row.selected {
|
|
border-color: var(--purple);
|
|
box-shadow: inset 0 0 0 1px var(--purple);
|
|
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
|
}
|
|
.container-row.selected > .container-icon {
|
|
box-shadow:
|
|
0 0 0 2px var(--purple),
|
|
0 0 12px -4px var(--purple);
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon > .container-icon-img {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon.icon-unreachable {
|
|
filter: grayscale(1);
|
|
opacity: 0.4;
|
|
}
|
|
.container-row .card-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* ── per-agent three-dot context menu ─────────────────────────────────────
|
|
Positioned after .card-body in the flex row. `<hive-agent-menu>`
|
|
(agent-menu/hive-agent-menu.js) is a light-DOM element with no shadow
|
|
root of its own — the generic dropdown mechanics (shadow attach,
|
|
open/close, positioning) live in the shared `<hive-menu>` component
|
|
instead (@hive/shared/hive-menu.js), which slots this element's
|
|
trigger/content nodes into position rather than moving them into its
|
|
own shadow root. That keeps these nodes in the ordinary light DOM the
|
|
whole way down, so this ordinary global stylesheet reaches them
|
|
directly — see hive-agent-menu.js's header for the full reasoning.
|
|
`hive-agent-menu` itself just needs the flex/alignment role the old
|
|
light-DOM `.agent-menu` wrapper div played in this row; the
|
|
`position: relative` an absolutely-positioned dropdown needs now lives
|
|
on `<hive-menu>`'s own `:host` instead, since that's the element whose
|
|
shadow tree the dropdown is actually positioned within. */
|
|
hive-agent-menu {
|
|
flex: none;
|
|
align-self: flex-start;
|
|
margin-top: 0.3em;
|
|
}
|
|
/* .agent-menu-btn carries no styling of its own anymore — the trigger is
|
|
a top-level `slot="trigger"` node inside <hive-menu>'s shadow tree, so
|
|
its base icon-button chrome now lives in hive-menu.css's
|
|
`::slotted([slot='trigger'])` rules instead (reachable and genuinely
|
|
generic — any <hive-menu> trigger gets the same treatment). The class
|
|
itself stays, just as an identification hook (aria-label/title
|
|
already carry the real semantics). */
|
|
/* Box chrome (background/border/radius/shadow/min-width/white-space) now
|
|
lives on <hive-menu>'s own `.menu-dropdown` wrapper (shadow-DOM-owned
|
|
markup, no slotting constraint) — this is just the `<ul>`'s own
|
|
list-reset + item padding, which only the caller (owning the actual
|
|
list markup) can set. */
|
|
.agent-menu-dropdown {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0.3em 0;
|
|
}
|
|
.agent-menu-item {
|
|
display: block;
|
|
width: 100%;
|
|
background: none;
|
|
border: none;
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
font-size: 0.88em;
|
|
letter-spacing: 0.04em;
|
|
text-align: left;
|
|
padding: 0.4em 0.9em;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
box-sizing: border-box;
|
|
}
|
|
.agent-menu-item:hover {
|
|
background: var(--border);
|
|
color: var(--purple);
|
|
}
|
|
.agent-menu-sep {
|
|
height: 1px;
|
|
background: var(--purple-dim);
|
|
margin: 0.3em 0;
|
|
padding: 0;
|
|
}
|
|
/* Hover reveal: `--menu-btn-opacity` is forced to 1 while a hover
|
|
selector (or the trigger's own open state, set by <hive-menu>) applies
|
|
— inherits down through `hive-agent-menu` → `<hive-menu>` → the
|
|
trigger button same as any custom property, shadow trees included. */
|
|
.container-row:hover hive-agent-menu {
|
|
--menu-btn-opacity: 1;
|
|
}
|
|
/* Card actions dim while a row has any pending-state badge (transient-
|
|
driven only now — see agents.js's pending-badge derivation comment;
|
|
there is no separate queued-but-not-running row state to tell apart
|
|
from this one anymore). */
|
|
.container-row.pending-running .actions {
|
|
opacity: 0.4;
|
|
pointer-events: none;
|
|
}
|
|
.container-row.pending-running {
|
|
border-color: var(--amber);
|
|
background: color-mix(in srgb, var(--amber) 5%, transparent);
|
|
}
|
|
@keyframes container-icon-spin {
|
|
from {
|
|
transform: rotate(0deg);
|
|
}
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
.container-row.pending-running > .container-icon::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 50%;
|
|
border: 3px solid transparent;
|
|
border-top-color: var(--amber);
|
|
animation: container-icon-spin 0.9s linear infinite;
|
|
pointer-events: none;
|
|
}
|
|
.container-row .head {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.5em;
|
|
margin-bottom: 0.4em;
|
|
}
|
|
.container-row .head .name {
|
|
font-size: 1.05em;
|
|
font-weight: bold;
|
|
}
|
|
.container-row .head .meta {
|
|
margin-left: auto;
|
|
}
|
|
.container-row .head .nav-strip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.35em;
|
|
}
|
|
.nav-link {
|
|
color: var(--muted);
|
|
font-size: 0.95em;
|
|
line-height: 1;
|
|
padding: 0.15em 0.35em;
|
|
border-radius: 3px;
|
|
text-decoration: none;
|
|
transition:
|
|
background 0.12s ease,
|
|
color 0.12s ease;
|
|
}
|
|
.nav-link:hover {
|
|
background: color-mix(in srgb, var(--purple) 12%, transparent);
|
|
color: var(--cyan);
|
|
text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 50%, transparent);
|
|
}
|
|
.container-row .actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4em;
|
|
}
|
|
.container-row .actions form.inline {
|
|
display: inline-block;
|
|
margin: 0;
|
|
}
|
|
|
|
.agent-status {
|
|
font-size: 0.82em;
|
|
color: var(--subtext0);
|
|
padding: 0.1em 0.3em 0.25em;
|
|
/* Row layout: the message can wrap to two lines and clamp; the age
|
|
stamp is a non-shrinking sibling so it is always shown (it wraps to
|
|
the next line if there is no room, but is never clipped away). */
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
column-gap: 0.3em;
|
|
}
|
|
.agent-status .status-msg {
|
|
flex: 0 1 auto;
|
|
min-width: 0;
|
|
/* Wrap to at most two lines, then ellipsize the overflow. */
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
line-clamp: 2;
|
|
overflow: hidden;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.agent-status .status-icon {
|
|
opacity: 0.65;
|
|
}
|
|
.agent-status .status-age {
|
|
flex: 0 0 auto;
|
|
opacity: 0.5;
|
|
font-size: 0.9em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.container-row.tombstone {
|
|
border-style: dashed;
|
|
background: color-mix(in srgb, var(--bg-elev) 35%, transparent);
|
|
opacity: 0.85;
|
|
}
|
|
.container-row.tombstone .name {
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* K3PT ST4T3 caveat and the port-collision banner both moved to the
|
|
shared <hive-warn> component — amber/no-pulse for
|
|
the standing caveat (a pulsing banner that's always present just
|
|
teaches you to stop seeing it), `severity="red" pulse` for the port
|
|
collision (an incident, not a caveat). See core.js/agents.js and
|
|
hive-warn.css for the actual styling now. */
|
|
|
|
/* Build-queue summary banner on the 4G3NTS tab moved to the shared
|
|
`JobqRollup` Preact component (`.jqr-*` classes, imported above) —
|
|
mounted by agents.js::initJobqRollup into #jobq-rollup-section. */
|
|
|
|
.pending-state {
|
|
color: var(--amber);
|
|
font-size: 0.85em;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 55%, transparent);
|
|
animation: badge-pulse 1.6s ease-in-out infinite;
|
|
}
|
|
@keyframes badge-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
/* Inline drill-in triggers (logs / config repo / view diff). */
|
|
.drill-ins {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.15em 1.1em;
|
|
margin-top: 0.4em;
|
|
}
|
|
.drill-ins .panel-trigger {
|
|
margin-top: 0;
|
|
}
|
|
|
|
ul form.inline {
|
|
display: inline-block;
|
|
}
|
|
|
|
.role-pending {
|
|
color: var(--amber);
|
|
border-color: var(--amber);
|
|
}
|
|
.btn-inline {
|
|
font-family: inherit;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
margin-left: 0.4em;
|
|
}
|
|
/* Off-palette warm-amber hover tint: not an exact theme var (brighter than
|
|
--amber), left as a literal pending a dedicated named var. */
|
|
.btn-inline:hover {
|
|
background: rgba(255, 184, 77, 0.1);
|
|
}
|
|
.kind {
|
|
display: inline-block;
|
|
margin-left: 0.4em;
|
|
padding: 0.05em 0.5em;
|
|
border: 1px solid var(--purple-dim);
|
|
color: var(--purple-dim);
|
|
border-radius: 2px;
|
|
font-size: 0.75em;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
details {
|
|
margin-top: 0.5em;
|
|
}
|
|
summary {
|
|
cursor: pointer;
|
|
color: var(--muted);
|
|
font-size: 0.85em;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
summary:hover {
|
|
color: var(--purple);
|
|
}
|
|
.diff {
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
padding: 0.8em;
|
|
margin-top: 0.4em;
|
|
overflow-x: auto;
|
|
font-size: 0.85em;
|
|
line-height: 1.4;
|
|
color: var(--muted);
|
|
white-space: pre;
|
|
}
|
|
.diff span {
|
|
display: block;
|
|
}
|
|
.diff .diff-add {
|
|
color: var(--green);
|
|
}
|
|
.diff .diff-del {
|
|
color: var(--red);
|
|
}
|
|
.diff .diff-hunk {
|
|
color: var(--cyan);
|
|
}
|
|
.diff .diff-file {
|
|
color: var(--purple);
|
|
font-weight: bold;
|
|
}
|
|
.diff .diff-ctx {
|
|
color: var(--fg);
|
|
}
|
|
|
|
footer {
|
|
margin-top: 4em;
|
|
text-align: center;
|
|
color: var(--muted);
|
|
font-size: 0.9em;
|
|
}
|
|
footer a {
|
|
color: var(--purple);
|
|
}
|
|
footer .banner-thin {
|
|
margin-bottom: 0.8em;
|
|
}
|
|
|
|
/* Selection bar — sticky-bottom strip for bulk actions. */
|
|
.selection-bar {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 40;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.6em;
|
|
padding: 0.55em 1em;
|
|
background: var(
|
|
--flow-frost-bg,
|
|
color-mix(in srgb, var(--bg) 74%, transparent)
|
|
);
|
|
-webkit-backdrop-filter: blur(12px) saturate(140%);
|
|
backdrop-filter: blur(12px) saturate(140%);
|
|
border-top: 1px solid var(--purple);
|
|
box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.4);
|
|
}
|
|
.selection-bar[hidden] {
|
|
display: none;
|
|
}
|
|
.selection-count {
|
|
color: var(--purple);
|
|
font-weight: bold;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.selection-names {
|
|
color: var(--muted);
|
|
font-size: 0.85em;
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.selection-actions {
|
|
display: inline-flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4em;
|
|
align-items: center;
|
|
}
|
|
.selection-actions .btn {
|
|
font-size: 0.75em;
|
|
padding: 0.2em 0.7em;
|
|
}
|
|
.selection-clear {
|
|
color: var(--muted);
|
|
border-color: var(--purple-dim);
|
|
font-size: 0.75em;
|
|
padding: 0.2em 0.7em;
|
|
}
|
|
/* Pad the dashboard body so the sticky bar doesn't cover the
|
|
bottom of the agent list. Only applies when bar is visible. */
|
|
body.dashboard-shell.has-selection {
|
|
padding-bottom: 4.5em;
|
|
}
|
|
|
|
/* ST4TS moved to its own page (`/stats.html`): the
|
|
window selector / summary chips / bars live in stats.css, and the
|
|
shared `.hive-stats-table` moved to common.css. */
|
|
|
|
/* ─── build tabs (builds.js) ────────────────────────────────────────
|
|
R3BU1LD QU3U3 and M3T4 1NPUTS are styled by system-sections.css and
|
|
jobq-graph.css; the BUILD L0GS list rules are below. */
|
|
|
|
.logs-toolbar {
|
|
display: flex;
|
|
gap: 0.6em;
|
|
align-items: center;
|
|
margin-bottom: 0.9em;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* ─── build-log list ──────────────────────────────────────────────
|
|
Scrollable list of log header rows; clicking a row expands full
|
|
stdout+stderr inline. */
|
|
.build-logs-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
overflow-y: auto;
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
}
|
|
.build-logs-item {
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0.15em 0;
|
|
}
|
|
.build-logs-row-btn {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4em;
|
|
align-items: baseline;
|
|
width: 100%;
|
|
background: none;
|
|
border: none;
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
font-size: 0.85em;
|
|
text-align: left;
|
|
padding: 0.25em 0.4em;
|
|
cursor: pointer;
|
|
border-radius: 3px;
|
|
}
|
|
.build-logs-row-btn:hover {
|
|
background: var(--bg-elev);
|
|
}
|
|
.build-logs-kind {
|
|
font-weight: 600;
|
|
}
|
|
.build-logs-age {
|
|
font-size: 0.88em;
|
|
}
|
|
.build-logs-cmdline {
|
|
color: var(--muted);
|
|
font-size: 0.82em;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 40em;
|
|
}
|
|
.build-logs-detail {
|
|
padding: 0 0.4em 0.4em;
|
|
}
|
|
.build-logs-output {
|
|
margin: 0.3em 0 0;
|
|
padding: 0.4em 0.6em;
|
|
background: var(--crust);
|
|
color: var(--fg);
|
|
border: 1px solid var(--purple-dim);
|
|
font-size: 0.82em;
|
|
line-height: 1.4;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
overflow-x: auto;
|
|
max-height: 30em;
|
|
overflow-y: auto;
|
|
}
|
|
.build-logs-dl {
|
|
color: var(--muted);
|
|
font-size: 0.8em;
|
|
text-decoration: none;
|
|
border: 1px solid var(--purple-dim);
|
|
padding: 0.1em 0.4em;
|
|
border-radius: 2px;
|
|
margin-top: 0.3em;
|
|
display: none;
|
|
}
|
|
.build-logs-dl:not([hidden]) {
|
|
display: inline-block;
|
|
}
|
|
.build-logs-dl:hover {
|
|
color: var(--fg);
|
|
border-color: var(--purple-dim);
|
|
}
|
|
|
|
.build-logs-live-badge {
|
|
margin-bottom: 0.4em;
|
|
}
|
|
|
|
@keyframes live-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.45;
|
|
}
|
|
}
|
|
.build-logs-live-badge.badge-running {
|
|
animation: live-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
|
|
.build-logs-runtime {
|
|
font-size: 0.85em;
|
|
color: var(--muted);
|
|
}
|