mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
refactor(frontend): migrate Stack/Fleet confirms and Git source dialog (#951)
* refactor(frontend): migrate Stack/Fleet confirms and Git source dialog
- LocalUpdateConfirmDialog -> ConfirmModal with kicker LOCAL · UPDATE
- GitSourcePanel form -> Modal with kicker {STACK} · GIT SOURCE; the
custom three-button footer (Remove / Pull now / Save) stays as-is
since ModalFooter's two-slot pattern doesn't fit a left-aligned
destructive action; ModalHeader provides the canonical chrome
- GitSourcePanel remove confirm -> destructive ConfirmModal with
kicker {STACK} · GIT · DISCONNECT
* test(e2e): scope git-source dialog assertion to the heading
The migration adds a kicker rune "<STACK> · GIT SOURCE" inside the
dialog. The previous locator getByText('Git Source', { exact: false })
matched both the kicker (uppercase, mono) and the italic-serif title,
producing a strict-mode violation. Use getByRole('heading') to target
the title specifically.
This commit is contained in:
@@ -36,7 +36,9 @@ async function openGitSourcePanel(page: Page) {
|
||||
const gitBtn = page.getByRole('button', { name: /Git Source/i });
|
||||
await expect(gitBtn).toBeVisible({ timeout: 10_000 });
|
||||
await gitBtn.click();
|
||||
await expect(page.getByRole('dialog').getByText('Git Source', { exact: false })).toBeVisible({ timeout: 5_000 });
|
||||
// Match the title heading specifically; the modal also has a mono kicker
|
||||
// ("<STACK> · GIT SOURCE") that would satisfy a plain getByText match.
|
||||
await expect(page.getByRole('dialog').getByRole('heading', { name: /git source/i })).toBeVisible({ timeout: 5_000 });
|
||||
}
|
||||
|
||||
test.describe('Git Sources', () => {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { Download } from 'lucide-react';
|
||||
import {
|
||||
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
||||
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { ConfirmModal } from '@/components/ui/modal';
|
||||
|
||||
interface LocalUpdateConfirmDialogProps {
|
||||
open: boolean;
|
||||
@@ -12,23 +9,22 @@ interface LocalUpdateConfirmDialogProps {
|
||||
|
||||
export function LocalUpdateConfirmDialog({ open, onOpenChange, onConfirm }: LocalUpdateConfirmDialogProps) {
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Update local node?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will pull the latest Sencho image and restart the server. The dashboard will be
|
||||
briefly disconnected and will automatically reconnect when the update completes.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={onConfirm}>
|
||||
<ConfirmModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
kicker="LOCAL · UPDATE"
|
||||
title="Update local node"
|
||||
confirmLabel={
|
||||
<>
|
||||
<Download className="w-4 h-4 mr-1.5" strokeWidth={1.5} />
|
||||
Update & Restart
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
Update & restart
|
||||
</>
|
||||
}
|
||||
onConfirm={onConfirm}
|
||||
>
|
||||
<p className="text-sm text-stat-subtitle">
|
||||
Pulls the latest Sencho image and restarts the server. The dashboard briefly disconnects and reconnects automatically when the update completes.
|
||||
</p>
|
||||
</ConfirmModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { GitBranch, Loader2, Trash2, RefreshCw, Save, AlertCircle } from 'lucide-react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Modal, ModalHeader, ConfirmModal } from '@/components/ui/modal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
@@ -290,18 +280,17 @@ export function GitSourcePanel({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-xl w-[95vw] p-0 gap-0">
|
||||
<DialogHeader className="px-6 pt-6 pb-4 border-b border-glass-border">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<GitBranch className="w-4 h-4" strokeWidth={1.5} />
|
||||
Git Source
|
||||
<span className="font-mono tabular-nums text-xs text-stat-subtitle">{stackName}</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Link this stack to a Git repository so compose updates can be pulled on demand or via webhook.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Modal open={open} onOpenChange={onOpenChange} size="xl">
|
||||
<ModalHeader
|
||||
kicker={`${stackName.toUpperCase()} · GIT SOURCE`}
|
||||
title={
|
||||
<span className="flex items-center gap-2">
|
||||
<GitBranch className="w-5 h-5" strokeWidth={1.5} />
|
||||
Git source
|
||||
</span>
|
||||
}
|
||||
description="Link this stack to a Git repository so compose updates can be pulled on demand or via webhook."
|
||||
/>
|
||||
|
||||
<ScrollArea className="max-h-[70vh]">
|
||||
<div className="px-6 py-5 space-y-5">
|
||||
@@ -416,8 +405,7 @@ export function GitSourcePanel({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
||||
<GitSourceDiffDialog
|
||||
open={diffOpen}
|
||||
@@ -432,22 +420,20 @@ export function GitSourcePanel({
|
||||
onDismiss={dismissPending}
|
||||
/>
|
||||
|
||||
<AlertDialog open={removeConfirmOpen} onOpenChange={setRemoveConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Remove Git source?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
The stack files on disk will be left in place. You can reconfigure the source later at any time.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={remove} disabled={deleting}>
|
||||
Remove
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<ConfirmModal
|
||||
open={removeConfirmOpen}
|
||||
onOpenChange={setRemoveConfirmOpen}
|
||||
variant="destructive"
|
||||
kicker={`${stackName.toUpperCase()} · GIT · DISCONNECT`}
|
||||
title="Remove Git source"
|
||||
confirmLabel={deleting ? 'Removing...' : 'Remove'}
|
||||
confirming={deleting}
|
||||
onConfirm={remove}
|
||||
>
|
||||
<p className="text-sm text-stat-subtitle">
|
||||
Disconnects the stack from its Git source. The stack files on disk are left in place and you can reconfigure the source later at any time.
|
||||
</p>
|
||||
</ConfirmModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user