Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/features/observers/ObserverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function ObserverPage({ wsManager }: { wsManager: WsManager }) {
</span>}
/>
<div className="relative flex min-h-0 min-w-0 flex-1">
<div className="flex min-h-0 w-full shrink-0 flex-col p-4 md:w-[260px] md:pr-0">
<div className="flex min-h-0 w-full shrink-0 flex-col md:w-[260px] md:p-4 md:pr-0">
<ObserverSidebar observers={observers} filtered={Boolean(search || status || type || broker || scope)} isPending={directory.isPending} isError={directory.isError}
onRetry={() => void directory.refetch()} range={range} selectedId={id} onSelect={select} />
</div>
Expand Down
24 changes: 12 additions & 12 deletions src/features/observers/ObserverSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import { getTopObservers } from "../../api/client";
import { useRegion } from "../../hooks/useRegion";
import { formatCount } from "../../lib/formatters";
import { Card } from "../stats/cards";
import { Segmented } from "../stats/Segmented";
import { RANGE_MS, type StatsRange } from "../stats/types";
import { observerName as nameOf } from "./observer-filter";
Expand Down Expand Up @@ -36,17 +35,18 @@ export function ObserverSidebar({ observers, filtered, isPending, isError, onRet
}, [observers, sort, counts]);

return (
<Card
title={t("tabs.Observers")}
right={<Segmented size="sm" ariaLabel={t("observerPage.sort")} value={sort} onChange={(v) => setSort(v as Sort)}
options={[{ value: "activity", label: t("observerPage.sortActivity") }, { value: "name", label: t("observerPage.sortName") }]} />}
className="flex min-h-0 w-full flex-col"
>
// a card at md+, an edge-to-edge list like the Nodes tab below it
<div className="flex min-h-0 w-full flex-col bg-bg-base md:rounded-lg md:border md:border-border md:bg-bg-surface md:p-3.5">
<div className="flex shrink-0 items-center justify-between gap-2 border-b border-border-subtle px-4 py-2 md:mb-2.5 md:border-0 md:p-0">
<div className="font-mono text-[11px] font-semibold uppercase tracking-wider text-text-normal">{t("tabs.Observers")}</div>
<Segmented size="sm" ariaLabel={t("observerPage.sort")} value={sort} onChange={(v) => setSort(v as Sort)}
options={[{ value: "activity", label: t("observerPage.sortActivity") }, { value: "name", label: t("observerPage.sortName") }]} />
</div>
{isPending ? <div className="py-6 text-center font-mono text-[11px] text-text-dim">{t("common.loading")}</div>
: isError ? <button type="button" onClick={onRetry} className="py-6 text-center font-mono text-[11px] text-danger">{t("common.loadFailed")} · {t("observerPage.retry")}</button>
: rows.length === 0 ? <div className="py-6 text-center font-mono text-[11px] text-text-dim">{t(filtered ? "observerPage.noMatches" : "observerPage.none")}</div>
: (
<div role="listbox" aria-label={t("tabs.Observers")} className="-mx-1 flex min-h-0 flex-col gap-0.5 overflow-y-auto px-1">
<div role="listbox" aria-label={t("tabs.Observers")} className="flex min-h-0 flex-col overflow-y-auto md:-mx-1 md:gap-0.5 md:px-1">
{rows.map((o) => {
const active = o.id === selectedId;
const count = counts.get(o.id);
Expand All @@ -57,21 +57,21 @@ export function ObserverSidebar({ observers, filtered, isPending, isError, onRet
role="option"
aria-selected={active}
onClick={() => onSelect(o.id)}
className={`relative shrink-0 overflow-hidden rounded border-l-2 px-2.5 py-1.5 text-left transition-colors ${
className={`relative shrink-0 overflow-hidden border-b border-l-2 border-b-border-subtle px-4 py-3 text-left md:rounded md:border-b-0 md:px-2.5 md:py-1.5 transition-colors ${
active ? "border-primary bg-primary/10" : "border-transparent hover:bg-text-normal/3"
}`}
>
{count != null && <div className="absolute inset-y-0 left-0 bg-secondary/15" style={{ width: `${(count / max) * 100}%` }} aria-hidden />}
{count != null && <div className="absolute inset-y-0 left-0 bg-secondary/8 md:bg-secondary/15" style={{ width: `${(count / max) * 100}%` }} aria-hidden />}
<div className="relative flex items-center gap-2">
<span aria-hidden className={`h-1.5 w-1.5 shrink-0 rounded-full ${deriveObserverStatus(o) === "online" ? "bg-green" : "bg-text-dim/30"}`} />
<span className={`min-w-0 flex-1 truncate font-mono text-[12px] ${active ? "text-text-bright" : "text-text-normal"}`}>{nameOf(o)}</span>
<span className={`min-w-0 flex-1 truncate font-mono text-[13px] md:text-[12px] ${active ? "text-text-bright" : "text-text-normal"}`}>{nameOf(o)}</span>
<span className="shrink-0 font-mono text-[11px] tabular-nums text-text-muted">{count != null ? formatCount(count) : "—"}</span>
</div>
</button>
);
})}
</div>
)}
</Card>
</div>
);
}
Loading