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.
This commit is contained in:
Anso
2026-05-07 05:41:53 -04:00
committed by GitHub
parent 907e7427e5
commit 77d5ff58d3
16 changed files with 1074 additions and 46 deletions
+1
View File
@@ -129,6 +129,7 @@
"features/pilot-agent",
"features/sencho-mesh",
"features/fleet-view",
"features/fleet-actions",
"features/fleet-sync",
"features/fleet-backups",
"features/remote-updates",
+64
View File
@@ -0,0 +1,64 @@
---
title: Fleet Actions
description: Run fleet-wide bulk operations from one place — stop stacks across nodes by label, or apply labels to many stacks at once.
---
<Note>
Fleet Actions require a Sencho **Skipper** or **Admiral** license.
</Note>
The **Fleet Actions** tab inside Fleet centralizes bulk operations that span more than a single stack and would otherwise need many manual clicks. It lives next to **Deployments** and **Traffic** in the Fleet view, after the separator that follows Status.
Fleet Actions covers the operations that aren't already exposed elsewhere in Sencho:
- **Stop fleet by label** dispatches a stop to every stack labeled with a given name across every node in the fleet.
- **Bulk label assign** applies the same label set to many stacks on one node in a single round trip.
Other bulk actions you may be looking for live in their natural homes:
| Goal | Where to do it |
|------|----------------|
| Restart, stop, or update a subset of stacks | **Bulk mode** in the stack sidebar (press `B` to enable, then check the stacks you want) |
| Schedule a recurring restart, update, or snapshot | **Schedules** in the primary navigation |
| Trigger a Sencho self-update on remote nodes | **Check Updates** button on the Fleet tab |
## Stop fleet by label
This card sends a stop to every stack on every node that has a label matching the name you type. Labels are matched **by name** across the fleet, so a label called `production` on one node and a separate label also called `production` on another node both match.
1. Open **Fleet > Fleet Actions**.
2. In the **Stop fleet by label** card, type a label name. The input suggests names that already exist on any node.
3. Click **Stop matching stacks**.
4. Confirm the destructive action. Sencho dispatches stops per node.
5. Results appear inline below the card, grouped by node, with per-stack success or failure rows.
A node that has no label by that name appears in the results with **no matching label** instead of an error.
## Bulk label assign
This card replaces the label set on many stacks at once on a single node.
1. Pick the node you want to update from the dropdown.
2. Check the stacks you want to update. **Select all** is available once stacks load.
3. Click each label pill to toggle it. The chosen pills become the new label set for every selected stack.
4. Click **Apply to N stacks** and confirm.
Selecting no labels and applying clears existing label assignments on the chosen stacks. The selected label set always **replaces** the existing one rather than appending to it.
## Permissions
Both cards require the **admin** role and a **Skipper** or **Admiral** license. Community-tier users see a calm explainer card in this tab instead of the action surface.
## Troubleshooting
**Nothing happens when I click "Stop matching stacks".**
Verify a label by that name exists on at least one node. The autocomplete suggestions are aggregated from every reachable node; if your label only exists on a node that is currently offline, it will not appear in the suggestions but the request still tries every node.
**A node shows "Label not present" but I know I created the label there.**
Labels are stored per node. The fleet stop matches by **name** only. If the label was renamed on one node, the new name is what gets matched. Check **Settings > Labels** on the affected node.
**Bulk label assign reports "Invalid stack name" for one row.**
Stack names must be alphanumeric with dashes and underscores. The endpoint validates each entry independently, so a single bad name does not block the rest of the batch.
**The Fleet Actions tab is missing from Fleet.**
Confirm the active license is **Skipper** or **Admiral** under **Settings > License**. The tab itself is always visible, but the action cards only render at paid tiers.
+1 -1
View File
@@ -27,7 +27,7 @@ For example, a Postgres `db` service in a stack named `api` on a node named `ops
## Enable the mesh
1. Open **Fleet → Traffic · Routing**.
1. Open **Fleet → Traffic**.
2. Toggle **mesh** on for each node you want to participate.
3. Click **Add stack to mesh** on any node and tick the stacks whose services should be reachable cross-node.