Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a42da315bc | |||
| d2bdb979a8 | |||
| 04d3d6f77f | |||
| 29cc5d5614 |
@@ -0,0 +1,68 @@
|
|||||||
|
import Alert from '@mui/material/Alert'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Dialog from '@mui/material/Dialog'
|
||||||
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import TextField from '@mui/material/TextField'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
|
import FormDialogContent from './FormDialogContent'
|
||||||
|
|
||||||
|
type ConfirmDeleteDialogProps = {
|
||||||
|
open: boolean
|
||||||
|
title: string
|
||||||
|
prompt: string
|
||||||
|
expectedText?: string
|
||||||
|
confirmLabel?: string
|
||||||
|
confirmValue?: string
|
||||||
|
onConfirmValueChange?: (value: string) => void
|
||||||
|
error?: string | null
|
||||||
|
busy?: boolean
|
||||||
|
confirmText?: string
|
||||||
|
busyText?: string
|
||||||
|
confirmDisabled?: boolean
|
||||||
|
onClose: () => void
|
||||||
|
onConfirm: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ConfirmDeleteDialog(props: ConfirmDeleteDialogProps) {
|
||||||
|
const requiresTypedConfirm: boolean = props.expectedText !== undefined
|
||||||
|
const inputMatches: boolean = !requiresTypedConfirm || props.confirmValue?.trim() === props.expectedText
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={props.open} onClose={props.onClose} maxWidth="xs" fullWidth>
|
||||||
|
<DialogTitle>{props.title}</DialogTitle>
|
||||||
|
<FormDialogContent>
|
||||||
|
{props.error ? <Alert severity="error">{props.error}</Alert> : null}
|
||||||
|
<Typography variant="body2" color="text.secondary">{props.prompt}</Typography>
|
||||||
|
{requiresTypedConfirm ? (
|
||||||
|
<>
|
||||||
|
{props.expectedText ? (
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{props.expectedText}
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
label={props.confirmLabel || 'Confirm'}
|
||||||
|
value={props.confirmValue || ''}
|
||||||
|
onChange={(event) => props.onConfirmValueChange?.(event.target.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</FormDialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
disabled={Boolean(props.busy) || Boolean(props.confirmDisabled) || !inputMatches}
|
||||||
|
onClick={props.onConfirm}
|
||||||
|
>
|
||||||
|
{props.busy ? (props.busyText || 'Deleting...') : (props.confirmText || 'Delete')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={props.onClose} disabled={Boolean(props.busy)}>Cancel</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -94,7 +94,7 @@ export default function PKICertDetailsDialog(props: PKICertDetailsDialogProps) {
|
|||||||
{item && props.onDownloadBundle ? <Button onClick={() => props.onDownloadBundle?.(item)}>Download Bundle</Button> : null}
|
{item && props.onDownloadBundle ? <Button onClick={() => props.onDownloadBundle?.(item)}>Download Bundle</Button> : null}
|
||||||
{item && props.onDownloadCert ? <Button onClick={() => props.onDownloadCert?.(item)}>Download Cert</Button> : null}
|
{item && props.onDownloadCert ? <Button onClick={() => props.onDownloadCert?.(item)}>Download Cert</Button> : null}
|
||||||
{item && props.onDownloadKey ? <Button onClick={() => props.onDownloadKey?.(item)}>Download Key</Button> : null}
|
{item && props.onDownloadKey ? <Button onClick={() => props.onDownloadKey?.(item)}>Download Key</Button> : null}
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function PKIClientProfileDetailsDialog(props: PKIClientProfileDet
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -120,10 +120,10 @@ export default function PKIClientProfileFormDialog(props: PKIClientProfileFormDi
|
|||||||
<FormControlLabel control={<Checkbox checked={props.form.enabled} onChange={(event) => props.setForm((prev) => ({ ...prev, enabled: event.target.checked }))} />} label="Enabled" />
|
<FormControlLabel control={<Checkbox checked={props.form.enabled} onChange={(event) => props.setForm((prev) => ({ ...prev, enabled: event.target.checked }))} />} label="Enabled" />
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose} disabled={props.busy}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={props.onSave} disabled={props.busy}>
|
<Button variant="contained" onClick={props.onSave} disabled={props.busy}>
|
||||||
{props.busy ? 'Saving...' : props.editID ? 'Save' : 'Create'}
|
{props.busy ? 'Saving...' : props.editID ? 'Save' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={props.onClose} disabled={props.busy}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default function SSHAccessProfileDetailsDialog(props: SSHAccessProfileDet
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -194,12 +194,12 @@ export default function SSHAccessProfileFormDialog(props: SSHAccessProfileFormDi
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose} disabled={props.saving}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" onClick={props.onSave} disabled={props.saving}>
|
<Button variant="contained" onClick={props.onSave} disabled={props.saving}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={props.onClose} disabled={props.saving}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function SSHPrincipalGrantDetailsDialog(props: SSHPrincipalGrantD
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export default function SSHPrincipalGrantFormDialog(props: SSHPrincipalGrantForm
|
|||||||
>
|
>
|
||||||
{props.busy ? 'Saving...' : 'Save'}
|
{props.busy ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={props.onClose} disabled={props.busy}>Close</Button>
|
<Button onClick={props.onClose} disabled={props.busy}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function SSHServerDetailsDialog(props: SSHServerDetailsDialogProp
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ export default function SSHServerFormDialog(props: SSHServerFormDialogProps) {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={props.onClose} disabled={props.saving}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" onClick={props.onSave} disabled={props.saving}>
|
<Button variant="contained" onClick={props.onSave} disabled={props.saving}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={props.onClose} disabled={props.saving}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function SSHUserCADetailsDialog(props: SSHUserCADetailsDialogProp
|
|||||||
<DialogActions>
|
<DialogActions>
|
||||||
{item && props.onDownloadPublic ? <Button startIcon={<DownloadIcon />} onClick={() => props.onDownloadPublic?.(item)}>Download Public</Button> : null}
|
{item && props.onDownloadPublic ? <Button startIcon={<DownloadIcon />} onClick={() => props.onDownloadPublic?.(item)}>Download Public</Button> : null}
|
||||||
{item && props.onDownloadPrivate ? <Button startIcon={<DownloadIcon />} color="warning" onClick={() => props.onDownloadPrivate?.(item)}>Download Private</Button> : null}
|
{item && props.onDownloadPrivate ? <Button startIcon={<DownloadIcon />} color="warning" onClick={() => props.onDownloadPrivate?.(item)}>Download Private</Button> : null}
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export default function SSHUserCAFormDialog(props: SSHUserCAFormDialogProps) {
|
|||||||
>
|
>
|
||||||
{props.busy ? 'Saving...' : 'Save'}
|
{props.busy ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={props.onClose}>Close</Button>
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import Alert from '@mui/material/Alert'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Checkbox from '@mui/material/Checkbox'
|
||||||
|
import Dialog from '@mui/material/Dialog'
|
||||||
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||||
|
import TextField from '@mui/material/TextField'
|
||||||
|
import FormDialogContent from './FormDialogContent'
|
||||||
|
import { User } from '../api'
|
||||||
|
|
||||||
|
export type UserFormState = {
|
||||||
|
username: string
|
||||||
|
displayName: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
passwordConfirm: string
|
||||||
|
isAdmin: boolean
|
||||||
|
canCreateProject: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserFormDialogProps = {
|
||||||
|
open: boolean
|
||||||
|
item: User | null
|
||||||
|
dialogError: string | null
|
||||||
|
saving: boolean
|
||||||
|
form: UserFormState
|
||||||
|
setForm: React.Dispatch<React.SetStateAction<UserFormState>>
|
||||||
|
onClose: () => void
|
||||||
|
onSave: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserFormDialog(props: UserFormDialogProps) {
|
||||||
|
const isEdit: boolean = Boolean(props.item)
|
||||||
|
const passwordMismatch: boolean = props.form.passwordConfirm !== '' && props.form.password !== props.form.passwordConfirm
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={props.open} onClose={props.onClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>{isEdit ? 'Edit User' : 'New User'}</DialogTitle>
|
||||||
|
<FormDialogContent>
|
||||||
|
{props.dialogError ? <Alert severity="error" sx={{ mb: 1 }}>{props.dialogError}</Alert> : null}
|
||||||
|
<Box sx={{ display: 'grid', gap: 1, mt: 1 }}>
|
||||||
|
<TextField
|
||||||
|
name="username"
|
||||||
|
label="Username"
|
||||||
|
value={isEdit ? (props.item?.username || '') : props.form.username}
|
||||||
|
onChange={(event) => props.setForm((prev) => ({ ...prev, username: event.target.value }))}
|
||||||
|
disabled={isEdit}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
name="display_name"
|
||||||
|
label="Display Name"
|
||||||
|
value={props.form.displayName}
|
||||||
|
onChange={(event) => props.setForm((prev) => ({ ...prev, displayName: event.target.value }))}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
value={props.form.email}
|
||||||
|
onChange={(event) => props.setForm((prev) => ({ ...prev, email: event.target.value }))}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
name="password"
|
||||||
|
label={isEdit ? 'New Password (optional)' : 'Password'}
|
||||||
|
type="password"
|
||||||
|
value={props.form.password}
|
||||||
|
onChange={(event) => props.setForm((prev) => ({ ...prev, password: event.target.value }))}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
name="password_confirm"
|
||||||
|
label={isEdit ? 'Confirm New Password' : 'Confirm Password'}
|
||||||
|
type="password"
|
||||||
|
value={props.form.passwordConfirm}
|
||||||
|
error={passwordMismatch}
|
||||||
|
helperText={passwordMismatch ? 'Passwords do not match.' : ''}
|
||||||
|
onChange={(event) => props.setForm((prev) => ({ ...prev, passwordConfirm: event.target.value }))}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={props.form.isAdmin} onChange={(event) => props.setForm((prev) => ({ ...prev, isAdmin: event.target.checked }))} />}
|
||||||
|
label="Admin"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={props.form.canCreateProject} onChange={(event) => props.setForm((prev) => ({ ...prev, canCreateProject: event.target.checked }))} />}
|
||||||
|
label="Allow project creation (direct grant)"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</FormDialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" onClick={props.onSave} disabled={props.saving}>
|
||||||
|
{props.saving ? (isEdit ? 'Saving...' : 'Creating...') : (isEdit ? 'Save' : 'Create')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={props.onClose}>Cancel</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -266,9 +266,6 @@ export default function AdminApiKeysPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => { setDeleteTarget(null); setDeleteConfirm('') }}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="error"
|
color="error"
|
||||||
@@ -277,6 +274,9 @@ export default function AdminApiKeysPage() {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => { setDeleteTarget(null); setDeleteConfirm('') }}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={bulkOpen} onClose={() => setBulkOpen(false)} maxWidth="xs" fullWidth>
|
<Dialog open={bulkOpen} onClose={() => setBulkOpen(false)} maxWidth="xs" fullWidth>
|
||||||
@@ -293,9 +293,6 @@ export default function AdminApiKeysPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => { setBulkOpen(false); setBulkConfirm('') }}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="error"
|
color="error"
|
||||||
@@ -304,6 +301,9 @@ export default function AdminApiKeysPage() {
|
|||||||
>
|
>
|
||||||
{bulkDeleting ? 'Deleting...' : 'Delete Selected'}
|
{bulkDeleting ? 'Deleting...' : 'Delete Selected'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => { setBulkOpen(false); setBulkConfirm('') }}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -375,7 +375,6 @@ export default function AdminPKIClientProfilesPage() {
|
|||||||
<TextField label="Confirm Name" value={deleteConfirm} onChange={(event) => setDeleteConfirm(event.target.value)} autoFocus />
|
<TextField label="Confirm Name" value={deleteConfirm} onChange={(event) => setDeleteConfirm(event.target.value)} autoFocus />
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => { setDeleteItem(null); setDeleteConfirm('') }} disabled={busy}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
color="error"
|
color="error"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -384,6 +383,7 @@ export default function AdminPKIClientProfilesPage() {
|
|||||||
>
|
>
|
||||||
{busy ? 'Deleting...' : 'Delete'}
|
{busy ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => { setDeleteItem(null); setDeleteConfirm('') }} disabled={busy}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -990,7 +990,6 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setACMEOpen(false)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={saveACME}
|
onClick={saveACME}
|
||||||
@@ -1004,6 +1003,7 @@ export default function AdminPKIPage() {
|
|||||||
>
|
>
|
||||||
{busy ? 'Saving...' : 'Save'}
|
{busy ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setACMEOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1018,8 +1018,8 @@ export default function AdminPKIPage() {
|
|||||||
<Typography variant="body2" color="text.secondary">Delete profile permanently?</Typography>
|
<Typography variant="body2" color="text.secondary">Delete profile permanently?</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setACMEDeleteID('')}>Cancel</Button>
|
|
||||||
<Button color="error" variant="contained" onClick={deleteACME} disabled={busy}>{busy ? 'Working...' : 'Delete'}</Button>
|
<Button color="error" variant="contained" onClick={deleteACME} disabled={busy}>{busy ? 'Working...' : 'Delete'}</Button>
|
||||||
|
<Button onClick={() => setACMEDeleteID('')}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1085,10 +1085,10 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setACMEOrderOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={createACMEOrder} disabled={busy || !acmeOrderProfileID || !acmeOrderCommonName.trim()}>
|
<Button variant="contained" onClick={createACMEOrder} disabled={busy || !acmeOrderProfileID || !acmeOrderCommonName.trim()}>
|
||||||
{busy ? 'Saving...' : 'Create'}
|
{busy ? 'Saving...' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setACMEOrderOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1103,8 +1103,8 @@ export default function AdminPKIPage() {
|
|||||||
<Typography variant="body2" color="text.secondary">Delete order permanently?</Typography>
|
<Typography variant="body2" color="text.secondary">Delete order permanently?</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setACMEOrderDeleteID('')}>Cancel</Button>
|
|
||||||
<Button color="error" variant="contained" onClick={deleteACMEOrder} disabled={busy}>{busy ? 'Working...' : 'Delete'}</Button>
|
<Button color="error" variant="contained" onClick={deleteACMEOrder} disabled={busy}>{busy ? 'Working...' : 'Delete'}</Button>
|
||||||
|
<Button onClick={() => setACMEOrderDeleteID('')}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1133,7 +1133,6 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setACMEFinalizeID('')}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => finalizeACMEOrder(acmeFinalizeID, acmeFinalizeMode)}
|
onClick={() => finalizeACMEOrder(acmeFinalizeID, acmeFinalizeMode)}
|
||||||
@@ -1141,6 +1140,7 @@ export default function AdminPKIPage() {
|
|||||||
>
|
>
|
||||||
{busy ? 'Working...' : 'Finalize'}
|
{busy ? 'Working...' : 'Finalize'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setACMEFinalizeID('')}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1219,8 +1219,8 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRootOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={createRoot} disabled={busy}>{busy ? 'Saving...' : 'Create'}</Button>
|
<Button variant="contained" onClick={createRoot} disabled={busy}>{busy ? 'Saving...' : 'Create'}</Button>
|
||||||
|
<Button onClick={() => setRootOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1277,8 +1277,8 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setInterOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={createIntermediate} disabled={busy}>{busy ? 'Saving...' : 'Create'}</Button>
|
<Button variant="contained" onClick={createIntermediate} disabled={busy}>{busy ? 'Saving...' : 'Create'}</Button>
|
||||||
|
<Button onClick={() => setInterOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1304,8 +1304,8 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setIssueOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={issueCert} disabled={busy}>{busy ? 'Saving...' : 'Issue'}</Button>
|
<Button variant="contained" onClick={issueCert} disabled={busy}>{busy ? 'Saving...' : 'Issue'}</Button>
|
||||||
|
<Button onClick={() => setIssueOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1355,8 +1355,8 @@ export default function AdminPKIPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setImportOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={importCert} disabled={busy}>{busy ? 'Saving...' : 'Import'}</Button>
|
<Button variant="contained" onClick={importCert} disabled={busy}>{busy ? 'Saving...' : 'Import'}</Button>
|
||||||
|
<Button onClick={() => setImportOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1371,8 +1371,8 @@ export default function AdminPKIPage() {
|
|||||||
<TextField fullWidth label="Reason (optional)" value={revokeReason} onChange={(event) => setRevokeReason(event.target.value)} />
|
<TextField fullWidth label="Reason (optional)" value={revokeReason} onChange={(event) => setRevokeReason(event.target.value)} />
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRevokeID('')}>Cancel</Button>
|
|
||||||
<Button color="warning" variant="contained" onClick={revokeCert} disabled={busy}>{busy ? 'Working...' : 'Revoke'}</Button>
|
<Button color="warning" variant="contained" onClick={revokeCert} disabled={busy}>{busy ? 'Working...' : 'Revoke'}</Button>
|
||||||
|
<Button onClick={() => setRevokeID('')}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1387,8 +1387,8 @@ export default function AdminPKIPage() {
|
|||||||
<Typography variant="body2" color="text.secondary">Delete certificate permanently?</Typography>
|
<Typography variant="body2" color="text.secondary">Delete certificate permanently?</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteID('')}>Cancel</Button>
|
|
||||||
<Button color="error" variant="contained" onClick={deleteCert} disabled={busy}>{busy ? 'Working...' : 'Delete'}</Button>
|
<Button color="error" variant="contained" onClick={deleteCert} disabled={busy}>{busy ? 'Working...' : 'Delete'}</Button>
|
||||||
|
<Button onClick={() => setDeleteID('')}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1410,7 +1410,6 @@ export default function AdminPKIPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteCAID('')}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
color="error"
|
color="error"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -1419,6 +1418,7 @@ export default function AdminPKIPage() {
|
|||||||
>
|
>
|
||||||
{busy ? 'Working...' : 'Delete CA'}
|
{busy ? 'Working...' : 'Delete CA'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteCAID('')}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1468,10 +1468,10 @@ export default function AdminPKIPage() {
|
|||||||
</SelectField>
|
</SelectField>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => { setEditCAID(''); setEditCAParent('') }}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={saveCAEdit} disabled={busy || !editCAName.trim()}>
|
<Button variant="contained" onClick={saveCAEdit} disabled={busy || !editCAName.trim()}>
|
||||||
{busy ? 'Saving...' : 'Save'}
|
{busy ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => { setEditCAID(''); setEditCAParent('') }}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import TextField from '@mui/material/TextField'
|
|||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
|
||||||
import FormDialogContent from '../components/FormDialogContent'
|
import FormDialogContent from '../components/FormDialogContent'
|
||||||
import SSHAccessProfileDetailsDialog from '../components/SSHAccessProfileDetailsDialog'
|
import SSHAccessProfileDetailsDialog from '../components/SSHAccessProfileDetailsDialog'
|
||||||
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
||||||
@@ -54,6 +55,7 @@ export default function AdminSSHAccessProfilesPage() {
|
|||||||
const [dialogError, setDialogError] = useState<string | null>(null)
|
const [dialogError, setDialogError] = useState<string | null>(null)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [deleteItem, setDeleteItem] = useState<SSHAccessProfile | null>(null)
|
const [deleteItem, setDeleteItem] = useState<SSHAccessProfile | null>(null)
|
||||||
|
const [deleteConfirm, setDeleteConfirm] = useState('')
|
||||||
const [viewItem, setViewItem] = useState<SSHAccessProfile | null>(null)
|
const [viewItem, setViewItem] = useState<SSHAccessProfile | null>(null)
|
||||||
const [viewServerItem, setViewServerItem] = useState<SSHServer | null>(null)
|
const [viewServerItem, setViewServerItem] = useState<SSHServer | null>(null)
|
||||||
const [viewCAItem, setViewCAItem] = useState<SSHUserCA | null>(null)
|
const [viewCAItem, setViewCAItem] = useState<SSHUserCA | null>(null)
|
||||||
@@ -279,10 +281,12 @@ export default function AdminSSHAccessProfilesPage() {
|
|||||||
try {
|
try {
|
||||||
await api.deleteSSHAccessProfileAdmin(deleteItem.id)
|
await api.deleteSSHAccessProfileAdmin(deleteItem.id)
|
||||||
setDeleteItem(null)
|
setDeleteItem(null)
|
||||||
|
setDeleteConfirm('')
|
||||||
await load()
|
await load()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to delete SSH access profile')
|
setError(err instanceof Error ? err.message : 'Failed to delete SSH access profile')
|
||||||
setDeleteItem(null)
|
setDeleteItem(null)
|
||||||
|
setDeleteConfirm('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +352,7 @@ export default function AdminSSHAccessProfilesPage() {
|
|||||||
<ListRowActions>
|
<ListRowActions>
|
||||||
<ListRowActionButton onClick={() => setViewItem(item)}>View</ListRowActionButton>
|
<ListRowActionButton onClick={() => setViewItem(item)}>View</ListRowActionButton>
|
||||||
<ListRowActionButton onClick={() => openEdit(item)}>Edit</ListRowActionButton>
|
<ListRowActionButton onClick={() => openEdit(item)}>Edit</ListRowActionButton>
|
||||||
<ListRowActionButton color="error" onClick={() => setDeleteItem(item)}>
|
<ListRowActionButton color="error" onClick={() => { setDeleteItem(item); setDeleteConfirm('') }}>
|
||||||
Delete
|
Delete
|
||||||
</ListRowActionButton>
|
</ListRowActionButton>
|
||||||
</ListRowActions>
|
</ListRowActions>
|
||||||
@@ -437,20 +441,18 @@ export default function AdminSSHAccessProfilesPage() {
|
|||||||
onDownloadPrivate={downloadCAPrivateKey}
|
onDownloadPrivate={downloadCAPrivateKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Dialog open={Boolean(deleteItem)} onClose={() => setDeleteItem(null)} maxWidth="xs" fullWidth>
|
<ConfirmDeleteDialog
|
||||||
<DialogTitle>Delete SSH Access Profile</DialogTitle>
|
open={Boolean(deleteItem)}
|
||||||
<FormDialogContent>
|
title="Delete SSH Access Profile"
|
||||||
<Typography variant="body2">
|
prompt="Type the profile name to confirm deletion."
|
||||||
Delete <strong>{deleteItem?.name}</strong>?
|
expectedText={deleteItem?.name}
|
||||||
</Typography>
|
confirmLabel="Profile Name"
|
||||||
</FormDialogContent>
|
confirmValue={deleteConfirm}
|
||||||
<DialogActions>
|
onConfirmValueChange={setDeleteConfirm}
|
||||||
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
confirmDisabled={!deleteItem}
|
||||||
<Button variant="contained" color="error" onClick={handleDelete}>
|
onConfirm={handleDelete}
|
||||||
Delete
|
onClose={() => { setDeleteItem(null); setDeleteConfirm('') }}
|
||||||
</Button>
|
/>
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
|
||||||
import HeaderActionButton from '../components/HeaderActionButton'
|
import HeaderActionButton from '../components/HeaderActionButton'
|
||||||
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
||||||
import SectionCard from '../components/SectionCard'
|
import SectionCard from '../components/SectionCard'
|
||||||
@@ -349,36 +350,20 @@ export default function AdminSSHPrincipalGrantsPage() {
|
|||||||
onSave={save}
|
onSave={save}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Dialog open={!!deleteItem} onClose={() => { if (!busy) setDeleteItem(null) }} maxWidth="xs" fullWidth>
|
<ConfirmDeleteDialog
|
||||||
<DialogTitle>Delete Principal Grant</DialogTitle>
|
open={Boolean(deleteItem)}
|
||||||
<FormDialogContent>
|
title="Delete Principal Grant"
|
||||||
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null}
|
prompt="Type the grant name to confirm deletion."
|
||||||
<Typography variant="body2">
|
expectedText={deleteItem ? (deleteItem.name || deleteItem.principal) : undefined}
|
||||||
Type the grant name to confirm deletion.
|
confirmLabel="Grant Name"
|
||||||
</Typography>
|
confirmValue={deleteConfirm}
|
||||||
<Typography variant="caption" color="text.secondary">
|
onConfirmValueChange={setDeleteConfirm}
|
||||||
{deleteItem ? `${deleteItem.name || deleteItem.principal}` : ''}
|
error={dialogError}
|
||||||
</Typography>
|
busy={busy}
|
||||||
<TextField
|
confirmDisabled={!deleteItem}
|
||||||
autoFocus
|
onConfirm={remove}
|
||||||
size="small"
|
onClose={() => { if (!busy) setDeleteItem(null) }}
|
||||||
label="Grant Name"
|
/>
|
||||||
value={deleteConfirm}
|
|
||||||
onChange={(event) => setDeleteConfirm(event.target.value)}
|
|
||||||
/>
|
|
||||||
</FormDialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button
|
|
||||||
color="error"
|
|
||||||
variant="contained"
|
|
||||||
onClick={remove}
|
|
||||||
disabled={busy || !deleteItem || deleteConfirm.trim() !== (deleteItem.name || deleteItem.principal)}
|
|
||||||
>
|
|
||||||
{busy ? 'Deleting...' : 'Delete'}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setDeleteItem(null)} disabled={busy}>Close</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,10 +350,10 @@ export default function AdminSSHServersPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
|
||||||
<Button variant="contained" color="error" onClick={handleDelete}>
|
<Button variant="contained" color="error" onClick={handleDelete}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -443,7 +443,7 @@ export default function AdminSSHServersPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeHostKeys}>Close</Button>
|
<Button onClick={closeHostKeys}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
|
||||||
import HeaderActionButton from '../components/HeaderActionButton'
|
import HeaderActionButton from '../components/HeaderActionButton'
|
||||||
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
||||||
import SectionCard from '../components/SectionCard'
|
import SectionCard from '../components/SectionCard'
|
||||||
@@ -362,31 +363,21 @@ export default function AdminSSHUserCAPage() {
|
|||||||
onLoadPrivateKeyFile={loadPrivateKeyFile}
|
onLoadPrivateKeyFile={loadPrivateKeyFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Dialog open={Boolean(deleteItem)} onClose={() => setDeleteItem(null)} maxWidth="xs" fullWidth>
|
<ConfirmDeleteDialog
|
||||||
<DialogTitle>
|
open={Boolean(deleteItem)}
|
||||||
<Box sx={{ display: 'grid', gap: 1 }}>
|
title="Delete SSH User CA"
|
||||||
<Typography variant="h6">Delete SSH User CA</Typography>
|
prompt="Type CA name to confirm deletion."
|
||||||
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null}
|
expectedText={deleteItem?.name}
|
||||||
</Box>
|
confirmLabel="Confirm Name"
|
||||||
</DialogTitle>
|
confirmValue={deleteConfirm}
|
||||||
<FormDialogContent>
|
onConfirmValueChange={setDeleteConfirm}
|
||||||
<Box sx={{ display: 'grid', gap: 1 }}>
|
error={dialogError}
|
||||||
<Typography variant="body2" color="text.secondary">Type CA name to confirm deletion.</Typography>
|
busy={busy}
|
||||||
<TextField size="small" label="Confirm Name" value={deleteConfirm} onChange={(event) => setDeleteConfirm(event.target.value)} />
|
busyText="Working..."
|
||||||
</Box>
|
confirmDisabled={!deleteItem}
|
||||||
</FormDialogContent>
|
onConfirm={remove}
|
||||||
<DialogActions>
|
onClose={() => setDeleteItem(null)}
|
||||||
<Button
|
/>
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
onClick={remove}
|
|
||||||
disabled={busy || !deleteItem || deleteConfirm.trim() !== deleteItem.name}
|
|
||||||
>
|
|
||||||
{busy ? 'Working...' : 'Delete'}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<SSHUserCADetailsDialog
|
<SSHUserCADetailsDialog
|
||||||
item={viewItem}
|
item={viewItem}
|
||||||
@@ -447,7 +438,7 @@ export default function AdminSSHUserCAPage() {
|
|||||||
>
|
>
|
||||||
{busy ? 'Working...' : 'Sign'}
|
{busy ? 'Working...' : 'Sign'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setSignOpen(false)}>Close</Button>
|
<Button onClick={() => setSignOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -464,7 +455,7 @@ export default function AdminSSHUserCAPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setInspectOpen(false)}>Close</Button>
|
<Button onClick={() => setInspectOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -733,8 +733,8 @@ export default function AdminServicePrincipalsPage() {
|
|||||||
<TextField label="Description" value={description} onChange={(event) => setDescription(event.target.value)} />
|
<TextField label="Description" value={description} onChange={(event) => setDescription(event.target.value)} />
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={createPrincipal} disabled={busy}>{busy ? 'Saving...' : 'Create'}</Button>
|
<Button variant="contained" onClick={createPrincipal} disabled={busy}>{busy ? 'Saving...' : 'Create'}</Button>
|
||||||
|
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -810,8 +810,8 @@ export default function AdminServicePrincipalsPage() {
|
|||||||
</SelectField>
|
</SelectField>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setBindingOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={upsertBinding} disabled={busy}>{busy ? 'Saving...' : 'Save'}</Button>
|
<Button variant="contained" onClick={upsertBinding} disabled={busy}>{busy ? 'Saving...' : 'Save'}</Button>
|
||||||
|
<Button onClick={() => setBindingOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -354,10 +354,10 @@ export default function AdminTLSAuthPoliciesPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDialogOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={handleSave} disabled={saving}>
|
<Button variant="contained" onClick={handleSave} disabled={saving}>
|
||||||
{saving ? 'Saving...' : editingID ? 'Save' : 'Create'}
|
{saving ? 'Saving...' : editingID ? 'Save' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDialogOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -369,10 +369,10 @@ export default function AdminTLSAuthPoliciesPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setConfirmOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" color="error" onClick={handleDelete}>
|
<Button variant="contained" color="error" onClick={handleDelete}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setConfirmOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ export default function AdminTLSSettingsPage() {
|
|||||||
const [confirmMessage, setConfirmMessage] = useState('')
|
const [confirmMessage, setConfirmMessage] = useState('')
|
||||||
const [confirmLabel, setConfirmLabel] = useState('Confirm')
|
const [confirmLabel, setConfirmLabel] = useState('Confirm')
|
||||||
const [confirmColor, setConfirmColor] = useState<'primary' | 'warning' | 'error'>('primary')
|
const [confirmColor, setConfirmColor] = useState<'primary' | 'warning' | 'error'>('primary')
|
||||||
|
const [confirmRequiresText, setConfirmRequiresText] = useState(false)
|
||||||
|
const [confirmExpectedText, setConfirmExpectedText] = useState('')
|
||||||
|
const [confirmInput, setConfirmInput] = useState('')
|
||||||
const [confirmAction, setConfirmAction] = useState<null | (() => Promise<void>)>(null)
|
const [confirmAction, setConfirmAction] = useState<null | (() => Promise<void>)>(null)
|
||||||
const policyNameByID = useMemo(() => {
|
const policyNameByID = useMemo(() => {
|
||||||
const map: Record<string, string> = {}
|
const map: Record<string, string> = {}
|
||||||
@@ -324,6 +327,9 @@ export default function AdminTLSSettingsPage() {
|
|||||||
setConfirmMessage(`Do you want to ${nextEnabled ? 'enable' : 'disable'} listener "${item.name}"?`)
|
setConfirmMessage(`Do you want to ${nextEnabled ? 'enable' : 'disable'} listener "${item.name}"?`)
|
||||||
setConfirmLabel(nextEnabled ? 'Enable' : 'Disable')
|
setConfirmLabel(nextEnabled ? 'Enable' : 'Disable')
|
||||||
setConfirmColor(nextEnabled ? 'primary' : 'warning')
|
setConfirmColor(nextEnabled ? 'primary' : 'warning')
|
||||||
|
setConfirmRequiresText(false)
|
||||||
|
setConfirmExpectedText('')
|
||||||
|
setConfirmInput('')
|
||||||
setConfirmAction(() => async () => {
|
setConfirmAction(() => async () => {
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
@@ -340,9 +346,12 @@ export default function AdminTLSSettingsPage() {
|
|||||||
|
|
||||||
const handleDeleteListener = async (item: TLSListener) => {
|
const handleDeleteListener = async (item: TLSListener) => {
|
||||||
setConfirmTitle('Delete Listener')
|
setConfirmTitle('Delete Listener')
|
||||||
setConfirmMessage(`Delete listener "${item.name}"?`)
|
setConfirmMessage('Type the listener name to confirm deletion.')
|
||||||
setConfirmLabel('Delete')
|
setConfirmLabel('Delete')
|
||||||
setConfirmColor('error')
|
setConfirmColor('error')
|
||||||
|
setConfirmRequiresText(true)
|
||||||
|
setConfirmExpectedText(item.name)
|
||||||
|
setConfirmInput('')
|
||||||
setConfirmAction(() => async () => {
|
setConfirmAction(() => async () => {
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
@@ -364,6 +373,9 @@ export default function AdminTLSSettingsPage() {
|
|||||||
}
|
}
|
||||||
await confirmAction()
|
await confirmAction()
|
||||||
setConfirmOpen(false)
|
setConfirmOpen(false)
|
||||||
|
setConfirmRequiresText(false)
|
||||||
|
setConfirmExpectedText('')
|
||||||
|
setConfirmInput('')
|
||||||
setConfirmAction(null)
|
setConfirmAction(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +388,7 @@ export default function AdminTLSSettingsPage() {
|
|||||||
title="Listeners"
|
title="Listeners"
|
||||||
actions={
|
actions={
|
||||||
<Button variant="outlined" onClick={openCreateDialog}>
|
<Button variant="outlined" onClick={openCreateDialog}>
|
||||||
Add Listener
|
New Listener
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -493,7 +505,7 @@ export default function AdminTLSSettingsPage() {
|
|||||||
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth>
|
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<Box sx={{ display: 'grid', gap: 1 }}>
|
<Box sx={{ display: 'grid', gap: 1 }}>
|
||||||
<Typography variant="h6">{editingMain ? 'Edit Main Listener' : editingID ? 'Edit Listener' : 'Add Listener'}</Typography>
|
<Typography variant="h6">{editingMain ? 'Edit Main Listener' : editingID ? 'Edit Listener' : 'New Listener'}</Typography>
|
||||||
{listenerError ? <Alert severity="error">{listenerError}</Alert> : null}
|
{listenerError ? <Alert severity="error">{listenerError}</Alert> : null}
|
||||||
{editingMain ? (
|
{editingMain ? (
|
||||||
<Alert severity="info">Main listener transport changes are saved now but require backend restart to take effect.</Alert>
|
<Alert severity="info">Main listener transport changes are saved now but require backend restart to take effect.</Alert>
|
||||||
@@ -603,23 +615,61 @@ export default function AdminTLSSettingsPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDialogOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={handleSaveDialog} disabled={listenerSaving}>
|
<Button variant="contained" onClick={handleSaveDialog} disabled={listenerSaving}>
|
||||||
{listenerSaving ? 'Saving...' : editingMain || editingID ? 'Save' : 'Create'}
|
{listenerSaving ? 'Saving...' : editingMain || editingID ? 'Save' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDialogOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)} maxWidth="xs" fullWidth>
|
<Dialog
|
||||||
|
open={confirmOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setConfirmOpen(false)
|
||||||
|
setConfirmRequiresText(false)
|
||||||
|
setConfirmExpectedText('')
|
||||||
|
setConfirmInput('')
|
||||||
|
}}
|
||||||
|
maxWidth="xs"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
<DialogTitle>{confirmTitle}</DialogTitle>
|
<DialogTitle>{confirmTitle}</DialogTitle>
|
||||||
<FormDialogContent>
|
<FormDialogContent>
|
||||||
<Typography variant="body2">{confirmMessage}</Typography>
|
<Typography variant="body2">{confirmMessage}</Typography>
|
||||||
|
{confirmRequiresText ? (
|
||||||
|
<>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{confirmExpectedText}
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
size="small"
|
||||||
|
label="Listener Name"
|
||||||
|
value={confirmInput}
|
||||||
|
onChange={(event) => setConfirmInput(event.target.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setConfirmOpen(false)}>Cancel</Button>
|
<Button
|
||||||
<Button variant="contained" color={confirmColor} onClick={handleConfirm}>
|
variant="contained"
|
||||||
|
color={confirmColor}
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={confirmRequiresText && confirmInput.trim() !== confirmExpectedText}
|
||||||
|
>
|
||||||
{confirmLabel}
|
{confirmLabel}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setConfirmOpen(false)
|
||||||
|
setConfirmRequiresText(false)
|
||||||
|
setConfirmExpectedText('')
|
||||||
|
setConfirmInput('')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -477,10 +477,10 @@ export default function AdminUserGroupsPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={saveGroup} disabled={busy || !name.trim()}>
|
<Button variant="contained" onClick={saveGroup} disabled={busy || !name.trim()}>
|
||||||
{busy ? 'Saving...' : 'Save'}
|
{busy ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -498,7 +498,6 @@ export default function AdminUserGroupsPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
color="error"
|
color="error"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -507,6 +506,7 @@ export default function AdminUserGroupsPage() {
|
|||||||
>
|
>
|
||||||
{busy ? 'Deleting...' : 'Delete'}
|
{busy ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import FormDialogContent from '../components/FormDialogContent'
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormControlLabel,
|
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
@@ -17,38 +15,41 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
|
||||||
import HeaderActionButton from '../components/HeaderActionButton'
|
import HeaderActionButton from '../components/HeaderActionButton'
|
||||||
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
|
||||||
import SectionCard from '../components/SectionCard'
|
import SectionCard from '../components/SectionCard'
|
||||||
|
import UserFormDialog, { UserFormState } from '../components/UserFormDialog'
|
||||||
import { api, SubjectPermission, User, UserCreatePayload, UserUpdatePayload } from '../api'
|
import { api, SubjectPermission, User, UserCreatePayload, UserUpdatePayload } from '../api'
|
||||||
|
|
||||||
const projectCreatePermission = 'project.create'
|
const projectCreatePermission = 'project.create'
|
||||||
|
|
||||||
|
function createEmptyForm(): UserFormState {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
displayName: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
passwordConfirm: '',
|
||||||
|
isAdmin: false,
|
||||||
|
canCreateProject: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function AdminUsersPage() {
|
export default function AdminUsersPage() {
|
||||||
const [users, setUsers] = useState<User[]>([])
|
const [users, setUsers] = useState<User[]>([])
|
||||||
const [subjectPermissions, setSubjectPermissions] = useState<SubjectPermission[]>([])
|
const [subjectPermissions, setSubjectPermissions] = useState<SubjectPermission[]>([])
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
const [createOpen, setCreateOpen] = useState(false)
|
||||||
const [createError, setCreateError] = useState<string | null>(null)
|
const [createError, setCreateError] = useState<string | null>(null)
|
||||||
const [creating, setCreating] = useState(false)
|
const [creating, setCreating] = useState(false)
|
||||||
const [username, setUsername] = useState('')
|
const [createForm, setCreateForm] = useState<UserFormState>(createEmptyForm)
|
||||||
const [displayName, setDisplayName] = useState('')
|
|
||||||
const [email, setEmail] = useState('')
|
|
||||||
const [password, setPassword] = useState('')
|
|
||||||
const [passwordConfirm, setPasswordConfirm] = useState('')
|
|
||||||
const [isAdmin, setIsAdmin] = useState(false)
|
|
||||||
const [createCanCreateProject, setCreateCanCreateProject] = useState(false)
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [deletingUser, setDeletingUser] = useState<User | null>(null)
|
const [deletingUser, setDeletingUser] = useState<User | null>(null)
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState('')
|
const [deleteConfirm, setDeleteConfirm] = useState('')
|
||||||
const [deleting, setDeleting] = useState(false)
|
const [deleting, setDeleting] = useState(false)
|
||||||
const [togglingID, setTogglingID] = useState('')
|
const [togglingID, setTogglingID] = useState('')
|
||||||
const [editUser, setEditUser] = useState<User | null>(null)
|
const [editUser, setEditUser] = useState<User | null>(null)
|
||||||
const [editDisplayName, setEditDisplayName] = useState('')
|
const [editForm, setEditForm] = useState<UserFormState>(createEmptyForm)
|
||||||
const [editEmail, setEditEmail] = useState('')
|
|
||||||
const [editPassword, setEditPassword] = useState('')
|
|
||||||
const [editPasswordConfirm, setEditPasswordConfirm] = useState('')
|
|
||||||
const [editIsAdmin, setEditIsAdmin] = useState(false)
|
|
||||||
const [editCanCreateProject, setEditCanCreateProject] = useState(false)
|
|
||||||
const [editError, setEditError] = useState<string | null>(null)
|
const [editError, setEditError] = useState<string | null>(null)
|
||||||
const [savingEdit, setSavingEdit] = useState(false)
|
const [savingEdit, setSavingEdit] = useState(false)
|
||||||
|
|
||||||
@@ -73,13 +74,7 @@ export default function AdminUsersPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resetCreateForm = () => {
|
const resetCreateForm = () => {
|
||||||
setUsername('')
|
setCreateForm(createEmptyForm())
|
||||||
setDisplayName('')
|
|
||||||
setEmail('')
|
|
||||||
setPassword('')
|
|
||||||
setPasswordConfirm('')
|
|
||||||
setIsAdmin(false)
|
|
||||||
setCreateCanCreateProject(false)
|
|
||||||
setCreateError(null)
|
setCreateError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,28 +83,27 @@ export default function AdminUsersPage() {
|
|||||||
resetCreateForm()
|
resetCreateForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCreate = async (evt: React.FormEvent) => {
|
const handleCreate = async () => {
|
||||||
let created: User
|
let created: User
|
||||||
let message: string
|
let message: string
|
||||||
let payload: UserCreatePayload
|
let payload: UserCreatePayload
|
||||||
evt.preventDefault()
|
|
||||||
setCreateError(null)
|
setCreateError(null)
|
||||||
setError(null)
|
setError(null)
|
||||||
payload = {
|
payload = {
|
||||||
username: username.trim(),
|
username: createForm.username.trim(),
|
||||||
display_name: displayName.trim(),
|
display_name: createForm.displayName.trim(),
|
||||||
email: email.trim(),
|
email: createForm.email.trim(),
|
||||||
password: password,
|
password: createForm.password,
|
||||||
is_admin: isAdmin
|
is_admin: createForm.isAdmin
|
||||||
}
|
}
|
||||||
if (password !== passwordConfirm) {
|
if (createForm.password !== createForm.passwordConfirm) {
|
||||||
setCreateError('Password and confirmation must match.')
|
setCreateError('Password and confirmation must match.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setCreating(true)
|
setCreating(true)
|
||||||
try {
|
try {
|
||||||
created = await api.createUser(payload)
|
created = await api.createUser(payload)
|
||||||
if (createCanCreateProject) {
|
if (createForm.canCreateProject) {
|
||||||
try {
|
try {
|
||||||
await api.createSubjectPermissions({
|
await api.createSubjectPermissions({
|
||||||
permission: projectCreatePermission,
|
permission: projectCreatePermission,
|
||||||
@@ -172,23 +166,21 @@ export default function AdminUsersPage() {
|
|||||||
|
|
||||||
const openEditUser = (user: User) => {
|
const openEditUser = (user: User) => {
|
||||||
setEditUser(user)
|
setEditUser(user)
|
||||||
setEditDisplayName(user.display_name || '')
|
setEditForm({
|
||||||
setEditEmail(user.email || '')
|
username: user.username,
|
||||||
setEditPassword('')
|
displayName: user.display_name || '',
|
||||||
setEditPasswordConfirm('')
|
email: user.email || '',
|
||||||
setEditIsAdmin(Boolean(user.is_admin))
|
password: '',
|
||||||
setEditCanCreateProject(hasDirectProjectCreate(user.id))
|
passwordConfirm: '',
|
||||||
|
isAdmin: Boolean(user.is_admin),
|
||||||
|
canCreateProject: hasDirectProjectCreate(user.id)
|
||||||
|
})
|
||||||
setEditError(null)
|
setEditError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeEditUser = () => {
|
const closeEditUser = () => {
|
||||||
setEditUser(null)
|
setEditUser(null)
|
||||||
setEditDisplayName('')
|
setEditForm(createEmptyForm())
|
||||||
setEditEmail('')
|
|
||||||
setEditPassword('')
|
|
||||||
setEditPasswordConfirm('')
|
|
||||||
setEditIsAdmin(false)
|
|
||||||
setEditCanCreateProject(false)
|
|
||||||
setEditError(null)
|
setEditError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +192,7 @@ export default function AdminUsersPage() {
|
|||||||
}
|
}
|
||||||
setSavingEdit(true)
|
setSavingEdit(true)
|
||||||
setEditError(null)
|
setEditError(null)
|
||||||
if (editPassword !== editPasswordConfirm) {
|
if (editForm.password !== editForm.passwordConfirm) {
|
||||||
setEditError('Password and confirmation must match.')
|
setEditError('Password and confirmation must match.')
|
||||||
setSavingEdit(false)
|
setSavingEdit(false)
|
||||||
return
|
return
|
||||||
@@ -208,19 +200,19 @@ export default function AdminUsersPage() {
|
|||||||
hadProjectCreate = hasDirectProjectCreate(editUser.id)
|
hadProjectCreate = hasDirectProjectCreate(editUser.id)
|
||||||
try {
|
try {
|
||||||
payload = {
|
payload = {
|
||||||
display_name: editDisplayName.trim(),
|
display_name: editForm.displayName.trim(),
|
||||||
email: editEmail.trim(),
|
email: editForm.email.trim(),
|
||||||
password: editPassword,
|
password: editForm.password,
|
||||||
is_admin: editIsAdmin
|
is_admin: editForm.isAdmin
|
||||||
}
|
}
|
||||||
await api.updateUser(editUser.id, payload)
|
await api.updateUser(editUser.id, payload)
|
||||||
if (editCanCreateProject && !hadProjectCreate) {
|
if (editForm.canCreateProject && !hadProjectCreate) {
|
||||||
await api.createSubjectPermissions({
|
await api.createSubjectPermissions({
|
||||||
permission: projectCreatePermission,
|
permission: projectCreatePermission,
|
||||||
targets: [{ subject_type: 'user', subject_id: editUser.id }]
|
targets: [{ subject_type: 'user', subject_id: editUser.id }]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!editCanCreateProject && hadProjectCreate) {
|
if (!editForm.canCreateProject && hadProjectCreate) {
|
||||||
await api.deleteSubjectPermission(projectCreatePermission, 'user', editUser.id)
|
await api.deleteSubjectPermission(projectCreatePermission, 'user', editUser.id)
|
||||||
}
|
}
|
||||||
await reloadUsers()
|
await reloadUsers()
|
||||||
@@ -280,120 +272,39 @@ export default function AdminUsersPage() {
|
|||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
<Dialog open={createOpen} onClose={closeCreateDialog} maxWidth="sm" fullWidth>
|
<UserFormDialog
|
||||||
<DialogTitle>New User</DialogTitle>
|
open={createOpen}
|
||||||
<FormDialogContent>
|
item={null}
|
||||||
{createError ? <Alert severity="error" sx={{ mb: 1 }}>{createError}</Alert> : null}
|
dialogError={createError}
|
||||||
<Box component="form" onSubmit={handleCreate} sx={{ display: 'grid', gap: 1, mt: 1 }}>
|
saving={creating}
|
||||||
<TextField name="username" label="Username" value={username} onChange={(event) => setUsername(event.target.value)} />
|
form={createForm}
|
||||||
<TextField
|
setForm={setCreateForm}
|
||||||
name="display_name"
|
onClose={closeCreateDialog}
|
||||||
label="Display Name"
|
onSave={handleCreate}
|
||||||
value={displayName}
|
/>
|
||||||
onChange={(event) => setDisplayName(event.target.value)}
|
<UserFormDialog
|
||||||
/>
|
open={Boolean(editUser)}
|
||||||
<TextField name="email" label="Email" value={email} onChange={(event) => setEmail(event.target.value)} />
|
item={editUser}
|
||||||
<TextField
|
dialogError={editError}
|
||||||
name="password"
|
saving={savingEdit}
|
||||||
label="Password"
|
form={editForm}
|
||||||
type="password"
|
setForm={setEditForm}
|
||||||
value={password}
|
onClose={closeEditUser}
|
||||||
onChange={(event) => setPassword(event.target.value)}
|
onSave={handleSaveEdit}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<ConfirmDeleteDialog
|
||||||
name="password_confirm"
|
open={Boolean(deletingUser)}
|
||||||
label="Confirm Password"
|
title="Delete User"
|
||||||
type="password"
|
prompt="Type the username to confirm deletion."
|
||||||
value={passwordConfirm}
|
expectedText={deletingUser?.username}
|
||||||
error={passwordConfirm !== '' && password !== passwordConfirm}
|
confirmLabel="Username"
|
||||||
helperText={passwordConfirm !== '' && password !== passwordConfirm ? 'Passwords do not match.' : ''}
|
confirmValue={deleteConfirm}
|
||||||
onChange={(event) => setPasswordConfirm(event.target.value)}
|
onConfirmValueChange={setDeleteConfirm}
|
||||||
/>
|
busy={deleting}
|
||||||
<FormControlLabel
|
confirmDisabled={!deletingUser}
|
||||||
control={<Checkbox checked={isAdmin} onChange={(event) => setIsAdmin(event.target.checked)} />}
|
onConfirm={handleDeleteUser}
|
||||||
label="Admin"
|
onClose={() => { setDeletingUser(null); setDeleteConfirm('') }}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={createCanCreateProject} onChange={(event) => setCreateCanCreateProject(event.target.checked)} />}
|
|
||||||
label="Allow project creation (direct grant)"
|
|
||||||
/>
|
|
||||||
<DialogActions sx={{ px: 0 }}>
|
|
||||||
<Button onClick={closeCreateDialog}>Cancel</Button>
|
|
||||||
<Button type="submit" variant="contained" disabled={creating}>
|
|
||||||
{creating ? 'Creating...' : 'Create'}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Box>
|
|
||||||
</FormDialogContent>
|
|
||||||
</Dialog>
|
|
||||||
<Dialog open={Boolean(editUser)} onClose={closeEditUser} maxWidth="sm" fullWidth>
|
|
||||||
<DialogTitle>Edit User</DialogTitle>
|
|
||||||
<FormDialogContent>
|
|
||||||
{editError ? <Alert severity="error" sx={{ mb: 1 }}>{editError}</Alert> : null}
|
|
||||||
<Box sx={{ display: 'grid', gap: 1, mt: 1 }}>
|
|
||||||
<TextField label="Username" value={editUser?.username || ''} disabled />
|
|
||||||
<TextField
|
|
||||||
label="Display Name"
|
|
||||||
value={editDisplayName}
|
|
||||||
onChange={(event) => setEditDisplayName(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextField label="Email" value={editEmail} onChange={(event) => setEditEmail(event.target.value)} />
|
|
||||||
<TextField
|
|
||||||
label="New Password (optional)"
|
|
||||||
type="password"
|
|
||||||
value={editPassword}
|
|
||||||
onChange={(event) => setEditPassword(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
label="Confirm New Password"
|
|
||||||
type="password"
|
|
||||||
value={editPasswordConfirm}
|
|
||||||
error={editPasswordConfirm !== '' && editPassword !== editPasswordConfirm}
|
|
||||||
helperText={editPasswordConfirm !== '' && editPassword !== editPasswordConfirm ? 'Passwords do not match.' : ''}
|
|
||||||
onChange={(event) => setEditPasswordConfirm(event.target.value)}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={editIsAdmin} onChange={(event) => setEditIsAdmin(event.target.checked)} />}
|
|
||||||
label="Admin"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={editCanCreateProject} onChange={(event) => setEditCanCreateProject(event.target.checked)} />}
|
|
||||||
label="Allow project creation (direct grant)"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</FormDialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={closeEditUser}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={handleSaveEdit} disabled={savingEdit}>
|
|
||||||
{savingEdit ? 'Saving...' : 'Save'}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
<Dialog open={Boolean(deletingUser)} onClose={() => setDeletingUser(null)} maxWidth="xs" fullWidth>
|
|
||||||
<DialogTitle>Delete User</DialogTitle>
|
|
||||||
<FormDialogContent>
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
|
||||||
Type the username to confirm deletion.
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label="Username"
|
|
||||||
value={deleteConfirm}
|
|
||||||
onChange={(event) => setDeleteConfirm(event.target.value)}
|
|
||||||
/>
|
|
||||||
</FormDialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => { setDeletingUser(null); setDeleteConfirm('') }}>Cancel</Button>
|
|
||||||
<Button
|
|
||||||
color="error"
|
|
||||||
variant="contained"
|
|
||||||
disabled={deleting || !deletingUser || deleteConfirm !== deletingUser.username}
|
|
||||||
onClick={handleDeleteUser}
|
|
||||||
>
|
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
|||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
import Alert from '@mui/material/Alert'
|
import Alert from '@mui/material/Alert'
|
||||||
import FormDialogContent from '../components/FormDialogContent'
|
import FormDialogContent from '../components/FormDialogContent'
|
||||||
|
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@@ -282,40 +283,28 @@ export default function ApiKeysPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeCreate}>{newToken ? 'Done' : 'Cancel'}</Button>
|
|
||||||
{!newToken ? (
|
{!newToken ? (
|
||||||
<Button onClick={handleCreate} variant="contained" disabled={creating}>
|
<Button onClick={handleCreate} variant="contained" disabled={creating}>
|
||||||
{creating ? 'Creating...' : 'Create'}
|
{creating ? 'Creating...' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
<Button onClick={closeCreate}>{newToken ? 'Done' : 'Cancel'}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={Boolean(deleteTarget)} onClose={() => setDeleteTarget(null)} maxWidth="xs" fullWidth>
|
<ConfirmDeleteDialog
|
||||||
<DialogTitle>Delete API Key</DialogTitle>
|
open={Boolean(deleteTarget)}
|
||||||
<FormDialogContent>
|
title="Delete API Key"
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
prompt="Type the API key name to confirm deletion."
|
||||||
Type the API key name to confirm deletion.
|
expectedText={deleteTarget?.name}
|
||||||
</Typography>
|
confirmLabel="Key name"
|
||||||
<TextField
|
confirmValue={deleteConfirm}
|
||||||
fullWidth
|
onConfirmValueChange={setDeleteConfirm}
|
||||||
label="Key name"
|
busy={deleting}
|
||||||
value={deleteConfirm}
|
confirmDisabled={!deleteTarget}
|
||||||
onChange={(event) => setDeleteConfirm(event.target.value)}
|
onConfirm={handleDelete}
|
||||||
/>
|
onClose={() => { setDeleteTarget(null); setDeleteConfirm('') }}
|
||||||
</FormDialogContent>
|
/>
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => { setDeleteTarget(null); setDeleteConfirm('') }}>Cancel</Button>
|
|
||||||
<Button
|
|
||||||
color="error"
|
|
||||||
variant="contained"
|
|
||||||
disabled={deleting || !deleteTarget || deleteConfirm !== deleteTarget.name}
|
|
||||||
onClick={handleDelete}
|
|
||||||
>
|
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import RepoSubNav from '../components/RepoSubNav'
|
|||||||
import EditIcon from '@mui/icons-material/Edit'
|
import EditIcon from '@mui/icons-material/Edit'
|
||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
|
||||||
|
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
|
||||||
import FormDialogContent from '../components/FormDialogContent'
|
import FormDialogContent from '../components/FormDialogContent'
|
||||||
|
|
||||||
export default function BranchesPage() {
|
export default function BranchesPage() {
|
||||||
@@ -334,10 +335,10 @@ export default function BranchesPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleCreate} variant="contained">
|
<Button onClick={handleCreate} variant="contained">
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={renameOpen} onClose={() => setRenameOpen(false)} maxWidth="sm" fullWidth>
|
<Dialog open={renameOpen} onClose={() => setRenameOpen(false)} maxWidth="sm" fullWidth>
|
||||||
@@ -358,38 +359,24 @@ export default function BranchesPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRenameOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleRename} variant="contained">
|
<Button onClick={handleRename} variant="contained">
|
||||||
Rename
|
Rename
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setRenameOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={deleteOpen} onClose={() => setDeleteOpen(false)} maxWidth="sm" fullWidth>
|
<ConfirmDeleteDialog
|
||||||
<DialogTitle>Delete Branch</DialogTitle>
|
open={deleteOpen}
|
||||||
<FormDialogContent>
|
title="Delete Branch"
|
||||||
{deleteError ? (
|
prompt="Type the branch name to confirm deletion."
|
||||||
<Typography variant="body2" color="error" sx={{ mb: 1 }}>
|
expectedText={deleteTarget}
|
||||||
{deleteError}
|
confirmLabel="Type branch name to confirm"
|
||||||
</Typography>
|
confirmValue={deleteConfirm}
|
||||||
) : null}
|
onConfirmValueChange={setDeleteConfirm}
|
||||||
<Typography variant="body2">
|
error={deleteError}
|
||||||
Delete branch <strong>{deleteTarget}</strong>?
|
onConfirm={handleDelete}
|
||||||
</Typography>
|
onClose={() => setDeleteOpen(false)}
|
||||||
<TextField
|
/>
|
||||||
label="Type branch name to confirm"
|
|
||||||
value={deleteConfirm}
|
|
||||||
onChange={(event) => setDeleteConfirm(event.target.value)}
|
|
||||||
fullWidth
|
|
||||||
margin="dense"
|
|
||||||
/>
|
|
||||||
</FormDialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => setDeleteOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleDelete} color="error" variant="contained" disabled={deleteConfirm.trim() !== deleteTarget}>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,10 +543,10 @@ export default function ClientCertificatesPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setIssueProfile(null)} disabled={busy}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={submitIssue} disabled={busy}>
|
<Button variant="contained" onClick={submitIssue} disabled={busy}>
|
||||||
{busy ? 'Issuing...' : 'Issue'}
|
{busy ? 'Issuing...' : 'Issue'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setIssueProfile(null)} disabled={busy}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -576,7 +576,7 @@ export default function ClientCertificatesPage() {
|
|||||||
<TextField label="CA Certificate" value={issueResult?.ca_cert_pem || ''} multiline minRows={6} InputProps={{ sx: { fontFamily: 'monospace' } }} />
|
<TextField label="CA Certificate" value={issueResult?.ca_cert_pem || ''} multiline minRows={6} InputProps={{ sx: { fontFamily: 'monospace' } }} />
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setIssueResult(null)}>Close</Button>
|
<Button onClick={() => setIssueResult(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -652,7 +652,7 @@ export default function ClientCertificatesPage() {
|
|||||||
<Button onClick={toggleViewDump} disabled={viewCertDumpLoading}>
|
<Button onClick={toggleViewDump} disabled={viewCertDumpLoading}>
|
||||||
{viewCertDumpLoading ? 'Loading Dump...' : viewCertDumpOpen ? 'Hide X509 Dump' : 'Show X509 Dump'}
|
{viewCertDumpLoading ? 'Loading Dump...' : viewCertDumpOpen ? 'Hide X509 Dump' : 'Show X509 Dump'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setViewCert(null)}>Close</Button>
|
<Button onClick={() => setViewCert(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -665,10 +665,10 @@ export default function ClientCertificatesPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRevokeItem(null)} disabled={busy}>Cancel</Button>
|
|
||||||
<Button variant="contained" color="error" onClick={revoke} disabled={busy}>
|
<Button variant="contained" color="error" onClick={revoke} disabled={busy}>
|
||||||
{busy ? 'Revoking...' : 'Revoke'}
|
{busy ? 'Revoking...' : 'Revoke'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setRevokeItem(null)} disabled={busy}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -642,10 +642,10 @@ export default function ProjectHomePage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleEditSave} variant="contained" disabled={savingEdit}>
|
<Button onClick={handleEditSave} variant="contained" disabled={savingEdit}>
|
||||||
{savingEdit ? 'Saving...' : 'Save'}
|
{savingEdit ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={deleteOpen} onClose={closeDelete} maxWidth="sm" fullWidth>
|
<Dialog open={deleteOpen} onClose={closeDelete} maxWidth="sm" fullWidth>
|
||||||
@@ -683,7 +683,6 @@ export default function ProjectHomePage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeDelete}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -700,6 +699,7 @@ export default function ProjectHomePage() {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={closeDelete}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -447,10 +447,10 @@ export default function ProjectsPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setEditProject(null)}>Cancel</Button>
|
|
||||||
<Button onClick={handleEditSave} variant="contained" disabled={savingEdit}>
|
<Button onClick={handleEditSave} variant="contained" disabled={savingEdit}>
|
||||||
{savingEdit ? 'Saving...' : 'Save'}
|
{savingEdit ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setEditProject(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={createOpen} onClose={closeCreate} maxWidth="sm" fullWidth>
|
<Dialog open={createOpen} onClose={closeCreate} maxWidth="sm" fullWidth>
|
||||||
@@ -512,10 +512,10 @@ export default function ProjectsPage() {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 1 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 1 }}>
|
||||||
<Button onClick={closeCreate}>Cancel</Button>
|
|
||||||
<Button type="submit" variant="contained" disabled={creating}>
|
<Button type="submit" variant="contained" disabled={creating}>
|
||||||
{creating ? 'Creating...' : 'Create'}
|
{creating ? 'Creating...' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={closeCreate}>Cancel</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
@@ -555,7 +555,6 @@ export default function ProjectsPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeDelete}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -572,6 +571,7 @@ export default function ProjectsPage() {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={closeDelete}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -498,7 +498,6 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteTagOpen(false)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDeleteTag}
|
onClick={handleDeleteTag}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -507,6 +506,7 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteTagOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={deleteImageOpen} onClose={() => setDeleteImageOpen(false)} maxWidth="xs" fullWidth>
|
<Dialog open={deleteImageOpen} onClose={() => setDeleteImageOpen(false)} maxWidth="xs" fullWidth>
|
||||||
@@ -525,7 +525,6 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteImageOpen(false)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDeleteImage}
|
onClick={handleDeleteImage}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -534,6 +533,7 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteImageOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={renameTagOpen} onClose={() => setRenameTagOpen(false)} maxWidth="xs" fullWidth>
|
<Dialog open={renameTagOpen} onClose={() => setRenameTagOpen(false)} maxWidth="xs" fullWidth>
|
||||||
@@ -550,10 +550,10 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRenameTagOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleRenameTag} variant="contained" disabled={renaming}>
|
<Button onClick={handleRenameTag} variant="contained" disabled={renaming}>
|
||||||
{renaming ? 'Renaming...' : 'Rename'}
|
{renaming ? 'Renaming...' : 'Rename'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setRenameTagOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={renameImageOpen} onClose={() => setRenameImageOpen(false)} maxWidth="xs" fullWidth>
|
<Dialog open={renameImageOpen} onClose={() => setRenameImageOpen(false)} maxWidth="xs" fullWidth>
|
||||||
@@ -571,10 +571,10 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRenameImageOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleRenameImage} variant="contained" disabled={renaming}>
|
<Button onClick={handleRenameImage} variant="contained" disabled={renaming}>
|
||||||
{renaming ? 'Renaming...' : 'Rename'}
|
{renaming ? 'Renaming...' : 'Rename'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setRenameImageOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1292,10 +1292,10 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setSubdirOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleCreateSubdir} variant="contained" disabled={subdirSaving}>
|
<Button onClick={handleCreateSubdir} variant="contained" disabled={subdirSaving}>
|
||||||
{subdirSaving ? 'Creating...' : 'Create'}
|
{subdirSaving ? 'Creating...' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setSubdirOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={uploadOpen} onClose={() => setUploadOpen(false)} maxWidth="sm" fullWidth>
|
<Dialog open={uploadOpen} onClose={() => setUploadOpen(false)} maxWidth="sm" fullWidth>
|
||||||
@@ -1319,10 +1319,10 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setUploadOpen(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleUpload} variant="contained" disabled={uploading}>
|
<Button onClick={handleUpload} variant="contained" disabled={uploading}>
|
||||||
{uploading ? 'Uploading...' : 'Upload'}
|
{uploading ? 'Uploading...' : 'Upload'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setUploadOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={deleteOpen} onClose={() => setDeleteOpen(false)} maxWidth="xs" fullWidth>
|
<Dialog open={deleteOpen} onClose={() => setDeleteOpen(false)} maxWidth="xs" fullWidth>
|
||||||
@@ -1343,7 +1343,6 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteOpen(false)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDeleteFolder}
|
onClick={handleDeleteFolder}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -1352,6 +1351,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -1553,6 +1553,9 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
<Button onClick={handleRenameSubdir} variant="contained" disabled={renaming || !renameNewName.trim()}>
|
||||||
|
{renaming ? (renameIsRepoDir ? 'Saving...' : 'Renaming...') : (renameIsRepoDir ? 'Save' : 'Rename')}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRenameOpen(false)
|
setRenameOpen(false)
|
||||||
@@ -1561,9 +1564,6 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleRenameSubdir} variant="contained" disabled={renaming || !renameNewName.trim()}>
|
|
||||||
{renaming ? (renameIsRepoDir ? 'Saving...' : 'Renaming...') : (renameIsRepoDir ? 'Save' : 'Rename')}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -1579,7 +1579,6 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setClearRunsConfirmOpen(false)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -1590,6 +1589,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
|||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setClearRunsConfirmOpen(false)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -553,10 +553,10 @@ export default function ReposPage() {
|
|||||||
<MenuItem value="docker">Docker</MenuItem>
|
<MenuItem value="docker">Docker</MenuItem>
|
||||||
</SelectField>
|
</SelectField>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 1 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 1 }}>
|
||||||
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
|
||||||
<Button type="submit" variant="contained" disabled={creating}>
|
<Button type="submit" variant="contained" disabled={creating}>
|
||||||
{creating ? 'Creating...' : 'Create'}
|
{creating ? 'Creating...' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
@@ -575,10 +575,10 @@ export default function ReposPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setEditRepo(null)}>Cancel</Button>
|
|
||||||
<Button onClick={handleEditSave} variant="contained" disabled={savingEdit}>
|
<Button onClick={handleEditSave} variant="contained" disabled={savingEdit}>
|
||||||
{savingEdit ? 'Saving...' : 'Save'}
|
{savingEdit ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setEditRepo(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={Boolean(deleteRepo)} onClose={closeDelete} maxWidth="sm" fullWidth>
|
<Dialog open={Boolean(deleteRepo)} onClose={closeDelete} maxWidth="sm" fullWidth>
|
||||||
@@ -620,7 +620,6 @@ export default function ReposPage() {
|
|||||||
)}
|
)}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeDelete}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -629,6 +628,7 @@ export default function ReposPage() {
|
|||||||
>
|
>
|
||||||
{deleting ? 'Deleting...' : 'Delete'}
|
{deleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={closeDelete}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={bulkDeleteOpen} onClose={closeBulkDelete} maxWidth="sm" fullWidth>
|
<Dialog open={bulkDeleteOpen} onClose={closeBulkDelete} maxWidth="sm" fullWidth>
|
||||||
@@ -659,7 +659,6 @@ export default function ReposPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeBulkDelete}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleBulkDelete}
|
onClick={handleBulkDelete}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -668,6 +667,7 @@ export default function ReposPage() {
|
|||||||
>
|
>
|
||||||
{bulkDeleting ? 'Deleting...' : 'Delete'}
|
{bulkDeleting ? 'Deleting...' : 'Delete'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={closeBulkDelete}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog open={foreignOpen} onClose={closeForeign} maxWidth="sm" fullWidth>
|
<Dialog open={foreignOpen} onClose={closeForeign} maxWidth="sm" fullWidth>
|
||||||
|
|||||||
@@ -654,10 +654,10 @@ export default function SSHServersPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
|
||||||
<Button variant="contained" color="error" onClick={handleDelete}>
|
<Button variant="contained" color="error" onClick={handleDelete}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteItem(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -690,10 +690,10 @@ export default function SSHServersPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteServerItem(null)}>Cancel</Button>
|
|
||||||
<Button variant="contained" color="error" onClick={handleServerDelete}>
|
<Button variant="contained" color="error" onClick={handleServerDelete}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setDeleteServerItem(null)}>Cancel</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1634,20 +1634,20 @@ export default function SSHSessionPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => historyConnectItem ? void connectHistorySession(historyConnectItem, historyConnectPassword, historyConnectOTPCode) : undefined}
|
||||||
|
disabled={
|
||||||
|
connectingHistorySession ||
|
||||||
|
(Boolean(historyConnectItem && requiresPromptedPassword(historyConnectItem)) && historyConnectPassword === '') ||
|
||||||
|
(Boolean(historyConnectItem && requiresPromptedTOTP(historyConnectItem)) && historyConnectOTPCode.trim() === '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{connectingHistorySession ? 'Connecting...' : 'Connect'}
|
||||||
|
</Button>
|
||||||
<Button onClick={closeHistoryConnectPrompt} disabled={connectingHistorySession}>
|
<Button onClick={closeHistoryConnectPrompt} disabled={connectingHistorySession}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => historyConnectItem ? void connectHistorySession(historyConnectItem, historyConnectPassword, historyConnectOTPCode) : undefined}
|
|
||||||
disabled={
|
|
||||||
connectingHistorySession ||
|
|
||||||
(Boolean(historyConnectItem && requiresPromptedPassword(historyConnectItem)) && historyConnectPassword === '') ||
|
|
||||||
(Boolean(historyConnectItem && requiresPromptedTOTP(historyConnectItem)) && historyConnectOTPCode.trim() === '')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{connectingHistorySession ? 'Connecting...' : 'Connect'}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1684,12 +1684,12 @@ export default function SSHSessionPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleCloseTranscript}>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" onClick={handleDownloadTranscript} disabled={!transcriptSession || loadingTranscript || transcriptText === ''}>
|
<Button variant="contained" onClick={handleDownloadTranscript} disabled={!transcriptSession || loadingTranscript || transcriptText === ''}>
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={handleCloseTranscript}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1705,12 +1705,12 @@ export default function SSHSessionPage() {
|
|||||||
/>
|
/>
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setNewSessionOpen(false)} disabled={creatingSession}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" onClick={() => void handleCreateSession()} disabled={creatingSession || !selectedProfile}>
|
<Button variant="contained" onClick={() => void handleCreateSession()} disabled={creatingSession || !selectedProfile}>
|
||||||
{creatingSession ? 'Connecting...' : 'Connect'}
|
{creatingSession ? 'Connecting...' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => setNewSessionOpen(false)} disabled={creatingSession}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -1740,9 +1740,6 @@ export default function SSHSessionPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={closeNewSessionPasswordPrompt} disabled={creatingSession}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => void handleCreateSession(newSessionPassword, newSessionOTPCode)}
|
onClick={() => void handleCreateSession(newSessionPassword, newSessionOTPCode)}
|
||||||
@@ -1754,6 +1751,9 @@ export default function SSHSessionPage() {
|
|||||||
>
|
>
|
||||||
{creatingSession ? 'Connecting...' : 'Connect'}
|
{creatingSession ? 'Connecting...' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={closeNewSessionPasswordPrompt} disabled={creatingSession}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user