Compare commits
2 Commits
16295f34bb
...
fc917daa0a
| Author | SHA1 | Date | |
|---|---|---|---|
| fc917daa0a | |||
| 1b534228ee |
@@ -1,37 +1,69 @@
|
|||||||
|
import Alert from '@mui/material/Alert'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import Dialog from '@mui/material/Dialog'
|
import Dialog from '@mui/material/Dialog'
|
||||||
import DialogActions from '@mui/material/DialogActions'
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
import DialogTitle from '@mui/material/DialogTitle'
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import MenuItem from '@mui/material/MenuItem'
|
||||||
import TextField from '@mui/material/TextField'
|
import TextField from '@mui/material/TextField'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
import Autocomplete from './Autocomplete'
|
import Autocomplete from './Autocomplete'
|
||||||
import FormDialogContent from './FormDialogContent'
|
import FormDialogContent from './FormDialogContent'
|
||||||
import { SSHAccessProfile } from '../api'
|
import { SSHAccessProfile, SSHServer } from '../api'
|
||||||
|
|
||||||
type SSHSessionCreateDialogProps = {
|
type SSHSessionCreateDialogProps = {
|
||||||
open: boolean
|
open: boolean
|
||||||
profiles: SSHAccessProfile[]
|
profiles: SSHAccessProfile[]
|
||||||
selectedProfile: SSHAccessProfile | null
|
selectedProfile: SSHAccessProfile | null
|
||||||
|
selectedServerID: string
|
||||||
|
serverOptions: SSHServer[]
|
||||||
|
serversLoading: boolean
|
||||||
|
error: string | null
|
||||||
onSelectedProfileChange: (value: SSHAccessProfile | null) => void
|
onSelectedProfileChange: (value: SSHAccessProfile | null) => void
|
||||||
|
onSelectedServerIDChange: (value: string) => void
|
||||||
busy: boolean
|
busy: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onConnect: () => void
|
onConnect: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SSHSessionCreateDialog(props: SSHSessionCreateDialogProps) {
|
export default function SSHSessionCreateDialog(props: SSHSessionCreateDialogProps) {
|
||||||
|
const needsServerSelection: boolean = Boolean(props.selectedProfile?.server_target_type === 'group')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={props.open} onClose={props.onClose} maxWidth="sm" fullWidth>
|
<Dialog open={props.open} onClose={props.onClose} maxWidth="sm" fullWidth>
|
||||||
<DialogTitle>New SSH Session</DialogTitle>
|
<DialogTitle>New SSH Session</DialogTitle>
|
||||||
<FormDialogContent>
|
<FormDialogContent>
|
||||||
|
{props.error ? <Alert severity="error">{props.error}</Alert> : null}
|
||||||
<Autocomplete<SSHAccessProfile, false, false, false>
|
<Autocomplete<SSHAccessProfile, false, false, false>
|
||||||
options={props.profiles}
|
options={props.profiles}
|
||||||
value={props.selectedProfile}
|
value={props.selectedProfile}
|
||||||
onChange={(_, value) => props.onSelectedProfileChange(value)}
|
onChange={(_, value) => props.onSelectedProfileChange(value)}
|
||||||
getOptionLabel={(item) => `${item.name} · ${item.server?.name || item.server_id} · ${item.remote_username}`}
|
getOptionLabel={(item) => `${item.name} · ${item.server_target_type === 'group' ? (item.server_group?.name || item.server_group_id) : (item.server?.name || item.server_id)} · ${item.remote_username}`}
|
||||||
renderInput={(params) => <TextField {...params} label="SSH Access Profile" />}
|
renderInput={(params) => <TextField {...params} label="SSH Access Profile" />}
|
||||||
/>
|
/>
|
||||||
|
{needsServerSelection ? (
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
label="SSH Server"
|
||||||
|
value={props.selectedServerID}
|
||||||
|
onChange={(event) => props.onSelectedServerIDChange(event.target.value)}
|
||||||
|
disabled={props.busy || props.serversLoading || props.serverOptions.length === 0}
|
||||||
|
helperText={props.serversLoading ? 'Loading servers...' : 'Select a target server from this server group.'}
|
||||||
|
>
|
||||||
|
{props.serverOptions.map((item: SSHServer) => (
|
||||||
|
<MenuItem key={item.id} value={item.id}>
|
||||||
|
{item.name} ({item.host}:{item.port})
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
) : null}
|
||||||
|
{needsServerSelection && !props.serversLoading && props.serverOptions.length === 0 ? (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
No enabled servers are available in this group.
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
</FormDialogContent>
|
</FormDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant="contained" onClick={props.onConnect} disabled={props.busy || !props.selectedProfile}>
|
<Button variant="contained" onClick={props.onConnect} disabled={props.busy || !props.selectedProfile || (needsServerSelection && !props.selectedServerID)}>
|
||||||
{props.busy ? 'Connecting...' : 'Connect'}
|
{props.busy ? 'Connecting...' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={props.onClose} disabled={props.busy}>
|
<Button onClick={props.onClose} disabled={props.busy}>
|
||||||
|
|||||||
@@ -324,15 +324,16 @@ export default function SSHServersPage() {
|
|||||||
setConnectSelectedServerIDs([])
|
setConnectSelectedServerIDs([])
|
||||||
}
|
}
|
||||||
|
|
||||||
const openConnectedSessions = (sessionIDs: string[]) => {
|
const openConnectedSessions = (sessionIDs: string[], failures: string[] = []) => {
|
||||||
let encodedIDs: string
|
let encodedIDs: string
|
||||||
|
const state: { connectFailures?: string[] } | undefined = failures.length > 0 ? { connectFailures: failures } : undefined
|
||||||
|
|
||||||
if (sessionIDs.length === 1) {
|
if (sessionIDs.length === 1) {
|
||||||
navigate(`/ssh-sessions/${sessionIDs[0]}`)
|
navigate(`/ssh-sessions/${sessionIDs[0]}`, state ? { state } : undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
encodedIDs = encodeURIComponent(sessionIDs.join(','))
|
encodedIDs = encodeURIComponent(sessionIDs.join(','))
|
||||||
navigate(`/ssh-sessions?sessions=${encodedIDs}`)
|
navigate(`/ssh-sessions?sessions=${encodedIDs}`, state ? { state } : undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getConnectServerLabel = (item: SSHAccessProfile, serverID: string): string => {
|
const getConnectServerLabel = (item: SSHAccessProfile, serverID: string): string => {
|
||||||
@@ -373,7 +374,7 @@ export default function SSHServersPage() {
|
|||||||
}
|
}
|
||||||
clearConnectServerSelection()
|
clearConnectServerSelection()
|
||||||
closeConnectPasswordPrompt()
|
closeConnectPasswordPrompt()
|
||||||
openConnectedSessions(sessions)
|
openConnectedSessions(sessions, failures)
|
||||||
if (failures.length > 0) {
|
if (failures.length > 0) {
|
||||||
setError(`${failures.length} connection${failures.length === 1 ? '' : 's'} failed.`)
|
setError(`${failures.length} connection${failures.length === 1 ? '' : 's'} failed.`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import SSHCredentialsPromptDialog from '../components/SSHCredentialsPromptDialog
|
|||||||
import SSHSessionCreateDialog from '../components/SSHSessionCreateDialog'
|
import SSHSessionCreateDialog from '../components/SSHSessionCreateDialog'
|
||||||
import SSHTranscriptDialog from '../components/SSHTranscriptDialog'
|
import SSHTranscriptDialog from '../components/SSHTranscriptDialog'
|
||||||
import TintedPanel from '../components/TintedPanel'
|
import TintedPanel from '../components/TintedPanel'
|
||||||
import { api, SSHAccessProfile, SSHSession, SSHSessionConnectResponse, SSHSessionListResponse } from '../api'
|
import { api, SSHAccessProfile, SSHServer, SSHSession, SSHSessionConnectResponse, SSHSessionListResponse } from '../api'
|
||||||
|
|
||||||
type SSHSessionStreamMessage = {
|
type SSHSessionStreamMessage = {
|
||||||
session_id?: string
|
session_id?: string
|
||||||
@@ -668,12 +668,17 @@ export default function SSHSessionPage() {
|
|||||||
const [transcriptError, setTranscriptError] = useState<string | null>(null)
|
const [transcriptError, setTranscriptError] = useState<string | null>(null)
|
||||||
const [loadingTranscript, setLoadingTranscript] = useState(false)
|
const [loadingTranscript, setLoadingTranscript] = useState(false)
|
||||||
const [pageError, setPageError] = useState<string | null>(null)
|
const [pageError, setPageError] = useState<string | null>(null)
|
||||||
|
const [connectFailures, setConnectFailures] = useState<string[]>([])
|
||||||
const [newSessionOpen, setNewSessionOpen] = useState(false)
|
const [newSessionOpen, setNewSessionOpen] = useState(false)
|
||||||
const [newSessionPasswordOpen, setNewSessionPasswordOpen] = useState(false)
|
const [newSessionPasswordOpen, setNewSessionPasswordOpen] = useState(false)
|
||||||
const [newSessionPassword, setNewSessionPassword] = useState('')
|
const [newSessionPassword, setNewSessionPassword] = useState('')
|
||||||
const [newSessionOTPCode, setNewSessionOTPCode] = useState('')
|
const [newSessionOTPCode, setNewSessionOTPCode] = useState('')
|
||||||
const [newSessionPasswordError, setNewSessionPasswordError] = useState<string | null>(null)
|
const [newSessionPasswordError, setNewSessionPasswordError] = useState<string | null>(null)
|
||||||
const [selectedProfile, setSelectedProfile] = useState<SSHAccessProfile | null>(null)
|
const [selectedProfile, setSelectedProfile] = useState<SSHAccessProfile | null>(null)
|
||||||
|
const [selectedNewSessionServerID, setSelectedNewSessionServerID] = useState('')
|
||||||
|
const [newSessionServerOptions, setNewSessionServerOptions] = useState<SSHServer[]>([])
|
||||||
|
const [newSessionServersLoading, setNewSessionServersLoading] = useState(false)
|
||||||
|
const [newSessionError, setNewSessionError] = useState<string | null>(null)
|
||||||
const [creatingSession, setCreatingSession] = useState(false)
|
const [creatingSession, setCreatingSession] = useState(false)
|
||||||
const [splitRatio, setSplitRatio] = useState(0.5)
|
const [splitRatio, setSplitRatio] = useState(0.5)
|
||||||
const [splitDragging, setSplitDragging] = useState(false)
|
const [splitDragging, setSplitDragging] = useState(false)
|
||||||
@@ -721,11 +726,14 @@ export default function SSHSessionPage() {
|
|||||||
}, [location.search])
|
}, [location.search])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const state = location.state as { historyOpen?: boolean } | null
|
const state = location.state as { historyOpen?: boolean, connectFailures?: string[] } | null
|
||||||
|
|
||||||
if (state?.historyOpen) {
|
if (state?.historyOpen) {
|
||||||
setHistoryOpen(true)
|
setHistoryOpen(true)
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(state?.connectFailures)) {
|
||||||
|
setConnectFailures(state.connectFailures)
|
||||||
|
}
|
||||||
}, [location.state])
|
}, [location.state])
|
||||||
|
|
||||||
const loadHistory = useCallback(async (signal?: AbortSignal) => {
|
const loadHistory = useCallback(async (signal?: AbortSignal) => {
|
||||||
@@ -1113,6 +1121,35 @@ export default function SSHSessionPage() {
|
|||||||
if (selectedProfile) setNewSessionOpen(true)
|
if (selectedProfile) setNewSessionOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleNewSessionProfileChange = async (profile: SSHAccessProfile | null) => {
|
||||||
|
let servers: SSHServer[]
|
||||||
|
let enabledServers: SSHServer[]
|
||||||
|
|
||||||
|
setSelectedProfile(profile)
|
||||||
|
setSelectedNewSessionServerID('')
|
||||||
|
setNewSessionServerOptions([])
|
||||||
|
setNewSessionError(null)
|
||||||
|
if (!profile || profile.server_target_type !== 'group') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setNewSessionServersLoading(true)
|
||||||
|
try {
|
||||||
|
servers = await api.listSSHAccessProfileServersForSelf(profile.id)
|
||||||
|
enabledServers = (Array.isArray(servers) ? servers : []).filter((item: SSHServer) => item.enabled)
|
||||||
|
setNewSessionServerOptions(enabledServers)
|
||||||
|
if (enabledServers.length === 1) {
|
||||||
|
setSelectedNewSessionServerID(enabledServers[0].id)
|
||||||
|
}
|
||||||
|
if (enabledServers.length === 0) {
|
||||||
|
setNewSessionError('No enabled servers are available in this group.')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setNewSessionError(err instanceof Error ? err.message : 'Failed to load SSH servers')
|
||||||
|
} finally {
|
||||||
|
setNewSessionServersLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleCreateSession = async (password?: string, otpCode?: string) => {
|
const handleCreateSession = async (password?: string, otpCode?: string) => {
|
||||||
let ti: TerminalInfo
|
let ti: TerminalInfo
|
||||||
let nextSession: SSHSessionConnectResponse
|
let nextSession: SSHSessionConnectResponse
|
||||||
@@ -1122,6 +1159,10 @@ export default function SSHSessionPage() {
|
|||||||
setPageError('SSH access profile is required')
|
setPageError('SSH access profile is required')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (selectedProfile.server_target_type === 'group' && !selectedNewSessionServerID) {
|
||||||
|
setNewSessionError('Select an SSH server for this server group profile.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ((requiresPromptedPassword(selectedProfile) && password === undefined) ||
|
if ((requiresPromptedPassword(selectedProfile) && password === undefined) ||
|
||||||
(requiresPromptedTOTP(selectedProfile) && otpCode === undefined)) {
|
(requiresPromptedTOTP(selectedProfile) && otpCode === undefined)) {
|
||||||
@@ -1146,6 +1187,9 @@ export default function SSHSessionPage() {
|
|||||||
setNewSessionPasswordError(null)
|
setNewSessionPasswordError(null)
|
||||||
try {
|
try {
|
||||||
ti = buildInitialTerminalInfo(password, otpCode)
|
ti = buildInitialTerminalInfo(password, otpCode)
|
||||||
|
if (selectedProfile.server_target_type === 'group') {
|
||||||
|
ti.server_id = selectedNewSessionServerID
|
||||||
|
}
|
||||||
nextSession = await api.connectSSHAccessProfileForSelf(selectedProfile.id, ti)
|
nextSession = await api.connectSSHAccessProfileForSelf(selectedProfile.id, ti)
|
||||||
setOpenSessionIDs((prev) => (prev.includes(nextSession.session_id) ? prev : [...prev, nextSession.session_id]))
|
setOpenSessionIDs((prev) => (prev.includes(nextSession.session_id) ? prev : [...prev, nextSession.session_id]))
|
||||||
setNewSessionOpen(false)
|
setNewSessionOpen(false)
|
||||||
@@ -1153,6 +1197,9 @@ export default function SSHSessionPage() {
|
|||||||
setNewSessionPassword('')
|
setNewSessionPassword('')
|
||||||
setNewSessionOTPCode('')
|
setNewSessionOTPCode('')
|
||||||
setSelectedProfile(null)
|
setSelectedProfile(null)
|
||||||
|
setSelectedNewSessionServerID('')
|
||||||
|
setNewSessionServerOptions([])
|
||||||
|
setNewSessionError(null)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message = err instanceof Error ? err.message : 'Failed to create SSH session'
|
message = err instanceof Error ? err.message : 'Failed to create SSH session'
|
||||||
if (password !== undefined || otpCode !== undefined) {
|
if (password !== undefined || otpCode !== undefined) {
|
||||||
@@ -1165,10 +1212,28 @@ export default function SSHSessionPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNewSessionPromptTargetText = (): string => {
|
||||||
|
let server: SSHServer | undefined
|
||||||
|
|
||||||
|
if (!selectedProfile) {
|
||||||
|
return 'Enter the password for this SSH access profile.'
|
||||||
|
}
|
||||||
|
if (selectedProfile.server_target_type === 'group') {
|
||||||
|
server = newSessionServerOptions.find((item: SSHServer) => item.id === selectedNewSessionServerID)
|
||||||
|
if (server) {
|
||||||
|
return `${selectedProfile.remote_username}@${server.host}:${server.port}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${selectedProfile.remote_username}@${selectedProfile.server?.host || selectedProfile.server_id}:${selectedProfile.server?.port || 22}`
|
||||||
|
}
|
||||||
|
|
||||||
const openNewSessionDialog = async () => {
|
const openNewSessionDialog = async () => {
|
||||||
const ac: AbortController = new AbortController()
|
const ac: AbortController = new AbortController()
|
||||||
|
|
||||||
setSelectedProfile(null)
|
setSelectedProfile(null)
|
||||||
|
setSelectedNewSessionServerID('')
|
||||||
|
setNewSessionServerOptions([])
|
||||||
|
setNewSessionError(null)
|
||||||
setPageError(null)
|
setPageError(null)
|
||||||
setNewSessionPasswordOpen(false)
|
setNewSessionPasswordOpen(false)
|
||||||
setNewSessionPassword('')
|
setNewSessionPassword('')
|
||||||
@@ -1428,7 +1493,7 @@ export default function SSHSessionPage() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'grid', gap: 1 }}>
|
<Box sx={{ display: 'grid', gridTemplateRows: 'auto auto minmax(0, 1fr)', gap: 1, height: 'calc(100vh - 76px)', minHeight: 0, overflow: 'hidden' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, flexWrap: 'wrap' }}>
|
||||||
<Typography variant="h5">SSH Sessions</Typography>
|
<Typography variant="h5">SSH Sessions</Typography>
|
||||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
@@ -1443,7 +1508,21 @@ export default function SSHSessionPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{pageError ? <Alert severity="error">{pageError}</Alert> : null}
|
<Box sx={{ display: 'grid', gap: 1, minHeight: 0 }}>
|
||||||
|
{pageError ? <Alert severity="error">{pageError}</Alert> : null}
|
||||||
|
{connectFailures.length > 0 ? (
|
||||||
|
<Alert severity="warning" onClose={() => setConnectFailures([])}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{connectFailures.length} SSH connection{connectFailures.length === 1 ? '' : 's'} failed.
|
||||||
|
</Typography>
|
||||||
|
{connectFailures.map((item: string) => (
|
||||||
|
<Typography key={item} variant="caption" component="div">
|
||||||
|
{item}
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
{/*openSessionIDs.length ? (
|
{/*openSessionIDs.length ? (
|
||||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
{openSessionIDs.map((id) => (
|
{openSessionIDs.map((id) => (
|
||||||
@@ -1455,6 +1534,7 @@ export default function SSHSessionPage() {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
) :*/ null}
|
) :*/ null}
|
||||||
|
<Box sx={{ minHeight: 0, minWidth: 0 }}>
|
||||||
{openSessionIDs.length ? (
|
{openSessionIDs.length ? (
|
||||||
stackedLayout ? (
|
stackedLayout ? (
|
||||||
<Box
|
<Box
|
||||||
@@ -1464,7 +1544,7 @@ export default function SSHSessionPage() {
|
|||||||
gridTemplateColumns: 'minmax(0, 1fr)',
|
gridTemplateColumns: 'minmax(0, 1fr)',
|
||||||
gridTemplateRows: gridTemplateRows,
|
gridTemplateRows: gridTemplateRows,
|
||||||
alignItems: 'stretch',
|
alignItems: 'stretch',
|
||||||
height: 'calc(100vh - 120px)',
|
height: '100%',
|
||||||
minHeight: minPanelHeight,
|
minHeight: minPanelHeight,
|
||||||
minWidth: 0
|
minWidth: 0
|
||||||
}}
|
}}
|
||||||
@@ -1527,7 +1607,7 @@ export default function SSHSessionPage() {
|
|||||||
gridTemplateColumns: (openSessionIDs.length > 1 ? `${splitRatio}fr 12px ${1 - splitRatio}fr` : 'minmax(0, 1fr)'),
|
gridTemplateColumns: (openSessionIDs.length > 1 ? `${splitRatio}fr 12px ${1 - splitRatio}fr` : 'minmax(0, 1fr)'),
|
||||||
gridTemplateRows: gridTemplateRows,
|
gridTemplateRows: gridTemplateRows,
|
||||||
alignItems: 'stretch',
|
alignItems: 'stretch',
|
||||||
height: 'calc(100vh - 120px)', // TODO: how to make this fit the content? (must subtract the height of the topbar and the page's title area
|
height: '100%',
|
||||||
minHeight: minPanelHeight,
|
minHeight: minPanelHeight,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
@@ -1622,6 +1702,7 @@ export default function SSHSessionPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
</TintedPanel>
|
</TintedPanel>
|
||||||
)}
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{historyDialogMode ? (
|
{historyDialogMode ? (
|
||||||
<Dialog open={historyOpen} onClose={handleCloseHistory} fullScreen>
|
<Dialog open={historyOpen} onClose={handleCloseHistory} fullScreen>
|
||||||
@@ -1677,7 +1758,12 @@ export default function SSHSessionPage() {
|
|||||||
open={newSessionOpen}
|
open={newSessionOpen}
|
||||||
profiles={profiles}
|
profiles={profiles}
|
||||||
selectedProfile={selectedProfile}
|
selectedProfile={selectedProfile}
|
||||||
onSelectedProfileChange={setSelectedProfile}
|
selectedServerID={selectedNewSessionServerID}
|
||||||
|
serverOptions={newSessionServerOptions}
|
||||||
|
serversLoading={newSessionServersLoading}
|
||||||
|
error={newSessionError}
|
||||||
|
onSelectedProfileChange={(profile) => void handleNewSessionProfileChange(profile)}
|
||||||
|
onSelectedServerIDChange={setSelectedNewSessionServerID}
|
||||||
busy={creatingSession}
|
busy={creatingSession}
|
||||||
onClose={() => setNewSessionOpen(false)}
|
onClose={() => setNewSessionOpen(false)}
|
||||||
onConnect={() => void handleCreateSession()}
|
onConnect={() => void handleCreateSession()}
|
||||||
@@ -1685,7 +1771,7 @@ export default function SSHSessionPage() {
|
|||||||
|
|
||||||
<SSHCredentialsPromptDialog
|
<SSHCredentialsPromptDialog
|
||||||
open={newSessionPasswordOpen}
|
open={newSessionPasswordOpen}
|
||||||
targetText={selectedProfile ? `${selectedProfile.remote_username}@${selectedProfile.server?.host || selectedProfile.server_id}:${selectedProfile.server?.port || 22}` : 'Enter the password for this SSH access profile.'}
|
targetText={getNewSessionPromptTargetText()}
|
||||||
error={newSessionPasswordError}
|
error={newSessionPasswordError}
|
||||||
passwordRequired={Boolean(selectedProfile && requiresPromptedPassword(selectedProfile))}
|
passwordRequired={Boolean(selectedProfile && requiresPromptedPassword(selectedProfile))}
|
||||||
password={newSessionPassword}
|
password={newSessionPassword}
|
||||||
|
|||||||
Reference in New Issue
Block a user