feat: move trivy auto-update, node labels, fleet topology, and single-scan SBOM to Community (#1336)

Rebalance several capabilities from the paid tier to the free Community tier:

- Managed Trivy auto-update toggle is admin-only, no longer tier-gated.
- Node labels (assign, view, manage) are available on every tier; cordon and
  FleetSync anchor reset stay paid.
- Fleet topology layout modes (Hub, Grouped, Free) are available on Community.
- Single-scan SBOM export (SPDX and CycloneDX) is admin-only on Community;
  SARIF export stays paid via a dedicated canExportSarif capability split out
  from the former shared SBOM flag.

Backend route guards and frontend affordances are updated together, with tier
and admin-role tests covering the Community-allowed and still-paid paths.
This commit is contained in:
Anso
2026-06-08 08:58:14 -04:00
committed by GitHub
parent 710647a44f
commit 54119be0c2
18 changed files with 214 additions and 74 deletions
@@ -28,7 +28,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp
interface FleetTopologyProps {
nodes: FleetTopologyNode[];
onNodeClick?: (nodeId: number) => void;
isPaid: boolean;
mode: LayoutMode;
onModeChange: (mode: LayoutMode) => void;
savedPositions: SavedPositions;
@@ -274,7 +273,6 @@ function TopologyToolbar({
export function FleetTopology({
nodes: fleetNodes,
onNodeClick,
isPaid,
mode,
onModeChange,
savedPositions,
@@ -289,13 +287,7 @@ export function FleetTopology({
const savedPositionsRef = useRef(savedPositions);
savedPositionsRef.current = savedPositions;
// Defensive: a Community user who somehow lands in a paid mode (older
// localStorage value, manual edit) gets snapped back to Hub. The toolbar
// also hides these modes for them, so this is a belt-and-suspenders guard.
const effectiveMode: LayoutMode = useMemo(() => {
if ((mode === 'grouped' || mode === 'free') && !isPaid) return 'hub';
return mode;
}, [mode, isPaid]);
const effectiveMode: LayoutMode = mode;
// Only re-layout when the topology *shape* changes (nodes added/removed,
// type/status/label flips, mode flips). Metric value changes alone must
@@ -397,8 +389,8 @@ export function FleetTopology({
return (
<div className="rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel overflow-hidden">
{isPaid && <TopologyToolbar mode={effectiveMode} onModeChange={onModeChange} />}
{isPaid && allRemotesUnlabeled && (
<TopologyToolbar mode={effectiveMode} onModeChange={onModeChange} />
{allRemotesUnlabeled && (
<div className="px-3 py-1.5 text-[11px] text-muted-foreground bg-muted/30 border-b border-card-border">
No node labels assigned. Add labels in Settings · Nodes to see remotes cluster.
</div>