mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-29 03:36:55 +00:00
feat: open security basics, manual fleet ops, and basic fleet management to Community (#930)
Realign tier guards to the user-stated philosophy: Community covers
deploy/monitor at scale plus security basics, Skipper adds automation
and advanced fleet management, Admiral keeps enterprise control.
Community now includes:
- Trivy install / uninstall / update from the Settings Hub (admin role)
- CVE suppressions CRUD (admin role; replicates fleet-wide)
- Manual image scan with vuln, secret, and misconfig results
- Stack-config scan, scan comparison
- Manual fleet snapshots: create, list, view, restore, delete
- Per-node Sencho self-update (Check Updates + per-node Update)
- Fleet Overview search, sort, filters, node-card expand, auto-refresh
Stays paid:
- Scan policies with block_on_deploy enforcement (Skipper+)
- SBOM (SPDX, CycloneDX), SARIF export (Skipper+)
- Bulk Update All across the fleet (Skipper+)
- Scheduled snapshot create (now Skipper, was Admiral)
- Trivy auto-update toggle, fleet-wide policy push (Admiral)
The Settings -> Security tab is unhidden by setting the registry tier to
null. The SecuritySection no longer early-returns a PaidGate; the policy
list, Add Policy button, and policy dialogs are wrapped in {isPaid && }.
The Fleet view drops isPaid gates on the Snapshots tab, Check Updates
button, per-node update handlers, OverviewToolbar grid controls, the
NodeCard expand affordance, and the auto-refresh notice. The
NodeUpdatesSheet receives a canBulkUpdate prop and gates the Update All
button on it. useFleetUpdateStatus and useFleetPolling drop their isPaid
guards so polling runs for Community; useFleetOverview drops the isPaid
wrap on the filter and sort path.
Backend route guards are flipped per the matrix above. The scheduler
tick and requireScheduledTaskTier add 'snapshot' to the Skipper+ branch.
Backend test assertions are inverted for the now-Community endpoints
and a positive Skipper-snapshot-task test is added.
Documentation across features/, api-reference/, and operations/ is
updated to reflect the new tier mapping.
This commit is contained in:
@@ -476,7 +476,6 @@ fleetRouter.get('/node/:nodeId/stacks/:stackName/containers', authMiddleware, as
|
||||
});
|
||||
|
||||
fleetRouter.get('/update-status', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
try {
|
||||
const db = DatabaseService.getInstance();
|
||||
const nodes = db.getNodes();
|
||||
@@ -625,7 +624,6 @@ fleetRouter.get('/update-status', authMiddleware, async (req: Request, res: Resp
|
||||
});
|
||||
|
||||
fleetRouter.post('/nodes/:nodeId/update', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
if (!requireAdmin(req, res)) return;
|
||||
try {
|
||||
const nodeId = parseIntParam(req, res, 'nodeId', 'node ID');
|
||||
@@ -779,7 +777,6 @@ fleetRouter.post('/update-all', authMiddleware, async (req: Request, res: Respon
|
||||
});
|
||||
|
||||
fleetRouter.delete('/nodes/:nodeId/update-status', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
try {
|
||||
const nodeId = parseIntParam(req, res, 'nodeId', 'node ID');
|
||||
if (nodeId === null) return;
|
||||
@@ -797,7 +794,6 @@ fleetRouter.delete('/nodes/:nodeId/update-status', authMiddleware, async (req: R
|
||||
});
|
||||
|
||||
fleetRouter.delete('/update-status', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
// Pre-fetch fresh latest version so the next GET has up-to-date data.
|
||||
if (req.query.recheck === 'true') {
|
||||
await getLatestVersion(true);
|
||||
@@ -810,11 +806,10 @@ fleetRouter.delete('/update-status', authMiddleware, async (req: Request, res: R
|
||||
res.status(204).send();
|
||||
});
|
||||
|
||||
// ─── Fleet Snapshots (Skipper+) ───
|
||||
// ─── Fleet Snapshots (manual: Community; scheduled: Skipper+) ───
|
||||
|
||||
fleetRouter.post('/snapshots', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requireAdmin(req, res)) return;
|
||||
if (!requirePaid(req, res)) return;
|
||||
|
||||
try {
|
||||
const { description = '' } = req.body;
|
||||
@@ -909,8 +904,6 @@ fleetRouter.post('/snapshots', authMiddleware, async (req: Request, res: Respons
|
||||
});
|
||||
|
||||
fleetRouter.get('/snapshots', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
|
||||
try {
|
||||
const limit = Math.min(parseInt(req.query.limit as string, 10) || 50, 100);
|
||||
const offset = parseInt(req.query.offset as string, 10) || 0;
|
||||
@@ -926,8 +919,6 @@ fleetRouter.get('/snapshots', authMiddleware, async (req: Request, res: Response
|
||||
});
|
||||
|
||||
fleetRouter.get('/snapshots/:id', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
|
||||
try {
|
||||
const id = parseIntParam(req, res, 'id', 'snapshot ID');
|
||||
if (id === null) return;
|
||||
@@ -972,7 +963,6 @@ fleetRouter.get('/snapshots/:id', authMiddleware, async (req: Request, res: Resp
|
||||
|
||||
fleetRouter.post('/snapshots/:id/restore', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requireAdmin(req, res)) return;
|
||||
if (!requirePaid(req, res)) return;
|
||||
|
||||
try {
|
||||
const snapshotId = parseIntParam(req, res, 'id', 'snapshot ID');
|
||||
@@ -1085,7 +1075,6 @@ fleetRouter.post('/snapshots/:id/restore', authMiddleware, async (req: Request,
|
||||
|
||||
fleetRouter.delete('/snapshots/:id', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requireAdmin(req, res)) return;
|
||||
if (!requirePaid(req, res)) return;
|
||||
|
||||
try {
|
||||
const id = parseIntParam(req, res, 'id', 'snapshot ID');
|
||||
|
||||
Reference in New Issue
Block a user