feat(security): reflow the node Security page for mobile (#1372)

* feat(security): reflow the node Security page for mobile

Below the md breakpoint the Security page now reads as a phone surface
instead of a squeezed desktop, with no change to the desktop layout.

- Masthead stat cluster moves into a full-width 3-cell strip
  (critical / high / last scan) below the tab strip, since the masthead
  hides its inline cluster on a phone.
- The eight-section tab strip becomes a horizontally scrollable mono row
  with an edge mask-fade and a cyan underline on the active tab; every
  section stays reachable by scroll.
- The six totals render as a 3x2 hairline-divided grid instead of the
  640px-wide rail that forced a horizontal scroll.
- The Images tab becomes a filterable, scrollable list (severity dot,
  truncated ref, freshness, critical/high count tags) with a chip row,
  in place of the desktop table.
- A freshness footer band states scan recency and scanner version.

All mobile treatment is gated by useIsMobile() or max-md: utilities, so
the desktop view is byte-identical. Charts are reused full-width.

* feat(security): make the mobile Security page a bespoke masthead-led screen

On a phone the Security page now drops the global top bar and leads with
its masthead (the notifications + more-menu cluster moves into the
masthead's right slot), matching Home and Fleet so the mobile shell is
continuous across pages. The view is reclassified bespoke and rendered
through the masthead-led path; the desktop layout is unchanged.

The mobile "more" menu now lists every destination instead of omitting
the bottom-tab views, so the same menu opens the same set on every
screen rather than changing contents from page to page.
This commit is contained in:
Anso
2026-06-14 21:28:13 -04:00
committed by GitHub
parent a5109e7916
commit 0066887cee
12 changed files with 712 additions and 96 deletions
+22 -1
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
import { lazy, Suspense, useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
import { Button } from './ui/button';
import { Plus, Loader2, ChevronLeft } from 'lucide-react';
import { UserProfileDropdown } from './UserProfileDropdown';
@@ -52,6 +52,11 @@ import { deriveMobileSurface, type MobileView } from './EditorLayout/mobile-surf
import { BESPOKE_MOBILE_VIEWS } from './EditorLayout/mobile-treatments';
import type { SectionId } from './settings/types';
// The Security page is heavy (charts, scan sheets) and already code-split on the
// desktop content path, so the bespoke phone screen reuses the same lazy chunk
// rather than pulling SecurityView into the main shell bundle.
const SecurityView = lazy(() => import('./SecurityView').then(m => ({ default: m.SecurityView })));
export default function EditorLayout() {
const { isAdmin, can } = useAuth();
const { status: trivy } = useTrivyStatus();
@@ -760,6 +765,22 @@ export default function EditorLayout() {
return <MobileSchedules headerActions={mobileMastheadActions} />;
case 'settings':
return <MobileSettings headerActions={mobileMastheadActions} />;
case 'security':
return (
<Suspense
fallback={(
<div className="flex h-full items-center justify-center">
<Loader2 className="h-5 w-5 animate-spin text-stat-subtitle" strokeWidth={1.5} />
</div>
)}
>
<SecurityView
activeTab={securityTab}
onTabChange={setSecurityTab}
headerActions={mobileMastheadActions}
/>
</Suspense>
);
default:
return workspaceEl;
}