Files
sencho/frontend/src/components/fleet/FleetActions/FleetActionsTab.tsx
T
Anso 77d5ff58d3 feat(fleet): add Fleet Actions tab for cross-node bulk operations (#963)
* feat(fleet): add Fleet Actions tab for cross-node bulk operations

Introduces a new "Actions" sub-tab in Fleet view with two Skipper+ cards
that fill gaps in the existing surface:

- Stop fleet by label: matches a label name across every node and stops
  every stack assigned to it, reporting per-node and per-stack results.
- Bulk label assign: applies the same label set to many stacks on one
  node in a single round trip.

Other bulk operations stay in their existing homes (sidebar bulk mode,
Schedules, NodeUpdatesSheet) to avoid duplicate surfaces.

Backend:
- POST /api/fleet/labels/fleet-stop (gateway-orchestrated, multi-node)
- POST /api/fleet-actions/labels/bulk-assign (per-node, capped at 1000)
- Tightens /api/fleet proxy-exempt prefix to /api/fleet/ so
  /api/fleet-actions/* is routed through the proxy for per-node calls.
- Exports activeBulkActions from labels.ts so fleet-stop and label-action
  share the per-node lock and cannot double-stop the same containers.
- Extracts containerActionForStack helper from stacks.ts for reuse.

* chore(fleet): rename Actions tab to Fleet Actions and reorder Fleet sub-tabs

- Tab label "Actions" -> "Fleet Actions" so the surface is unambiguous
  alongside Schedules and the sidebar bulk bar.
- Reorder Fleet sub-tabs as Overview / Snapshots / Status | Deployments /
  Traffic / Fleet Actions, with the separator after Status.
- Rename "Traffic · Routing" -> "Traffic" and update Sencho Mesh docs to
  match the shorter label.
- Update Fleet Actions docs to the new tab name and placement.
2026-05-07 05:41:53 -04:00

50 lines
1.6 KiB
TypeScript

import { Square, Tags } from 'lucide-react';
import { useLicense } from '@/context/LicenseContext';
import type { FleetNode } from '@/components/FleetView/types';
import { LabelFleetStopCard } from './cards/LabelFleetStopCard';
import { BulkLabelAssignCard } from './cards/BulkLabelAssignCard';
interface Props {
nodes: FleetNode[];
}
export function FleetActionsTab({ nodes }: Props) {
const { isPaid } = useLicense();
if (nodes.length === 0) {
return (
<div className="text-sm text-stat-subtitle">Add a node to the fleet to use bulk actions.</div>
);
}
if (!isPaid) {
// Both actions are Skipper+. Community users see a calm empty state with
// upgrade context rather than a stripped-down launcher.
return (
<EmptyState />
);
}
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<LabelFleetStopCard nodes={nodes} accentTone="rose" icon={Square} />
<BulkLabelAssignCard nodes={nodes} accentTone="purple" icon={Tags} />
</div>
);
}
function EmptyState() {
return (
<div className="rounded-lg border border-card-border/60 bg-card p-8 text-center">
<div className="mx-auto mb-3 inline-flex h-10 w-10 items-center justify-center rounded-md bg-glass-highlight">
<Tags className="h-5 w-5 text-stat-subtitle" strokeWidth={1.5} />
</div>
<h3 className="text-sm font-medium text-stat-value mb-1">Fleet-wide bulk actions</h3>
<p className="text-xs text-stat-subtitle max-w-md mx-auto">
Stop stacks across every node by label name, and assign labels to many
stacks in one shot. Available on Skipper and Admiral.
</p>
</div>
);
}