UI polish across sections: one server selector, skeletons, inline errors
CI / check (push) Successful in 2m39s
Hub / check (push) Successful in 50s
CI / image (push) Successful in 1m30s
Hub / image (push) Successful in 1m37s

- Every section puts the hub's server selector in the chrome slot, the
  same place and gap as the Dashboard.
- Data regions load with a Skeleton and fail inline with Retry; KPIs show
  the no-data dash instead of 0 when a server did not answer. Settings
  keeps its header and selector while loading or failing.
- Cache, Apps and Logs headers group secondary or hub-refused actions in
  menus; app descriptions clamp to two lines; Unknown cells say why.
- DNS Client: Protocol label, Resolve and Import together. DHCP and Query
  Logs empty states with actions. View Logs list capped with scroll.
- Blocking: Lists KPI tiles like the Overview, app list table fits its
  panel with one-line URLs. Status re-reads on tab return at most once
  a minute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-11-02 21:42:28 +07:00
1 parent 172e06f1ce
commit ba3cdd08d2
43 files changed
+671 -261

No files matched your search

+2 -1
View File
@@ -157,7 +157,8 @@ describe('who reports the failure', () => {
vi.spyOn(client, 'apiRequest').mockResolvedValue({ kind: 'error', message: 'boom' } as never)
render(<Zones token="t" canModify canDelete />)
expect(await screen.findByText(/boom/)).toBeInTheDocument()
/* The notice and the region both carry it: the region stays when the notice is dismissed. */
expect((await screen.findAllByText(/boom/)).length).toBeGreaterThan(0)
expect(screen.queryByText(/Could not refresh/)).not.toBeInTheDocument()
})
})