Compare commits

..

2 Commits

7 changed files with 206 additions and 303 deletions
+12 -15
View File
@@ -3,6 +3,7 @@ import KeyIcon from '@mui/icons-material/Key'
import BlockIcon from '@mui/icons-material/Block'
import VisibilityIcon from '@mui/icons-material/Visibility'
import Alert from '@mui/material/Alert'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import FormDialogContent from '../components/FormDialogContent'
import {
Box,
@@ -656,21 +657,17 @@ export default function ClientCertificatesPage() {
</DialogActions>
</Dialog>
<Dialog open={Boolean(revokeItem)} onClose={() => { if (!busy) setRevokeItem(null) }} fullWidth maxWidth="sm">
<DialogTitle>Revoke Client Certificate</DialogTitle>
<FormDialogContent>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null}
<Typography variant="body2" color="text.secondary">
Revoke {revokeItem?.common_name} ({revokeItem?.cert_id})?
</Typography>
</FormDialogContent>
<DialogActions>
<Button variant="contained" color="error" onClick={revoke} disabled={busy}>
{busy ? 'Revoking...' : 'Revoke'}
</Button>
<Button onClick={() => setRevokeItem(null)} disabled={busy}>Cancel</Button>
</DialogActions>
</Dialog>
<ConfirmDeleteDialog
open={Boolean(revokeItem)}
title="Revoke Client Certificate"
prompt={`Revoke ${revokeItem?.common_name || ''} (${revokeItem?.cert_id || ''})?`}
error={dialogError}
busy={busy}
confirmText="Revoke"
busyText="Revoking..."
onConfirm={revoke}
onClose={() => setRevokeItem(null)}
/>
</Box>
)
}
+17 -38
View File
@@ -12,6 +12,7 @@ import EditIcon from '@mui/icons-material/Edit'
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
import SectionCard from '../components/SectionCard'
import DeleteIcon from '@mui/icons-material/Delete'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import FormDialogContent from '../components/FormDialogContent'
import SelectField from '../components/SelectField'
@@ -648,15 +649,21 @@ export default function ProjectHomePage() {
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog open={deleteOpen} onClose={closeDelete} maxWidth="sm" fullWidth>
<DialogTitle>Delete Project</DialogTitle>
<FormDialogContent>
{deleteError ? <Alert severity="error">{deleteError}</Alert> : null}
{deleteLoading ? (
<Typography variant="body2" color="text.secondary">
Checking project contents...
</Typography>
) : deleteCounts ? (
<ConfirmDeleteDialog
open={deleteOpen}
title="Delete Project"
prompt={deleteLoading ? 'Checking project contents...' : 'Type the project slug to confirm deletion.'}
expectedText={deleteCounts && deleteCounts.repos + deleteCounts.issues + deleteCounts.wiki + deleteCounts.uploads > 0 ? (project?.slug || undefined) : undefined}
confirmLabel="Project Slug"
confirmValue={deleteConfirm}
onConfirmValueChange={setDeleteConfirm}
error={deleteError}
busy={deleting}
confirmDisabled={deleteLoading || !project || !deleteCounts}
onConfirm={handleDelete}
onClose={closeDelete}
>
{deleteLoading ? null : deleteCounts ? (
deleteCounts.repos + deleteCounts.issues + deleteCounts.wiki + deleteCounts.uploads === 0 ? (
<Typography variant="body2" color="text.secondary">
This project has no repositories, issues, wiki pages, or files.
@@ -670,38 +677,10 @@ export default function ProjectHomePage() {
<Typography variant="body2">Issues: {deleteCounts.issues}</Typography>
<Typography variant="body2">Wiki pages: {deleteCounts.wiki}</Typography>
<Typography variant="body2">Files: {deleteCounts.uploads}</Typography>
<Typography variant="body2" color="text.secondary">
Type the project slug to confirm deletion.
</Typography>
<TextField
label="Project Slug"
value={deleteConfirm}
onChange={(event) => setDeleteConfirm(event.target.value)}
/>
</Box>
)
) : null}
</FormDialogContent>
<DialogActions>
<Button
onClick={handleDelete}
variant="contained"
color="error"
disabled={
deleting ||
deleteLoading ||
!project ||
(deleteCounts
? deleteCounts.repos + deleteCounts.issues + deleteCounts.wiki + deleteCounts.uploads > 0 &&
deleteConfirm !== project.slug
: true)
}
>
{deleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={closeDelete}>Cancel</Button>
</DialogActions>
</Dialog>
</ConfirmDeleteDialog>
</Box>
)
}
+17 -38
View File
@@ -2,6 +2,7 @@ import AddIcon from '@mui/icons-material/Add'
import DeleteIcon from '@mui/icons-material/Delete'
import EditIcon from '@mui/icons-material/Edit'
import Alert from '@mui/material/Alert'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import { Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, List, ListItem, ListItemText, MenuItem, Paper, Tab, Tabs, TextField, Typography } from '@mui/material'
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
@@ -520,15 +521,21 @@ export default function ProjectsPage() {
</Box>
</FormDialogContent>
</Dialog>
<Dialog open={Boolean(deleteProject)} onClose={closeDelete} maxWidth="sm" fullWidth>
<DialogTitle>Delete Project</DialogTitle>
<FormDialogContent>
{deleteError ? <Alert severity="error">{deleteError}</Alert> : null}
{deleteLoading ? (
<Typography variant="body2" color="text.secondary">
Checking project contents...
</Typography>
) : deleteCounts ? (
<ConfirmDeleteDialog
open={Boolean(deleteProject)}
title="Delete Project"
prompt={deleteLoading ? 'Checking project contents...' : 'Type the project slug to confirm deletion.'}
expectedText={deleteCounts && deleteCounts.repos + deleteCounts.issues + deleteCounts.wiki + deleteCounts.uploads > 0 ? (deleteProject?.slug || undefined) : undefined}
confirmLabel="Project Slug"
confirmValue={deleteConfirm}
onConfirmValueChange={setDeleteConfirm}
error={deleteError}
busy={deleting}
confirmDisabled={deleteLoading || !deleteProject || !deleteCounts}
onConfirm={handleDelete}
onClose={closeDelete}
>
{deleteLoading ? null : deleteCounts ? (
deleteCounts.repos + deleteCounts.issues + deleteCounts.wiki + deleteCounts.uploads === 0 ? (
<Typography variant="body2" color="text.secondary">
This project has no repositories, issues, wiki pages, or files.
@@ -542,38 +549,10 @@ export default function ProjectsPage() {
<Typography variant="body2">Issues: {deleteCounts.issues}</Typography>
<Typography variant="body2">Wiki pages: {deleteCounts.wiki}</Typography>
<Typography variant="body2">Files: {deleteCounts.uploads}</Typography>
<Typography variant="body2" color="text.secondary">
Type the project slug to confirm deletion.
</Typography>
<TextField
label="Project Slug"
value={deleteConfirm}
onChange={(event) => setDeleteConfirm(event.target.value)}
/>
</Box>
)
) : null}
</FormDialogContent>
<DialogActions>
<Button
onClick={handleDelete}
variant="contained"
color="error"
disabled={
deleting ||
deleteLoading ||
!deleteProject ||
(deleteCounts
? deleteCounts.repos + deleteCounts.issues + deleteCounts.wiki + deleteCounts.uploads > 0 &&
deleteConfirm !== deleteProject.slug
: true)
}
>
{deleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={closeDelete}>Cancel</Button>
</DialogActions>
</Dialog>
</ConfirmDeleteDialog>
</Box>
)
}
+30 -49
View File
@@ -25,6 +25,7 @@ import RepoSubNav from '../components/RepoSubNav'
import TintedPanel from '../components/TintedPanel'
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'
import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
type RepoDockerDetailPageProps = {
initialRepo?: Repo
@@ -482,60 +483,40 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
)}
</TintedPanel>
</Box>
<Dialog open={deleteTagOpen} onClose={() => setDeleteTagOpen(false)} maxWidth="xs" fullWidth>
<DialogTitle>Delete tag</DialogTitle>
<FormDialogContent>
{deleteError ? <Alert severity="error">{deleteError}</Alert> : null}
<Typography variant="body2" sx={{ mt: 1 }}>
<ConfirmDeleteDialog
open={deleteTagOpen}
title="Delete tag"
prompt="Type the tag name to confirm deletion."
expectedText={deleteTagName}
confirmLabel="Type tag to confirm"
confirmValue={deleteTagConfirm}
onConfirmValueChange={setDeleteTagConfirm}
error={deleteError}
busy={deleting}
onConfirm={handleDeleteTag}
onClose={() => setDeleteTagOpen(false)}
>
<Typography variant="body2">
Delete tag "{deleteTagName}"?
</Typography>
<TextField
label="Type tag to confirm"
value={deleteTagConfirm}
onChange={(event) => setDeleteTagConfirm(event.target.value)}
fullWidth
sx={{ mt: 1 }}
/>
</FormDialogContent>
<DialogActions>
<Button
onClick={handleDeleteTag}
variant="contained"
color="error"
disabled={deleting || deleteTagConfirm.trim() !== deleteTagName}
</ConfirmDeleteDialog>
<ConfirmDeleteDialog
open={deleteImageOpen}
title="Delete image"
prompt="Type the image name to confirm deletion."
expectedText={deleteImageLabel}
confirmLabel="Type image name to confirm"
confirmValue={deleteImageConfirm}
onConfirmValueChange={setDeleteImageConfirm}
error={deleteError}
busy={deleting}
onConfirm={handleDeleteImage}
onClose={() => setDeleteImageOpen(false)}
>
{deleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={() => setDeleteTagOpen(false)}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog open={deleteImageOpen} onClose={() => setDeleteImageOpen(false)} maxWidth="xs" fullWidth>
<DialogTitle>Delete image</DialogTitle>
<FormDialogContent>
{deleteError ? <Alert severity="error">{deleteError}</Alert> : null}
<Typography variant="body2" sx={{ mt: 1 }}>
<Typography variant="body2">
Delete image "{deleteImageLabel || '(root)'}" and all tags?
</Typography>
<TextField
label="Type image name to confirm"
value={deleteImageConfirm}
onChange={(event) => setDeleteImageConfirm(event.target.value)}
fullWidth
sx={{ mt: 1 }}
/>
</FormDialogContent>
<DialogActions>
<Button
onClick={handleDeleteImage}
variant="contained"
color="error"
disabled={deleting || deleteImageConfirm.trim() !== deleteImageLabel}
>
{deleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={() => setDeleteImageOpen(false)}>Cancel</Button>
</DialogActions>
</Dialog>
</ConfirmDeleteDialog>
<Dialog open={renameTagOpen} onClose={() => setRenameTagOpen(false)} maxWidth="xs" fullWidth>
<DialogTitle>Rename tag</DialogTitle>
<FormDialogContent>
+16 -25
View File
@@ -33,6 +33,7 @@ import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutli
import FolderIcon from '@mui/icons-material/Folder'
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import MonitorHeartOutlinedIcon from '@mui/icons-material/MonitorHeartOutlined'
import ProjectNavBar from '../components/ProjectNavBar'
import RepoSubNav from '../components/RepoSubNav'
@@ -1325,35 +1326,25 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
<Button onClick={() => setUploadOpen(false)}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog open={deleteOpen} onClose={() => setDeleteOpen(false)} maxWidth="xs" fullWidth>
<DialogTitle>{deleteIsFile ? 'Delete file' : 'Delete folder'}</DialogTitle>
<FormDialogContent>
{deleteError ? <Alert severity="error">{deleteError}</Alert> : null}
<Typography variant="body2" sx={{ mt: 1 }}>
<ConfirmDeleteDialog
open={deleteOpen}
title={deleteIsFile ? 'Delete file' : 'Delete folder'}
prompt="Type the name to confirm deletion."
expectedText={deleteName}
confirmLabel="Type name to confirm"
confirmValue={deleteConfirm}
onConfirmValueChange={setDeleteConfirm}
error={deleteError}
busy={deleting}
onConfirm={handleDeleteFolder}
onClose={() => setDeleteOpen(false)}
>
<Typography variant="body2">
{deleteIsFile
? `Delete file "${deleteName}"?`
: `Delete folder "${deleteName}" and all of its contents?`}
</Typography>
<TextField
label="Type name to confirm"
value={deleteConfirm}
onChange={(event) => setDeleteConfirm(event.target.value)}
fullWidth
sx={{ mt: 1 }}
/>
</FormDialogContent>
<DialogActions>
<Button
onClick={handleDeleteFolder}
variant="contained"
color="error"
disabled={deleting || deleteConfirm.trim() !== deleteName}
>
{deleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={() => setDeleteOpen(false)}>Cancel</Button>
</DialogActions>
</Dialog>
</ConfirmDeleteDialog>
<Dialog
open={statusOpen}
onClose={() => {
+33 -60
View File
@@ -4,6 +4,7 @@ import EditIcon from '@mui/icons-material/Edit'
import LinkOffIcon from '@mui/icons-material/LinkOff'
import LinkIcon from '@mui/icons-material/Link'
import Alert from '@mui/material/Alert'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import FormDialogContent from '../components/FormDialogContent'
import {
Box,
@@ -581,15 +582,21 @@ export default function ReposPage() {
<Button onClick={() => setEditRepo(null)}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog open={Boolean(deleteRepo)} onClose={closeDelete} maxWidth="sm" fullWidth>
<DialogTitle>Delete Repository</DialogTitle>
<FormDialogContent>
{deleteError ? <Alert severity="error">{deleteError}</Alert> : null}
{deleteLoading ? (
<Typography variant="body2" color="text.secondary">
Loading repository stats...
</Typography>
) : deleteStats ? (
<ConfirmDeleteDialog
open={Boolean(deleteRepo)}
title="Delete Repository"
prompt={deleteLoading ? 'Loading repository stats...' : 'Type the repository name to confirm deletion.'}
expectedText={deleteRepo?.name}
confirmLabel="Repository Name"
confirmValue={deleteConfirm}
onConfirmValueChange={setDeleteConfirm}
error={deleteError}
busy={deleting}
confirmDisabled={deleteLoading || !deleteRepo}
onConfirm={handleDelete}
onClose={closeDelete}
>
{deleteLoading ? null : deleteStats ? (
<Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="body2" color="text.secondary">
This will permanently delete the repository and all its data.
@@ -600,41 +607,28 @@ export default function ReposPage() {
) : (
<Typography variant="body2">Last commit: none</Typography>
)}
{deleteStats.last_message ? (
<Typography variant="body2">Message: {deleteStats.last_message}</Typography>
) : null}
<Typography variant="body2" color="text.secondary">
Type the repository name to confirm deletion.
</Typography>
<TextField
label="Repository Name"
value={deleteConfirm}
onChange={(event) => setDeleteConfirm(event.target.value)}
fullWidth
/>
{deleteStats.last_message ? <Typography variant="body2">Message: {deleteStats.last_message}</Typography> : null}
</Box>
) : (
<Typography variant="body2" color="text.secondary">
Unable to load repository stats.
</Typography>
)}
</FormDialogContent>
<DialogActions>
<Button
onClick={handleDelete}
variant="contained"
color="error"
disabled={deleting || deleteLoading || !deleteRepo || deleteConfirm !== deleteRepo.name}
</ConfirmDeleteDialog>
<ConfirmDeleteDialog
open={bulkDeleteOpen}
title="Delete Repositories"
prompt={`Type DELETE ${selectedRepoIds.length} to confirm.`}
expectedText={`DELETE ${selectedRepoIds.length}`}
confirmLabel="Confirmation"
confirmValue={bulkDeleteConfirm}
onConfirmValueChange={setBulkDeleteConfirm}
error={bulkDeleteError}
busy={bulkDeleting}
onConfirm={handleBulkDelete}
onClose={closeBulkDelete}
>
{deleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={closeDelete}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog open={bulkDeleteOpen} onClose={closeBulkDelete} maxWidth="sm" fullWidth>
<DialogTitle>Delete Repositories</DialogTitle>
<FormDialogContent>
{bulkDeleteError ? <Alert severity="error">{bulkDeleteError}</Alert> : null}
<>
<Typography variant="body2" color="text.secondary">
You are deleting {selectedRepoIds.length} repositories. This action is permanent.
</Typography>
@@ -647,29 +641,8 @@ export default function ReposPage() {
</Typography>
))}
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
Type DELETE {selectedRepoIds.length} to confirm.
</Typography>
<TextField
sx={{ mt: 1 }}
label="Confirmation"
value={bulkDeleteConfirm}
onChange={(event) => setBulkDeleteConfirm(event.target.value)}
fullWidth
/>
</FormDialogContent>
<DialogActions>
<Button
onClick={handleBulkDelete}
variant="contained"
color="error"
disabled={bulkDeleting || bulkDeleteConfirm !== `DELETE ${selectedRepoIds.length}`}
>
{bulkDeleting ? 'Deleting...' : 'Delete'}
</Button>
<Button onClick={closeBulkDelete}>Cancel</Button>
</DialogActions>
</Dialog>
</>
</ConfirmDeleteDialog>
<Dialog open={foreignOpen} onClose={closeForeign} maxWidth="sm" fullWidth>
<DialogTitle>Add Foreign Repository</DialogTitle>
<FormDialogContent>
+33 -30
View File
@@ -12,6 +12,7 @@ import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
import { useEffect, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import FormDialogContent from '../components/FormDialogContent'
import SSHAccessProfileDetailsDialog from '../components/SSHAccessProfileDetailsDialog'
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
@@ -68,12 +69,14 @@ export default function SSHServersPage() {
const [dialogError, setDialogError] = useState<string | null>(null)
const [saving, setSaving] = useState(false)
const [deleteItem, setDeleteItem] = useState<SSHAccessProfile | null>(null)
const [deleteConfirm, setDeleteConfirm] = useState('')
const [serverDialogOpen, setServerDialogOpen] = useState(false)
const [editingServerID, setEditingServerID] = useState<string | null>(null)
const [serverForm, setServerForm] = useState<SSHServerFormState>(emptyServerForm())
const [serverDialogError, setServerDialogError] = useState<string | null>(null)
const [serverSaving, setServerSaving] = useState(false)
const [deleteServerItem, setDeleteServerItem] = useState<SSHServer | null>(null)
const [deleteServerConfirm, setDeleteServerConfirm] = useState('')
const [viewServerItem, setViewServerItem] = useState<SSHServer | null>(null)
const [viewSharedServerItem, setViewSharedServerItem] = useState<SSHServer | null>(null)
const [hostKeyServer, setHostKeyServer] = useState<SSHServer | null>(null)
@@ -342,10 +345,12 @@ export default function SSHServersPage() {
try {
await api.deleteSSHAccessProfileForSelf(deleteItem.id)
setDeleteItem(null)
setDeleteConfirm('')
await load()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete SSH access profile')
setDeleteItem(null)
setDeleteConfirm('')
}
}
@@ -395,10 +400,12 @@ export default function SSHServersPage() {
try {
await api.deleteSSHServerForSelf(deleteServerItem.id)
setDeleteServerItem(null)
setDeleteServerConfirm('')
await load()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete SSH server')
setDeleteServerItem(null)
setDeleteServerConfirm('')
}
}
@@ -487,7 +494,7 @@ export default function SSHServersPage() {
<ListRowActionButton onClick={() => openEdit(item)}>Edit</ListRowActionButton>
) : null}
{item.owner_scope === 'user' ? (
<ListRowActionButton color="error" onClick={() => setDeleteItem(item)}>
<ListRowActionButton color="error" onClick={() => { setDeleteItem(item); setDeleteConfirm('') }}>
Delete
</ListRowActionButton>
) : null}
@@ -547,7 +554,7 @@ export default function SSHServersPage() {
<ListRowActions>
<ListRowActionButton onClick={() => setViewServerItem(item)}>View</ListRowActionButton>
<ListRowActionButton onClick={() => openServerEdit(item)}>Edit</ListRowActionButton>
<ListRowActionButton color="error" onClick={() => setDeleteServerItem(item)}>
<ListRowActionButton color="error" onClick={() => { setDeleteServerItem(item); setDeleteServerConfirm('') }}>
Delete
</ListRowActionButton>
<ListRowActionButton onClick={() => void openHostKeys(item)}>Host Keys</ListRowActionButton>
@@ -646,20 +653,18 @@ export default function SSHServersPage() {
onSave={handleSave}
/>
<Dialog open={Boolean(deleteItem)} onClose={() => setDeleteItem(null)} maxWidth="xs" fullWidth>
<DialogTitle>Delete SSH Access Profile</DialogTitle>
<FormDialogContent>
<Typography variant="body2">
Delete <strong>{deleteItem?.name}</strong>?
</Typography>
</FormDialogContent>
<DialogActions>
<Button variant="contained" color="error" onClick={handleDelete}>
Delete
</Button>
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
</DialogActions>
</Dialog>
<ConfirmDeleteDialog
open={Boolean(deleteItem)}
title="Delete SSH Access Profile"
prompt="Type the profile name to confirm deletion."
expectedText={deleteItem?.name}
confirmLabel="Profile Name"
confirmValue={deleteConfirm}
onConfirmValueChange={setDeleteConfirm}
confirmDisabled={!deleteItem}
onConfirm={handleDelete}
onClose={() => { setDeleteItem(null); setDeleteConfirm('') }}
/>
<SSHServerFormDialog
open={serverDialogOpen}
@@ -682,20 +687,18 @@ export default function SSHServersPage() {
onClose={() => setViewSharedServerItem(null)}
/>
<Dialog open={Boolean(deleteServerItem)} onClose={() => setDeleteServerItem(null)} maxWidth="xs" fullWidth>
<DialogTitle>Delete SSH Server</DialogTitle>
<FormDialogContent>
<Typography variant="body2">
Delete <strong>{deleteServerItem?.name}</strong>?
</Typography>
</FormDialogContent>
<DialogActions>
<Button variant="contained" color="error" onClick={handleServerDelete}>
Delete
</Button>
<Button onClick={() => setDeleteServerItem(null)}>Cancel</Button>
</DialogActions>
</Dialog>
<ConfirmDeleteDialog
open={Boolean(deleteServerItem)}
title="Delete SSH Server"
prompt="Type the server name to confirm deletion."
expectedText={deleteServerItem?.name}
confirmLabel="Server Name"
confirmValue={deleteServerConfirm}
onConfirmValueChange={setDeleteServerConfirm}
confirmDisabled={!deleteServerItem}
onConfirm={handleServerDelete}
onClose={() => { setDeleteServerItem(null); setDeleteServerConfirm('') }}
/>
<Dialog open={Boolean(connectPasswordItem)} onClose={closeConnectPasswordPrompt} maxWidth="xs" fullWidth>
<DialogTitle>Enter SSH Credentials</DialogTitle>