diff --git a/packages/api/public/login.js b/packages/api/public/login.js index 133a980..4cb190c 100644 --- a/packages/api/public/login.js +++ b/packages/api/public/login.js @@ -1,4 +1,6 @@ (() => { + // Shared with the console (useTheme.ts, index.html) so one choice applies everywhere. + const THEME_KEY = 'floci-theme'; const root = document.documentElement; const buttons = document.querySelectorAll('.tg button'); const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches; @@ -6,7 +8,7 @@ function setTheme(theme) { root.setAttribute('data-theme', theme); try { - localStorage.setItem('thecloud-theme', theme); + localStorage.setItem(THEME_KEY, theme); } catch (ignored) { // Theme persistence is optional when browser storage is unavailable. } @@ -15,7 +17,7 @@ let initialTheme = matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; try { - const savedTheme = localStorage.getItem('thecloud-theme'); + const savedTheme = localStorage.getItem(THEME_KEY) ?? localStorage.getItem('thecloud-theme'); if (savedTheme === 'dark' || savedTheme === 'light' || savedTheme === 'cyber') { initialTheme = savedTheme; } diff --git a/packages/frontend/src/index.css b/packages/frontend/src/index.css index db17acb..d54042d 100644 --- a/packages/frontend/src/index.css +++ b/packages/frontend/src/index.css @@ -779,6 +779,47 @@ pre, position: relative; } +@media (max-width: 640px) { + .topbar { + gap: 8px; + padding: 0 10px; + } + + .topbar .search { + flex: 1 1 auto; + min-width: 0; + } + + .topbar .search input { + padding-right: 8px; + } + + .topbar .kbd, + .topbar .account-meta, + .topbar .connection-target { + display: none; + } + + .topbar .connection-state { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + } + + .topbar .connection { + flex: 0 0 auto; + padding: 8px; + } + + .topbar .account-trigger { + gap: 4px; + padding: 0 6px; + } +} + .topbar-status:empty + .account-switcher { margin-left: auto; } diff --git a/packages/frontend/src/lib/useTheme.ts b/packages/frontend/src/lib/useTheme.ts index c487811..a57158a 100644 --- a/packages/frontend/src/lib/useTheme.ts +++ b/packages/frontend/src/lib/useTheme.ts @@ -2,9 +2,17 @@ import {useEffect, useState} from 'react' export type Theme = 'dark' | 'light' | 'cyber' +/** Shared with the login page (public/login.js) and index.html. */ +const THEME_KEY = 'floci-theme' + function getStored(): Theme { - const v = localStorage.getItem('floci-theme') - return v === 'light' || v === 'cyber' ? v : 'dark' + try { + const v = localStorage.getItem(THEME_KEY) + return v === 'light' || v === 'cyber' ? v : 'dark' + } catch { + // localStorage unavailable (private mode) — fall back to the default theme. + return 'dark' + } } export function useTheme() { @@ -12,7 +20,11 @@ export function useTheme() { useEffect(() => { document.documentElement.setAttribute('data-theme', theme) - localStorage.setItem('floci-theme', theme) + try { + localStorage.setItem(THEME_KEY, theme) + } catch { + // Persisting the choice is optional when storage is unavailable. + } }, [theme]) return {theme, setTheme} diff --git a/packages/frontend/src/pages/DiagnosticsPage.test.tsx b/packages/frontend/src/pages/DiagnosticsPage.test.tsx index 2f74ea2..1041d22 100644 --- a/packages/frontend/src/pages/DiagnosticsPage.test.tsx +++ b/packages/frontend/src/pages/DiagnosticsPage.test.tsx @@ -49,7 +49,9 @@ test('summarises the runtime and lists failing console services first', async () expect(screen.getByText('1 not running')).toBeInTheDocument() expect(screen.getByText('2 / 3')).toBeInTheDocument() - const rows = within(screen.getByRole('region', {name: 'Console services'})).getAllByRole('row') + const consoleServices = screen.getByRole('region', {name: 'Console services'}) + const rows = within(consoleServices).getAllByRole('row') + expect(rows).toHaveLength(2) expect(rows[1]).toHaveTextContent('messaging') expect(rows[1]).toHaveTextContent('Service is not running (RUNTIME_UNAVAILABLE)') @@ -59,6 +61,25 @@ test('summarises the runtime and lists failing console services first', async () expect(screen.getByText('OIDC single sign-on')).toBeInTheDocument() }) +test('keeps responding console services behind a toggle', async () => { + const user = userEvent.setup() + api.getCloudDiagnostics.mockResolvedValue({ + ...healthy, + consoleServices: healthy.consoleServices.map((service) => ({...service, runtime: 'reachable', error: null, errorCode: null})), + } satisfies RuntimeDiagnostics) + renderPage() + const consoleServices = await screen.findByRole('region', {name: 'Console services'}) + + expect(within(consoleServices).getByText('All 2 responding')).toBeInTheDocument() + expect(within(consoleServices).queryByRole('table')).not.toBeInTheDocument() + + await user.click(within(consoleServices).getByRole('button', {name: 'Show 2 responding'})) + expect(within(consoleServices).getAllByRole('row')).toHaveLength(3) + + await user.click(within(consoleServices).getByRole('button', {name: 'Hide responding'})) + expect(within(consoleServices).queryByRole('table')).not.toBeInTheDocument() +}) + test('filters emulated services by console coverage and state', async () => { const user = userEvent.setup() api.getCloudDiagnostics.mockResolvedValue(healthy) diff --git a/packages/frontend/src/pages/DiagnosticsPage.tsx b/packages/frontend/src/pages/DiagnosticsPage.tsx index 4fddce8..f0a9a3f 100644 --- a/packages/frontend/src/pages/DiagnosticsPage.tsx +++ b/packages/frontend/src/pages/DiagnosticsPage.tsx @@ -88,17 +88,25 @@ function DiagnosticsReport({report}: {report: RuntimeDiagnostics}) { } function ConsoleServicesTable({services}: {services: CloudServiceStatus[]}) { + const [showResponding, setShowResponding] = useState(false) const failing = services.filter((service) => service.runtime !== 'reachable') - const ordered = [...failing, ...services.filter((service) => service.runtime === 'reachable')] + const responding = services.filter((service) => service.runtime === 'reachable') + const ordered = showResponding ? [...failing, ...responding] : failing return (

Console services

- {failing.length ? `${failing.length} of ${services.length} failing` : `${services.length} responding`} +
+ {failing.length ? `${failing.length} of ${services.length} failing` : `All ${services.length} responding`} + {responding.length > 0 && ( + + )} +
- {services.length === 0 - ?

No console services are registered for this cloud.

- : + {services.length === 0 &&

No console services are registered for this cloud.

} + {ordered.length > 0 &&
{ordered.map((service) => (
ServiceStatusLatencyDetail