mirror of
https://github.com/Termix-SSH/Mobile.git
synced 2026-07-28 05:51:43 +02:00
6c40d2f0cc
* fix: register physical Tab key on iPadOS/iOS hardware keyboards (#14) The native module only registered Shift+Tab but not bare Tab, so iOS intercepted it for UI focus navigation. Register an unmodified Tab UIKeyCommand with wantsPriorityOverSystemBehavior and handle both Tab and Shift+Tab in the same JS branch. Fixes Termix-SSH/Support#557 * fix: pass jumpHosts, forceKeyboardInteractive, and overrideCredentialUsername to terminal connection (#15) These fields were defined on SSHHost but omitted from the terminal hostConfig passthrough. Without jumpHosts the backend cannot route through jump servers; without forceKeyboardInteractive hosts that require keyboard-interactive auth fail silently; without overrideCredentialUsername shared credentials use the wrong username. Fixes Termix-SSH/Support#422 Fixes Termix-SSH/Support#638 * fix: improve mobile terminal scroll recovery (#17) * Add mobile remote desktop sessions (#18) * feat: add mobile remote desktop sessions * feat: add remote desktop input controls * feat: polish remote desktop controls * feat: add mobile terminal font selection (#19) * feat: add mobile shift tab hotkey (#20) * feat: initial UI redesign * feat: revamp server login system * chore: update app icon/background color * fix: remove unused fields in the settings tab * feat: improve the user pin system and update all settings modals to use new ui * fix: active server not updating unless app restarts * feat: add version in settings * feat: improve host page UI (updated host form and added credential management) * fix: preserve composed iOS terminal input * ci: restore mobile checks * feat: rewrite of connection system for all types * fix: preserve mobile jump hosts (#30) * fix: guard android user ca trust (#29) * fix: capture ios hardware tab key (#27) * fix: reset terminal input after special keys (#28) * fix: open oidc in system browser (#26) * fix: preserve websocket auth context (#25) * fix: keep none-auth terminal prompts alive (#23) * fix: allow local network SSL in Android app (#21) * feat: allow oidc passkeys * feat: bundeled xterm into app and added docker loading screen * feat: add 2fa, api key, and active sessions to settings and addressed multiple bug fixes and inconsistencies with termix web * chore: update repo images and readme * chore: update readme to split table for screenshots * chore: rename ios to apple in build workflow * fix: android build failure * feat: add snippet management and fix several bugs * fix: web login and oidc incorrect logic * chore: run linter --------- Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
159 lines
4.6 KiB
TypeScript
159 lines
4.6 KiB
TypeScript
import { useState, useEffect, useCallback } from "react";
|
|
import { View, Platform } from "react-native";
|
|
import { Lock } from "lucide-react-native";
|
|
import { Dialog, Input, Button, Text, SegmentedControl } from "@/app/components/ui";
|
|
import { useThemeColor } from "@/app/contexts/ThemeContext";
|
|
|
|
interface SSHAuthDialogProps {
|
|
visible: boolean;
|
|
onSubmit: (credentials: {
|
|
password?: string;
|
|
sshKey?: string;
|
|
keyPassword?: string;
|
|
}) => void;
|
|
onCancel: () => void;
|
|
hostInfo: {
|
|
name?: string;
|
|
ip: string;
|
|
port: number;
|
|
username: string;
|
|
};
|
|
reason: "no_keyboard" | "auth_failed" | "timeout";
|
|
}
|
|
|
|
export function SSHAuthDialog({
|
|
visible,
|
|
onSubmit,
|
|
onCancel,
|
|
hostInfo,
|
|
reason,
|
|
}: SSHAuthDialogProps) {
|
|
const color = useThemeColor();
|
|
const [authMethod, setAuthMethod] = useState<"password" | "key">("password");
|
|
const [password, setPassword] = useState("");
|
|
const [sshKey, setSshKey] = useState("");
|
|
const [keyPassword, setKeyPassword] = useState("");
|
|
|
|
useEffect(() => {
|
|
if (!visible) {
|
|
setPassword("");
|
|
setSshKey("");
|
|
setKeyPassword("");
|
|
setAuthMethod("password");
|
|
}
|
|
}, [visible]);
|
|
|
|
const handleSubmit = useCallback(() => {
|
|
if (authMethod === "password" && password.trim()) {
|
|
onSubmit({ password });
|
|
} else if (authMethod === "key" && sshKey.trim()) {
|
|
onSubmit({ sshKey, keyPassword: keyPassword.trim() || undefined });
|
|
}
|
|
}, [authMethod, password, sshKey, keyPassword, onSubmit]);
|
|
|
|
const isValid =
|
|
authMethod === "password" ? !!password.trim() : !!sshKey.trim();
|
|
|
|
const hostLabel = hostInfo.name
|
|
? `${hostInfo.name} · ${hostInfo.username}@${hostInfo.ip}:${hostInfo.port}`
|
|
: `${hostInfo.username}@${hostInfo.ip}:${hostInfo.port}`;
|
|
|
|
const reasonText =
|
|
reason === "no_keyboard"
|
|
? "Keyboard-interactive auth is not available. Enter credentials directly."
|
|
: reason === "auth_failed"
|
|
? "Authentication failed. Please re-enter your credentials."
|
|
: "Connection timed out. Please try again with your credentials.";
|
|
|
|
const bannerBg =
|
|
reason === "auth_failed" || reason === "timeout"
|
|
? "bg-destructive/10 border-destructive/40"
|
|
: "bg-yellow-500/10 border-yellow-500/40";
|
|
const bannerText =
|
|
reason === "auth_failed" || reason === "timeout"
|
|
? "text-destructive"
|
|
: "text-yellow-400";
|
|
|
|
return (
|
|
<Dialog
|
|
visible={visible}
|
|
onClose={onCancel}
|
|
icon={<Lock size={18} color={color("accent-brand")} />}
|
|
title="SSH Authentication Required"
|
|
description={hostLabel}
|
|
footer={
|
|
<View className="flex-row gap-2 flex-1">
|
|
<Button variant="outline" className="flex-1" onPress={onCancel}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="accent"
|
|
className="flex-1"
|
|
onPress={handleSubmit}
|
|
disabled={!isValid}
|
|
>
|
|
Connect
|
|
</Button>
|
|
</View>
|
|
}
|
|
>
|
|
<View className={`border rounded px-3 py-2.5 mb-3 ${bannerBg}`}>
|
|
<Text className={`text-xs ${bannerText}`}>{reasonText}</Text>
|
|
</View>
|
|
|
|
<SegmentedControl<"password" | "key">
|
|
options={[
|
|
{ id: "password", label: "Password" },
|
|
{ id: "key", label: "SSH Key" },
|
|
]}
|
|
value={authMethod}
|
|
onChange={setAuthMethod}
|
|
className="mb-3"
|
|
/>
|
|
|
|
{authMethod === "password" ? (
|
|
<Input
|
|
value={password}
|
|
onChangeText={setPassword}
|
|
placeholder="Password"
|
|
secureTextEntry
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
autoComplete="off"
|
|
autoFocus
|
|
onSubmitEditing={handleSubmit}
|
|
/>
|
|
) : (
|
|
<View className="gap-2">
|
|
<Input
|
|
value={sshKey}
|
|
onChangeText={setSshKey}
|
|
placeholder={"-----BEGIN OPENSSH PRIVATE KEY-----\nPaste your private key here...\n-----END OPENSSH PRIVATE KEY-----"}
|
|
multiline
|
|
numberOfLines={5}
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
autoComplete="off"
|
|
autoFocus
|
|
style={{
|
|
minHeight: 110,
|
|
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
fontSize: 12,
|
|
}}
|
|
/>
|
|
<Input
|
|
value={keyPassword}
|
|
onChangeText={setKeyPassword}
|
|
placeholder="Key passphrase (optional)"
|
|
secureTextEntry
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
autoComplete="off"
|
|
onSubmitEditing={handleSubmit}
|
|
/>
|
|
</View>
|
|
)}
|
|
</Dialog>
|
|
);
|
|
}
|