diff --git a/frontend/src/components/ApiTokensSection.tsx b/frontend/src/components/ApiTokensSection.tsx index e7a28afb..5e87f080 100644 --- a/frontend/src/components/ApiTokensSection.tsx +++ b/frontend/src/components/ApiTokensSection.tsx @@ -5,7 +5,7 @@ import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Combobox } from '@/components/ui/combobox'; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; +import { ConfirmModal } from '@/components/ui/modal'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { copyToClipboard } from '@/lib/clipboard'; @@ -60,6 +60,7 @@ export function ApiTokensSection() { const [creating, setCreating] = useState(false); const [showForm, setShowForm] = useState(false); const [newToken, setNewToken] = useState<{ id: number; token: string } | null>(null); + const [revokeTarget, setRevokeTarget] = useState(null); const [formName, setFormName] = useState(''); const [formScope, setFormScope] = useState('read-only'); @@ -244,27 +245,14 @@ export function ApiTokensSection() { {SCOPE_LABELS[token.scope] || token.scope} - - - - - - - Revoke API token? - - Revoking {token.name} will immediately invalidate it. Any pipelines or scripts using this token will stop working. - - - - Cancel - handleRevoke(token.id)} className="bg-destructive text-destructive-foreground hover:bg-destructive/90"> - Revoke - - - - +
@@ -282,6 +270,26 @@ export function ApiTokensSection() {
))} + + { if (!open) setRevokeTarget(null); }} + variant="destructive" + kicker="API TOKEN · REVOKE · IRREVERSIBLE" + title="Revoke API token" + confirmLabel="Revoke" + onConfirm={() => { + if (revokeTarget) { + const id = revokeTarget.id; + setRevokeTarget(null); + handleRevoke(id); + } + }} + > +

+ Invalidates {revokeTarget?.name} immediately. Any pipelines or scripts using this token will stop working. +

+
diff --git a/frontend/src/components/RegistriesSection.tsx b/frontend/src/components/RegistriesSection.tsx index c99b97e7..2dca79d3 100644 --- a/frontend/src/components/RegistriesSection.tsx +++ b/frontend/src/components/RegistriesSection.tsx @@ -5,7 +5,7 @@ import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Combobox } from '@/components/ui/combobox'; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; +import { ConfirmModal } from '@/components/ui/modal'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { AdmiralGate } from './AdmiralGate'; @@ -95,6 +95,7 @@ export function RegistriesSection() { const [editingId, setEditingId] = useState(null); const [testingId, setTestingId] = useState(null); const [testingForm, setTestingForm] = useState(false); + const [deleteRegistry, setDeleteRegistry] = useState(null); const [formName, setFormName] = useState(''); const [formUrl, setFormUrl] = useState(''); @@ -422,32 +423,15 @@ export function RegistriesSection() { - - - - - - - Delete registry? - - Deleting {reg.name} will remove its stored credentials. Stacks using images from this registry will fail to pull until credentials are re-added. - - - - Cancel - handleDelete(reg.id)} className="bg-destructive text-destructive-foreground hover:bg-destructive/90"> - Delete - - - - +
@@ -468,6 +452,26 @@ export function RegistriesSection() {
))} + + { if (!open) setDeleteRegistry(null); }} + variant="destructive" + kicker="REGISTRY · DELETE · IRREVERSIBLE" + title="Delete registry" + confirmLabel="Delete" + onConfirm={() => { + if (deleteRegistry) { + const id = deleteRegistry.id; + setDeleteRegistry(null); + handleDelete(id); + } + }} + > +

+ Removes {deleteRegistry?.name} and its stored credentials. Stacks using images from this registry will fail to pull until credentials are re-added. +

+
diff --git a/frontend/src/components/settings/CloudBackupSection.tsx b/frontend/src/components/settings/CloudBackupSection.tsx index 1c4efc37..3704f215 100644 --- a/frontend/src/components/settings/CloudBackupSection.tsx +++ b/frontend/src/components/settings/CloudBackupSection.tsx @@ -5,21 +5,12 @@ import { Label } from '@/components/ui/label'; import { Skeleton } from '@/components/ui/skeleton'; import { Combobox } from '@/components/ui/combobox'; import { TogglePill } from '@/components/ui/toggle-pill'; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog'; +import { ConfirmModal } from '@/components/ui/modal'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { formatBytes } from '@/lib/utils'; import { AdmiralGate } from '@/components/AdmiralGate'; -import { Cloud, CloudOff, RefreshCw, CheckCircle2, AlertCircle, Loader2, Trash2, Download } from 'lucide-react'; +import { Cloud, CloudOff, RefreshCw, CheckCircle2, Loader2, Trash2, Download } from 'lucide-react'; import { SettingsPrimaryButton } from './SettingsActions'; import { useMastheadStats } from './MastheadStatsContext'; @@ -521,25 +512,19 @@ export function CloudBackupSection() { )} - !open && setDeleteKey(null)}> - - - - - Delete cloud snapshot? - - - This permanently removes the archive from your bucket. The local SQLite copy is unaffected. - - - - Cancel - - Delete - - - - + !open && setDeleteKey(null)} + variant="destructive" + kicker="CLOUD · DELETE · IRREVERSIBLE" + title="Delete cloud snapshot" + confirmLabel="Delete" + onConfirm={confirmDelete} + > +

+ Permanently removes the archive from your bucket. The local SQLite copy is unaffected. +

+
); diff --git a/frontend/src/components/settings/UsersSection.tsx b/frontend/src/components/settings/UsersSection.tsx index 14a14df3..064f6280 100644 --- a/frontend/src/components/settings/UsersSection.tsx +++ b/frontend/src/components/settings/UsersSection.tsx @@ -5,10 +5,7 @@ import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Combobox } from '@/components/ui/combobox'; -import { - AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, - AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from '@/components/ui/alert-dialog'; +import { ConfirmModal } from '@/components/ui/modal'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { useAuth, type UserRole } from '@/context/AuthContext'; @@ -53,6 +50,10 @@ export function UsersSection() { const [formConfirmPassword, setFormConfirmPassword] = useState(''); const [formRole, setFormRole] = useState('viewer'); + // Per-row destructive confirms + const [resetMfaTarget, setResetMfaTarget] = useState(null); + const [deleteTarget, setDeleteTarget] = useState(null); + const fetchUsers = async () => { try { const res = await apiFetch('/users', { localOnly: true }); @@ -455,45 +456,23 @@ export function UsersSection() { {u.mfaEnabled && ( - - - - - - - Reset two-factor authentication for "{u.username}"? - - This removes the user's authenticator enrolment and backup codes. They will sign in with just their password on their next login and can re-enrol from their account settings. Use this when a user has lost access to their authenticator. - - - - Cancel - handleResetMfa(u.id, u.username)}>Reset 2FA - - - + )} - - - - - - - Delete user "{u.username}"? - - This action cannot be undone. The user will lose access immediately. - - - - Cancel - handleDelete(u.id)}>Delete - - - + @@ -503,6 +482,45 @@ export function UsersSection() { )} + + { if (!open) setResetMfaTarget(null); }} + kicker="USERS · RESET 2FA" + title={`Reset 2FA for ${resetMfaTarget?.username ?? ''}`} + confirmLabel="Reset 2FA" + onConfirm={() => { + if (resetMfaTarget) { + const user = resetMfaTarget; + setResetMfaTarget(null); + handleResetMfa(user.id, user.username); + } + }} + > +

+ Removes the user's authenticator enrolment and backup codes. They will sign in with just their password on their next login and can re-enrol from their account settings. Use this when a user has lost access to their authenticator. +

+
+ + { if (!open) setDeleteTarget(null); }} + variant="destructive" + kicker="USERS · DELETE · IRREVERSIBLE" + title={`Delete user "${deleteTarget?.username ?? ''}"`} + confirmLabel="Delete" + onConfirm={() => { + if (deleteTarget) { + const id = deleteTarget.id; + setDeleteTarget(null); + handleDelete(id); + } + }} + > +

+ Removes the user immediately. They lose access right away. +

+