fix: Several bug fixes for terminals, server stats, and general feature improvements
This commit is contained in:
@@ -640,6 +640,9 @@ export function CredentialsManager({
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{credential.username}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
ID: {credential.id}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{credential.authType === "password"
|
||||
? t("credentials.password")
|
||||
|
||||
@@ -527,41 +527,20 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = () => reject(reader.error);
|
||||
|
||||
const isTextFile =
|
||||
file.type.startsWith("text/") ||
|
||||
file.type === "application/json" ||
|
||||
file.type === "application/javascript" ||
|
||||
file.type === "application/xml" ||
|
||||
file.type === "image/svg+xml" ||
|
||||
file.name.match(
|
||||
/\.(txt|json|js|ts|jsx|tsx|css|scss|less|html|htm|xml|svg|yaml|yml|md|markdown|mdown|mkdn|mdx|py|java|c|cpp|h|sh|bash|zsh|bat|ps1|toml|ini|conf|config|sql|vue|svelte)$/i,
|
||||
);
|
||||
|
||||
if (isTextFile) {
|
||||
reader.onload = () => {
|
||||
if (reader.result) {
|
||||
resolve(reader.result as string);
|
||||
} else {
|
||||
reject(new Error("Failed to read text file content"));
|
||||
reader.onload = () => {
|
||||
if (reader.result instanceof ArrayBuffer) {
|
||||
const bytes = new Uint8Array(reader.result);
|
||||
let binary = "";
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
reader.onload = () => {
|
||||
if (reader.result instanceof ArrayBuffer) {
|
||||
const bytes = new Uint8Array(reader.result);
|
||||
let binary = "";
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
const base64 = btoa(binary);
|
||||
resolve(base64);
|
||||
} else {
|
||||
reject(new Error("Failed to read binary file"));
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
const base64 = btoa(binary);
|
||||
resolve(base64);
|
||||
} else {
|
||||
reject(new Error("Failed to read file"));
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
|
||||
await uploadSSHFile(
|
||||
|
||||
@@ -217,6 +217,7 @@ export function HostManagerEditor({
|
||||
pin: z.boolean().default(false),
|
||||
authType: z.enum(["password", "key", "credential", "none"]),
|
||||
credentialId: z.number().optional().nullable(),
|
||||
overrideCredentialUsername: z.boolean().optional(),
|
||||
password: z.string().optional(),
|
||||
key: z.any().optional().nullable(),
|
||||
keyPassword: z.string().optional(),
|
||||
@@ -389,6 +390,7 @@ export function HostManagerEditor({
|
||||
pin: false,
|
||||
authType: "password" as const,
|
||||
credentialId: null,
|
||||
overrideCredentialUsername: false,
|
||||
password: "",
|
||||
key: null,
|
||||
keyPassword: "",
|
||||
@@ -407,7 +409,8 @@ export function HostManagerEditor({
|
||||
useEffect(() => {
|
||||
if (authTab === "credential") {
|
||||
const currentCredentialId = form.getValues("credentialId");
|
||||
if (currentCredentialId) {
|
||||
const overrideUsername = form.getValues("overrideCredentialUsername");
|
||||
if (currentCredentialId && !overrideUsername) {
|
||||
const selectedCredential = credentials.find(
|
||||
(c) => c.id === currentCredentialId,
|
||||
);
|
||||
@@ -464,6 +467,9 @@ export function HostManagerEditor({
|
||||
pin: Boolean(cleanedHost.pin),
|
||||
authType: defaultAuthType as "password" | "key" | "credential" | "none",
|
||||
credentialId: null,
|
||||
overrideCredentialUsername: Boolean(
|
||||
cleanedHost.overrideCredentialUsername,
|
||||
),
|
||||
password: "",
|
||||
key: null,
|
||||
keyPassword: "",
|
||||
@@ -512,6 +518,7 @@ export function HostManagerEditor({
|
||||
pin: false,
|
||||
authType: "password" as const,
|
||||
credentialId: null,
|
||||
overrideCredentialUsername: false,
|
||||
password: "",
|
||||
key: null,
|
||||
keyPassword: "",
|
||||
@@ -574,6 +581,7 @@ export function HostManagerEditor({
|
||||
tags: data.tags || [],
|
||||
pin: Boolean(data.pin),
|
||||
authType: data.authType,
|
||||
overrideCredentialUsername: Boolean(data.overrideCredentialUsername),
|
||||
enableTerminal: Boolean(data.enableTerminal),
|
||||
enableTunnel: Boolean(data.enableTunnel),
|
||||
enableFileManager: Boolean(data.enableFileManager),
|
||||
@@ -882,17 +890,28 @@ export function HostManagerEditor({
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-6">
|
||||
<FormLabel>{t("hosts.username")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t("placeholders.username")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
render={({ field }) => {
|
||||
const isCredentialAuth = authTab === "credential";
|
||||
const hasCredential = !!form.watch("credentialId");
|
||||
const overrideEnabled = !!form.watch(
|
||||
"overrideCredentialUsername",
|
||||
);
|
||||
const shouldDisable =
|
||||
isCredentialAuth && hasCredential && !overrideEnabled;
|
||||
|
||||
return (
|
||||
<FormItem className="col-span-6">
|
||||
<FormLabel>{t("hosts.username")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t("placeholders.username")}
|
||||
disabled={shouldDisable}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FormLabel className="mb-3 mt-3 font-bold">
|
||||
@@ -1263,29 +1282,60 @@ export function HostManagerEditor({
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="credential">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="credentialId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<CredentialSelector
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
onCredentialSelect={(credential) => {
|
||||
if (credential) {
|
||||
form.setValue(
|
||||
"username",
|
||||
credential.username,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("hosts.credentialDescription")}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="credentialId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<CredentialSelector
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
onCredentialSelect={(credential) => {
|
||||
if (
|
||||
credential &&
|
||||
!form.getValues(
|
||||
"overrideCredentialUsername",
|
||||
)
|
||||
) {
|
||||
form.setValue(
|
||||
"username",
|
||||
credential.username,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("hosts.credentialDescription")}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{form.watch("credentialId") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="overrideCredentialUsername"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>
|
||||
{t("hosts.overrideCredentialUsername")}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t("hosts.overrideCredentialUsernameDesc")}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="none">
|
||||
<Alert className="mt-2">
|
||||
|
||||
@@ -112,6 +112,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
const [keyboardInteractiveDetected, setKeyboardInteractiveDetected] =
|
||||
useState(false);
|
||||
const isVisibleRef = useRef<boolean>(false);
|
||||
const isReadyRef = useRef<boolean>(false);
|
||||
const isFittingRef = useRef(false);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const reconnectAttempts = useRef(0);
|
||||
@@ -157,6 +158,10 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
isVisibleRef.current = isVisible;
|
||||
}, [isVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
isReadyRef.current = isReady;
|
||||
}, [isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = () => {
|
||||
const jwtToken = getCookie("jwt");
|
||||
@@ -507,6 +512,9 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
}),
|
||||
);
|
||||
terminal.onData((data) => {
|
||||
if (data === "\x00" || data === "\u0000") {
|
||||
return;
|
||||
}
|
||||
ws.send(JSON.stringify({ type: "input", data }));
|
||||
});
|
||||
|
||||
@@ -915,15 +923,21 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
|
||||
element?.addEventListener("keydown", handleMacKeyboard, true);
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const handleResize = () => {
|
||||
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
||||
resizeTimeout.current = setTimeout(() => {
|
||||
if (!isVisibleRef.current || !isReady) return;
|
||||
if (!isVisibleRef.current || !isReadyRef.current) return;
|
||||
performFit();
|
||||
}, 50);
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
resizeObserver.observe(xtermRef.current);
|
||||
const resizeObserver = new ResizeObserver(handleResize);
|
||||
|
||||
if (xtermRef.current) {
|
||||
resizeObserver.observe(xtermRef.current);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
setVisible(true);
|
||||
|
||||
@@ -936,6 +950,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
setIsReady(false);
|
||||
isFittingRef.current = false;
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener("resize", handleResize);
|
||||
element?.removeEventListener("contextmenu", handleContextMenu);
|
||||
element?.removeEventListener("keydown", handleMacKeyboard, true);
|
||||
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
|
||||
|
||||
Reference in New Issue
Block a user