feat: RBAC, atomic deployments, and fleet-wide backups (Pro) (#181)

* feat: add RBAC viewer accounts, atomic deployments, and fleet-wide backups (Pro)

Introduces three Pro-tier features:

- RBAC: Multi-user system with admin/viewer roles, user management UI,
  automatic migration from single-admin credentials, viewer restrictions
  across the entire UI (read-only editor, hidden action buttons)

- Atomic Deployments: Pre-deploy file backup to .sencho-backup/, automatic
  rollback on health probe failure, manual rollback button, health probes
  added to stack updates, webhook-triggered deploys use atomic rollback

- Fleet-Wide Backups: Point-in-time snapshots of compose files across all
  nodes (local + remote), stored centrally in SQLite, per-stack restore
  with optional redeploy, graceful handling of offline nodes

* fix(settings): use correct ProGate prop name in UsersSection

* fix(settings): remove unused isPro prop from UsersSection

* fix(auth): fetch user info after login and setup so isAdmin is set correctly
This commit is contained in:
Anso
2026-03-26 12:51:30 -04:00
committed by GitHub
parent 72670ffb42
commit db73d7671a
21 changed files with 2466 additions and 292 deletions
+3 -3
View File
@@ -34,7 +34,7 @@ export class WebhookService {
);
}
public async execute(webhookId: number, action: string, triggerSource: string | null): Promise<{ success: boolean; error?: string; duration_ms: number }> {
public async execute(webhookId: number, action: string, triggerSource: string | null, atomic?: boolean): Promise<{ success: boolean; error?: string; duration_ms: number }> {
const db = DatabaseService.getInstance();
const webhook = db.getWebhook(webhookId);
if (!webhook) throw new Error('Webhook not found');
@@ -62,7 +62,7 @@ export class WebhookService {
const compose = ComposeService.getInstance(defaultNodeId);
switch (action) {
case 'deploy':
await compose.deployStack(webhook.stack_name);
await compose.deployStack(webhook.stack_name, undefined, atomic);
break;
case 'restart':
await compose.runCommand(webhook.stack_name, 'restart');
@@ -74,7 +74,7 @@ export class WebhookService {
await compose.runCommand(webhook.stack_name, 'start');
break;
case 'pull':
await compose.updateStack(webhook.stack_name);
await compose.updateStack(webhook.stack_name, undefined, atomic);
break;
default:
throw new Error(`Unknown action: ${action}`);