Repository navigation
feat(admin): add a Hosts page listing fleet hosts with filters - #1975
Devin-Holland wants to merge 2 commits into
Conversation
A staff-only /admin/hosts page next to Regions, backed by central-manager's new GET /Admin/Host/ and gated on host:read. Shows running hosts by default, with a status filter (including all statuses), an organization picker that finds both the org's dedicated hosts and the shared hosts its instances run on, provider / location / region filters, and a client-side search over name, id, FQDN and IP. The picker resolves a label, an exact id, a unique name or a pasted org- id, and says why instead of querying for a shared name or partial text. The org directory loads only for accounts holding org:read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'Hosts' section to the admin dashboard, allowing administrators to view, search, and filter the fleet of hosts by status, organization, cloud provider, location, and region. It adds the /admin/hosts route, navigation items, permission checks for host:read, and comprehensive tests. The review feedback focuses on improving TypeScript type safety under strict null checks by explicitly converting potential null values from API responses to undefined when using them in helper functions and maps.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DavidCockerill
left a comment
There was a problem hiding this comment.
Good call rejecting a shared name in the org resolver instead of taking the first match. Nothing blocking; the two inline notes are about the page giving a confident wrong answer to someone looking up a customer in a hurry. As you said, this waits on central-manager#953 being deployed.
Findings drafted by claude-opus-5-5 via daivid-review, reviewed and posted by @DavidCockerill.
| ? 'ambiguous' in organizationPick | ||
| ? `${organizationPick.ambiguous} organizations are named “${debouncedOrganizationText.trim()}” — pick one from the list.` | ||
| : canReadOrganizations | ||
| ? `No organization matches “${debouncedOrganizationText.trim()}”.` |
There was a problem hiding this comment.
“No organization matches” is shown whenever the directory has no entry for the text, including while it's still loading or after it failed. For example, open the page and type Acme before the org sweep finishes. This message appears and the host query stays off until the list arrives, and if a page of the sweep fails (it runs with retry: false) it stays that way. Could this say no match only once orgResult has fully loaded and isn't truncated, and otherwise say the list is loading or unavailable and that a pasted org- id still works? A page test that leaves the /Admin/Organization/ mock pending and types Globex would pin it. Non-blocking.
There was a problem hiding this comment.
Fixed in 93bb826: "No organization matches" now shows only once the org list has fully loaded (and wasn't cut off at the page cap). While it's loading, after it fails, or past the cap, the picker says so and points to pasting an org- id, which still filters. New page tests cover a pending and a failing org list; both fail on the previous code.
🤖 Generated by Claude Code (Claude Opus 5.5); posted via @Devin-Holland.
| } | ||
|
|
||
| export function HostsIndex() { | ||
| const [status, setStatus] = useState<HostStatusFilter>('RUNNING'); |
There was a problem hiding this comment.
With an organization picked, the RUNNING default leaves out that customer's DEGRADED hosts. Say a customer reports trouble and their instance sits on a host that went DEGRADED. Filtering by their org lists only the healthy hosts, and the one that matters is missing unless someone thinks to change the status. CM already accepts a comma list (status=RUNNING,DEGRADED), so could the default, or a new first option, cover both? Non-blocking, since the copy and the select both say Running.
There was a problem hiding this comment.
Added a Running + degraded option directly under Running in 93bb826; it sends status=RUNNING,DEGRADED. I kept Running as the default because the original ask was running-only by default. Making the combined option the default is a one-line change if we'd rather.
🤖 Generated by Claude Code (Claude Opus 5.5); posted via @Devin-Holland.
…t is still loading The status filter gains a "Running + degraded" option, so a customer's degraded host is one click away when looking them up. Running stays the default. The organization picker only says nothing matches once the directory has fully loaded. While it loads, after it fails, or past its page cap, it says so instead and points to pasting an org- id, which still filters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DavidCockerill
left a comment
There was a problem hiding this comment.
Both notes are covered, thanks. I'm fine with Running as the default now that the combined option is right under it. Good to go once central-manager#953 is deployed.
Findings drafted by claude-opus-5-5 via daivid-review, reviewed and posted by @DavidCockerill.
⊙ Problem
Staff can see regions in the admin section but not hosts. Answering "which hosts does this customer run on?" or "what is degraded in us-ord?" takes direct database queries.
🤔 Is a full page warranted now?
Yes, as asked: a list with filters next to Regions, read-only. Host edits stay where they are; this only adds a view.
💡 Solution
host:read, backed by central-manager's newGET /Admin/Host/(HarperFast/central-manager#953).org-id (resolver). It finds the org's dedicated hosts and the shared hosts its instances run on. Ambiguous text explains itself instead of querying, as does an org list still loading or failed.⚖️ Alternatives
🤔 Should the org picker accept a pasted id without the directory?
Yes: the directory needs
org:read, whichhost:readdoes not imply, so a pastedorg-id is the fallback. Dropping it would leave such accounts with no org filter.🔧 Changes
src/features/admin/hosts/index.tsx— the page: filters, org resolution, table.src/features/admin/hosts/queries/getHosts.ts— query string and request; blank filters are left out because the endpoint rejects them.src/features/admin/routes.ts,src/features/admin/components/AdminShell.tsx— the route, and a nav item requiringhost:read; AdminShell's existing per-page check redirects a deep link from anyone without it.src/integrations/api/api.patch.d.ts—host:readpermission and the response type until the generated OpenAPI types include the endpoint.✅ Verification
pnpm test5711 passing on this head (5875 on a trial rebase onto currentstage). Page tests and query tests cover the default RUNNING request, server-side org filter, ambiguous and partial names, a loading or failed org list, the no-org:readpath, error state and client search. The ambiguity guard fails its tests when removed. Shell visibility and route tests include Hosts.🤖 Generated by Claude Code (Claude Opus 5.5); posted via @Devin-Holland.
Related PRs: 2 independent
Complexity: medium
Review-Coverage: authored=claude; ran=cursor-composer,gemini,codex; blocked=domain(auth); declined=cursor-grok,cursor-kimi,cursor-muse,muse; rounds=2; full=1 @ 93bb826
Review-Attention: read ~8m (raised: degraded review) @ 93bb826