Skip to content

feat(admin): add a Hosts page listing fleet hosts with filters - #1975

Open
Devin-Holland wants to merge 2 commits into
stagefrom
admin-hosts-page
Open

Devin-Holland wants to merge 2 commits into
stagefrom
admin-hosts-page

Conversation

@Devin-Holland

@Devin-Holland Devin-Holland commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Deploy order: HarperFast/central-manager#953 must merge and deploy first. This page calls its new GET /Admin/Host/ and shows "Couldn't load hosts." until that is live.

⊙ 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

⚖️ Alternatives

  • Client-side filtering only, like Regions — "show all" includes every removed host ever created, and the org filter needs the server-side join.

🤔 Should the org picker accept a pasted id without the directory?

Yes: the directory needs org:read, which host:read does not imply, so a pasted org- id is the fallback. Dropping it would leave such accounts with no org filter.

🔧 Changes

✅ Verification

  • pnpm test 5711 passing on this head (5875 on a trial rebase onto current stage). 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:read path, error state and client search. The ambiguity guard fails its tests when removed. Shell visibility and route tests include Hosts.
  • Rendered the real page against stub data in the browser (light, dark, narrow): status, org and search filters behave as described.
  • Not run against a deployed central-manager; the endpoint itself was smoke-tested on released Harper 5.2.15 and 5.3.1 in HarperFast/central-manager#953.

🤖 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

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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/features/admin/hosts/index.tsx
Comment thread src/features/admin/hosts/index.tsx
Comment thread src/features/admin/hosts/index.tsx
Comment thread src/features/admin/hosts/index.tsx
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 80.09% 15014 / 18745
🔵 Statements 80.04% 15947 / 19923
🔵 Functions 75.57% 4017 / 5315
🔵 Branches 74.71% 11666 / 15613
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/admin/routes.ts 72.72% 100% 60% 72.72% 33-34, 40-41, 47-48
src/features/admin/components/AdminShell.tsx 29.41% 46.66% 60% 33.33% 42-64
src/features/admin/hosts/index.tsx 84.61% 78.94% 77.77% 88.88% 91, 101, 126, 127, 128, 198-199, 203, 208-210, 222, 260-272
src/features/admin/hosts/queries/getHosts.ts 100% 100% 100% 100%
Generated in workflow #2390 for commit 93bb826 by the Vitest Coverage Report Action

@Devin-Holland
Devin-Holland marked this pull request as ready for review October 15, 2026 16:21
@Devin-Holland
Devin-Holland requested a review from a team as a code owner October 15, 2026 16:21

@DavidCockerill DavidCockerill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/features/admin/hosts/index.tsx Outdated
? 'ambiguous' in organizationPick
? `${organizationPick.ambiguous} organizations are named “${debouncedOrganizationText.trim()}” — pick one from the list.`
: canReadOrganizations
? `No organization matches “${debouncedOrganizationText.trim()}”.`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 DavidCockerill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants