Add SSH connection validation for settings
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
useSSHKeys,
|
||||
useSaveMonitoringMachine,
|
||||
useSaveSSHKey,
|
||||
useTestMonitoringMachineSSH,
|
||||
} from "../hooks/useSettings";
|
||||
import { DialogFooter } from "../components/DialogFooter";
|
||||
import { HoverEditButton } from "../components/HoverEditButton";
|
||||
@@ -76,6 +77,10 @@ function MachineEditor({
|
||||
machine,
|
||||
sshKeys,
|
||||
onChange,
|
||||
onValidateSSH,
|
||||
isValidatingSSH,
|
||||
sshValidationMessage,
|
||||
sshValidationError,
|
||||
}: {
|
||||
title: string;
|
||||
hint?: string;
|
||||
@@ -86,6 +91,10 @@ function MachineEditor({
|
||||
| MonitoringMachineInput
|
||||
| ((current: MonitoringMachineInput) => MonitoringMachineInput),
|
||||
) => void;
|
||||
onValidateSSH: () => void;
|
||||
isValidatingSSH: boolean;
|
||||
sshValidationMessage: string;
|
||||
sshValidationError: string;
|
||||
}) {
|
||||
const draft = machine;
|
||||
const setDraft = onChange;
|
||||
@@ -417,6 +426,35 @@ function MachineEditor({
|
||||
SSH machines usually need monitoring or files enabled.
|
||||
</Alert>
|
||||
)}
|
||||
{!isLocal && (
|
||||
<Stack spacing={1}>
|
||||
<Alert severity="info">
|
||||
Validate SSH before saving: this records the first trusted host key
|
||||
in the backend-managed known_hosts file, then checks SSH auth.
|
||||
</Alert>
|
||||
<Stack direction="row" spacing={1} sx={{ flexWrap: "wrap" }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={onValidateSSH}
|
||||
disabled={
|
||||
isValidatingSSH ||
|
||||
!draft.host.trim() ||
|
||||
!draft.username.trim()
|
||||
}
|
||||
>
|
||||
{isValidatingSSH
|
||||
? "Validating SSH..."
|
||||
: "Validate SSH + trust host"}
|
||||
</Button>
|
||||
</Stack>
|
||||
{sshValidationMessage && (
|
||||
<Alert severity="success">{sshValidationMessage}</Alert>
|
||||
)}
|
||||
{sshValidationError && (
|
||||
<Alert severity="error">{sshValidationError}</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
{hasJellyseerr && !draft.jellyseerr_url && (
|
||||
<Alert severity="info">
|
||||
Jellyseerr is enabled, but no URL is configured yet.
|
||||
@@ -828,7 +866,10 @@ export function Settings() {
|
||||
const { data: sshKeys = [] } = useSSHKeys();
|
||||
const saveMachine = useSaveMonitoringMachine();
|
||||
const deleteMachine = useDeleteMonitoringMachine();
|
||||
const testMachineSSH = useTestMonitoringMachineSSH();
|
||||
const [tab, setTab] = useState<SettingsTab>("machines");
|
||||
const [sshValidationMessage, setSSHValidationMessage] = useState("");
|
||||
const [sshValidationError, setSSHValidationError] = useState("");
|
||||
const [machineDialogOpen, setMachineDialogOpen] = useState(false);
|
||||
const [machineDraft, setMachineDraft] = useState<MonitoringMachineInput>(
|
||||
emptyMachine(),
|
||||
@@ -848,26 +889,54 @@ export function Settings() {
|
||||
const sshMachines = orderedMachines.filter(
|
||||
(machine) => machine.mode === "ssh",
|
||||
);
|
||||
const clearSSHValidation = () => {
|
||||
setSSHValidationMessage("");
|
||||
setSSHValidationError("");
|
||||
};
|
||||
const beginLocal = () => {
|
||||
clearSSHValidation();
|
||||
setMachineDraft(emptyMachine("local"));
|
||||
setMachineDialogOpen(true);
|
||||
};
|
||||
const beginRemote = () => {
|
||||
clearSSHValidation();
|
||||
setMachineDraft(emptyMachine("ssh"));
|
||||
setMachineDialogOpen(true);
|
||||
};
|
||||
const openEditMachine = (machine: MonitoringMachineInput) => {
|
||||
clearSSHValidation();
|
||||
setMachineDraft(machine);
|
||||
setMachineDialogOpen(true);
|
||||
};
|
||||
const closeMachineDialog = () => {
|
||||
clearSSHValidation();
|
||||
setMachineDialogOpen(false);
|
||||
};
|
||||
const updateMachineDraft = (
|
||||
draft:
|
||||
| MonitoringMachineInput
|
||||
| ((current: MonitoringMachineInput) => MonitoringMachineInput),
|
||||
) => {
|
||||
clearSSHValidation();
|
||||
setMachineDraft(draft);
|
||||
};
|
||||
const saveMachineDraft = async (draft: MonitoringMachineInput) => {
|
||||
clearSSHValidation();
|
||||
await saveMachine.mutateAsync(draft);
|
||||
setMachineDialogOpen(false);
|
||||
setMachineDraft(emptyMachine(draft.mode));
|
||||
};
|
||||
const validateMachineSSH = async () => {
|
||||
clearSSHValidation();
|
||||
try {
|
||||
const result = await testMachineSSH.mutateAsync(machineDraft);
|
||||
setSSHValidationMessage(result.message);
|
||||
} catch (error) {
|
||||
setSSHValidationError(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Stack spacing={2.25}>
|
||||
<Stack spacing={0.5}>
|
||||
@@ -1209,7 +1278,11 @@ export function Settings() {
|
||||
}
|
||||
machine={machineDraft}
|
||||
sshKeys={sshKeys}
|
||||
onChange={setMachineDraft}
|
||||
onChange={updateMachineDraft}
|
||||
onValidateSSH={validateMachineSSH}
|
||||
isValidatingSSH={testMachineSSH.isPending}
|
||||
sshValidationMessage={sshValidationMessage}
|
||||
sshValidationError={sshValidationError}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogFooter
|
||||
|
||||
Reference in New Issue
Block a user