) : filteredFiles.length === 0 ? (
-
) : (
diff --git a/src/ui/Desktop/Apps/File Manager/FileManagerLeftSidebarFileViewer.tsx b/src/ui/Desktop/Apps/File Manager/FileManagerLeftSidebarFileViewer.tsx
index 5a86b5c0..f77fe27f 100644
--- a/src/ui/Desktop/Apps/File Manager/FileManagerLeftSidebarFileViewer.tsx
+++ b/src/ui/Desktop/Apps/File Manager/FileManagerLeftSidebarFileViewer.tsx
@@ -1,8 +1,7 @@
import React from 'react';
import {Button} from '@/components/ui/button.tsx';
import {Card} from '@/components/ui/card.tsx';
-import {Separator} from '@/components/ui/separator.tsx';
-import {Plus, Folder, File, Star, Trash2, Edit, Link2, Server, Pin} from 'lucide-react';
+import {Folder, File, Trash2, Pin} from 'lucide-react';
import {useTranslation} from 'react-i18next';
interface SSHConnection {
@@ -43,12 +42,6 @@ interface FileManagerLeftSidebarVileViewerProps {
}
export function FileManagerLeftSidebarFileViewer({
- sshConnections,
- onAddSSH,
- onConnectSSH,
- onEditSSH,
- onDeleteSSH,
- onPinSSH,
currentPath,
files,
onOpenFile,
@@ -58,12 +51,9 @@ export function FileManagerLeftSidebarFileViewer({
isLoading,
error,
isSSHMode,
- onSwitchToLocal,
- onSwitchToSSH,
- currentSSH,
}: FileManagerLeftSidebarVileViewerProps) {
const {t} = useTranslation();
-
+
return (
diff --git a/src/ui/Desktop/Apps/File Manager/FileManagerOperations.tsx b/src/ui/Desktop/Apps/File Manager/FileManagerOperations.tsx
index d21a7199..258251f3 100644
--- a/src/ui/Desktop/Apps/File Manager/FileManagerOperations.tsx
+++ b/src/ui/Desktop/Apps/File Manager/FileManagerOperations.tsx
@@ -10,14 +10,13 @@ import {
Trash2,
Edit3,
X,
- Check,
AlertCircle,
FileText,
Folder
} from 'lucide-react';
import {cn} from '@/lib/utils.ts';
import {useTranslation} from 'react-i18next';
-import type { FileManagerOperationsProps } from '../../../types/index.js';
+import type {FileManagerOperationsProps} from '../../../types/index.js';
export function FileManagerOperations({
currentPath,
@@ -56,7 +55,7 @@ export function FileManagerOperations({
};
checkContainerWidth();
-
+
const resizeObserver = new ResizeObserver(checkContainerWidth);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
@@ -71,32 +70,28 @@ export function FileManagerOperations({
if (!uploadFile || !sshSessionId) return;
setIsLoading(true);
-
- // Show loading toast
+
const {toast} = await import('sonner');
- const loadingToast = toast.loading(t('fileManager.uploadingFile', { name: uploadFile.name }));
-
+ const loadingToast = toast.loading(t('fileManager.uploadingFile', {name: uploadFile.name}));
+
try {
const content = await uploadFile.text();
const {uploadSSHFile} = await import('@/ui/main-axios.ts');
const response = await uploadSSHFile(sshSessionId, currentPath, uploadFile.name, content);
-
- // Dismiss loading toast and show success
+
toast.dismiss(loadingToast);
-
- // Handle toast notification from backend
+
if (response?.toast) {
toast[response.toast.type](response.toast.message);
} else {
- onSuccess(t('fileManager.fileUploadedSuccessfully', { name: uploadFile.name }));
+ onSuccess(t('fileManager.fileUploadedSuccessfully', {name: uploadFile.name}));
}
-
+
setShowUpload(false);
setUploadFile(null);
onOperationComplete();
} catch (error: any) {
- // Dismiss loading toast and show error
toast.dismiss(loadingToast);
onError(error?.response?.data?.error || t('fileManager.failedToUploadFile'));
} finally {
@@ -108,31 +103,27 @@ export function FileManagerOperations({
if (!newFileName.trim() || !sshSessionId) return;
setIsLoading(true);
-
- // Show loading toast
+
const {toast} = await import('sonner');
- const loadingToast = toast.loading(t('fileManager.creatingFile', { name: newFileName.trim() }));
-
+ const loadingToast = toast.loading(t('fileManager.creatingFile', {name: newFileName.trim()}));
+
try {
const {createSSHFile} = await import('@/ui/main-axios.ts');
const response = await createSSHFile(sshSessionId, currentPath, newFileName.trim());
-
- // Dismiss loading toast
+
toast.dismiss(loadingToast);
-
- // Handle toast notification from backend
+
if (response?.toast) {
toast[response.toast.type](response.toast.message);
} else {
- onSuccess(t('fileManager.fileCreatedSuccessfully', { name: newFileName.trim() }));
+ onSuccess(t('fileManager.fileCreatedSuccessfully', {name: newFileName.trim()}));
}
-
+
setShowCreateFile(false);
setNewFileName('');
onOperationComplete();
} catch (error: any) {
- // Dismiss loading toast and show error
toast.dismiss(loadingToast);
onError(error?.response?.data?.error || t('fileManager.failedToCreateFile'));
} finally {
@@ -144,31 +135,27 @@ export function FileManagerOperations({
if (!newFolderName.trim() || !sshSessionId) return;
setIsLoading(true);
-
- // Show loading toast
+
const {toast} = await import('sonner');
- const loadingToast = toast.loading(t('fileManager.creatingFolder', { name: newFolderName.trim() }));
-
+ const loadingToast = toast.loading(t('fileManager.creatingFolder', {name: newFolderName.trim()}));
+
try {
const {createSSHFolder} = await import('@/ui/main-axios.ts');
const response = await createSSHFolder(sshSessionId, currentPath, newFolderName.trim());
-
- // Dismiss loading toast
+
toast.dismiss(loadingToast);
-
- // Handle toast notification from backend
+
if (response?.toast) {
toast[response.toast.type](response.toast.message);
} else {
- onSuccess(t('fileManager.folderCreatedSuccessfully', { name: newFolderName.trim() }));
+ onSuccess(t('fileManager.folderCreatedSuccessfully', {name: newFolderName.trim()}));
}
-
+
setShowCreateFolder(false);
setNewFolderName('');
onOperationComplete();
} catch (error: any) {
- // Dismiss loading toast and show error
toast.dismiss(loadingToast);
onError(error?.response?.data?.error || t('fileManager.failedToCreateFolder'));
} finally {
@@ -180,35 +167,31 @@ export function FileManagerOperations({
if (!deletePath || !sshSessionId) return;
setIsLoading(true);
-
- // Show loading toast
+
const {toast} = await import('sonner');
- const loadingToast = toast.loading(t('fileManager.deletingItem', {
+ const loadingToast = toast.loading(t('fileManager.deletingItem', {
type: deleteIsDirectory ? t('fileManager.folder') : t('fileManager.file'),
name: deletePath.split('/').pop()
}));
-
+
try {
const {deleteSSHItem} = await import('@/ui/main-axios.ts');
const response = await deleteSSHItem(sshSessionId, deletePath, deleteIsDirectory);
-
- // Dismiss loading toast
+
toast.dismiss(loadingToast);
-
- // Handle toast notification from backend
+
if (response?.toast) {
toast[response.toast.type](response.toast.message);
} else {
- onSuccess(t('fileManager.itemDeletedSuccessfully', { type: deleteIsDirectory ? t('fileManager.folder') : t('fileManager.file') }));
+ onSuccess(t('fileManager.itemDeletedSuccessfully', {type: deleteIsDirectory ? t('fileManager.folder') : t('fileManager.file')}));
}
-
+
setShowDelete(false);
setDeletePath('');
setDeleteIsDirectory(false);
onOperationComplete();
} catch (error: any) {
- // Dismiss loading toast and show error
toast.dismiss(loadingToast);
onError(error?.response?.data?.error || t('fileManager.failedToDeleteItem'));
} finally {
@@ -220,37 +203,33 @@ export function FileManagerOperations({
if (!renamePath || !newName.trim() || !sshSessionId) return;
setIsLoading(true);
-
- // Show loading toast
+
const {toast} = await import('sonner');
- const loadingToast = toast.loading(t('fileManager.renamingItem', {
+ const loadingToast = toast.loading(t('fileManager.renamingItem', {
type: renameIsDirectory ? t('fileManager.folder') : t('fileManager.file'),
oldName: renamePath.split('/').pop(),
newName: newName.trim()
}));
-
+
try {
const {renameSSHItem} = await import('@/ui/main-axios.ts');
const response = await renameSSHItem(sshSessionId, renamePath, newName.trim());
-
- // Dismiss loading toast
+
toast.dismiss(loadingToast);
-
- // Handle toast notification from backend
+
if (response?.toast) {
toast[response.toast.type](response.toast.message);
} else {
- onSuccess(t('fileManager.itemRenamedSuccessfully', { type: renameIsDirectory ? t('fileManager.folder') : t('fileManager.file') }));
+ onSuccess(t('fileManager.itemRenamedSuccessfully', {type: renameIsDirectory ? t('fileManager.folder') : t('fileManager.file')}));
}
-
+
setShowRename(false);
setRenamePath('');
setRenameIsDirectory(false);
setNewName('');
onOperationComplete();
} catch (error: any) {
- // Dismiss loading toast and show error
toast.dismiss(loadingToast);
onError(error?.response?.data?.error || t('fileManager.failedToRenameItem'));
} finally {
@@ -577,7 +556,8 @@ export function FileManagerOperations({
-
{t('fileManager.warningCannotUndo')}
+
{t('fileManager.warningCannotUndo')}
diff --git a/src/ui/Desktop/Apps/Host Manager/HostManager.tsx b/src/ui/Desktop/Apps/Host Manager/HostManager.tsx
index 4eb426e4..fa8a8ea9 100644
--- a/src/ui/Desktop/Apps/Host Manager/HostManager.tsx
+++ b/src/ui/Desktop/Apps/Host Manager/HostManager.tsx
@@ -7,7 +7,7 @@ import {CredentialsManager} from "@/ui/Desktop/Apps/Credentials/CredentialsManag
import {CredentialEditor} from "@/ui/Desktop/Apps/Credentials/CredentialEditor.tsx";
import {useSidebar} from "@/components/ui/sidebar.tsx";
import {useTranslation} from "react-i18next";
-import type { SSHHost, HostManagerProps } from '../../../types/index';
+import type {SSHHost, HostManagerProps} from '../../../types/index';
export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): React.ReactElement {
const {t} = useTranslation();
@@ -40,7 +40,6 @@ export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): Rea
const handleTabChange = (value: string) => {
setActiveTab(value);
- // Reset editing states when switching away from edit tabs
if (value !== "add_host") {
setEditingHost(null);
}
@@ -95,7 +94,7 @@ export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): Rea
-
+
diff --git a/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx b/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx
index f4d3e35f..44889159 100644
--- a/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx
+++ b/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx
@@ -3,15 +3,7 @@ import {Controller, useForm} from "react-hook-form"
import {z} from "zod"
import {Button} from "@/components/ui/button.tsx"
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form.tsx";
+import {Form, FormControl, FormDescription, FormField, FormItem, FormLabel,} from "@/components/ui/form.tsx";
import {Input} from "@/components/ui/input.tsx";
import {PasswordInput} from "@/components/ui/password-input.tsx";
import {ScrollArea} from "@/components/ui/scroll-area.tsx"
@@ -21,7 +13,7 @@ import React, {useEffect, useRef, useState} from "react";
import {Switch} from "@/components/ui/switch.tsx";
import {Alert, AlertDescription} from "@/components/ui/alert.tsx";
import {toast} from "sonner";
-import {createSSHHost, updateSSHHost, getSSHHosts, getCredentials} from '@/ui/main-axios.ts';
+import {createSSHHost, getCredentials, getSSHHosts, updateSSHHost} from '@/ui/main-axios.ts';
import {useTranslation} from "react-i18next";
import {CredentialSelector} from "@/ui/Desktop/Apps/Credentials/CredentialSelector.tsx";
@@ -65,8 +57,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
const [authTab, setAuthTab] = useState<'password' | 'key' | 'credential'>('password');
const [keyInputMethod, setKeyInputMethod] = useState<'upload' | 'paste'>('upload');
const isSubmittingRef = useRef(false);
-
- // Ref for the IP address input to manage focus
+
const ipInputRef = useRef(null);
useEffect(() => {
@@ -103,7 +94,6 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
fetchData();
}, []);
- // Listen for credential changes to refresh the credential list
useEffect(() => {
const handleCredentialChange = async () => {
try {
@@ -126,14 +116,13 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
setFolders(uniqueFolders);
setSshConfigurations(uniqueConfigurations);
} catch (error) {
- // Handle error silently
} finally {
setLoading(false);
}
};
-
+
window.addEventListener('credentials:changed', handleCredentialChange);
-
+
return () => {
window.removeEventListener('credentials:changed', handleCredentialChange);
};
@@ -247,7 +236,6 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
}
});
- // Update username when switching to credential tab and a credential is selected
useEffect(() => {
if (authTab === 'credential') {
const currentCredentialId = form.getValues('credentialId');
@@ -262,7 +250,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
useEffect(() => {
if (editingHost) {
- const cleanedHost = { ...editingHost };
+ const cleanedHost = {...editingHost};
if (cleanedHost.credentialId && cleanedHost.key) {
cleanedHost.key = undefined;
cleanedHost.keyPassword = undefined;
@@ -272,10 +260,10 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
} else if (cleanedHost.key && cleanedHost.password) {
cleanedHost.password = undefined;
}
-
+
const defaultAuthType = cleanedHost.credentialId ? 'credential' : (cleanedHost.key ? 'key' : 'password');
setAuthTab(defaultAuthType);
-
+
const formData = {
name: cleanedHost.name || "",
ip: cleanedHost.ip || "",
@@ -296,12 +284,11 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
defaultPath: cleanedHost.defaultPath || "/",
tunnelConnections: cleanedHost.tunnelConnections || [],
};
-
- // Only set the relevant authentication fields based on authType
+
if (defaultAuthType === 'password') {
formData.password = cleanedHost.password || "";
} else if (defaultAuthType === 'key') {
- formData.key = "existing_key"; // Placeholder to indicate existing key
+ formData.key = "existing_key";
formData.keyPassword = cleanedHost.keyPassword || "";
formData.keyType = (cleanedHost.keyType as any) || "auto";
} else if (defaultAuthType === 'credential') {
@@ -349,7 +336,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
const onSubmit = async (data: FormData) => {
try {
isSubmittingRef.current = true;
-
+
if (!data.name || data.name.trim() === '') {
data.name = `${data.username}@${data.ip}`;
}
@@ -399,23 +386,22 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
if (editingHost) {
const updatedHost = await updateSSHHost(editingHost.id, submitData);
- toast.success(t('hosts.hostUpdatedSuccessfully', { name: data.name }));
-
+ toast.success(t('hosts.hostUpdatedSuccessfully', {name: data.name}));
+
if (onFormSubmit) {
onFormSubmit(updatedHost);
}
} else {
const newHost = await createSSHHost(submitData);
- toast.success(t('hosts.hostAddedSuccessfully', { name: data.name }));
-
+ toast.success(t('hosts.hostAddedSuccessfully', {name: data.name}));
+
if (onFormSubmit) {
onFormSubmit(newHost);
}
}
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
-
- // Reset form after successful submission
+
form.reset();
} catch (error) {
toast.error(t('hosts.failedToSaveHost'));
@@ -574,8 +560,8 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
{t('hosts.ipAddress')}
- {
field.ref(e);
@@ -745,8 +731,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
const newAuthType = value as 'password' | 'key' | 'credential';
setAuthTab(newAuthType);
form.setValue('authType', newAuthType);
-
- // Clear authentication fields based on what we're switching away from
+
if (newAuthType === 'password') {
form.setValue('key', null);
form.setValue('keyPassword', '');
@@ -773,11 +758,12 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
(
+ render={({field}) => (
{t('hosts.password')}
-
+
)}
@@ -788,7 +774,6 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
value={keyInputMethod}
onValueChange={(value) => {
setKeyInputMethod(value as 'upload' | 'paste');
- // Clear the other field when switching
if (value === 'upload') {
form.setValue('key', null);
} else {
@@ -797,7 +782,8 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
}}
className="w-full"
>
-
+
{t('hosts.uploadFile')}
{t('hosts.pasteKey')}
@@ -827,8 +813,8 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
>
- {field.value === "existing_key" ? t('hosts.existingKey') :
- field.value ? (editingHost ? t('hosts.updateKey') : field.value.name) : t('hosts.upload')}
+ {field.value === "existing_key" ? t('hosts.existingKey') :
+ field.value ? (editingHost ? t('hosts.updateKey') : field.value.name) : t('hosts.upload')}
@@ -856,7 +842,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
)}
/>
-
+
(
+ render={({field}) => (
{
if (credential) {
- // Update username when credential is selected
form.setValue('username', credential.username);
}
}}
@@ -1002,7 +987,8 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
sshpass or sudo dnf install
sshpass
-
• {t('hosts.macos')}
brew
+ • {t('hosts.macos')} brew
install hudochenkov/sshpass/sshpass
• {t('hosts.windows')}
@@ -1026,9 +1012,9 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
- window.open('https://docs.termix.site/tunnels', '_blank')}
>
@@ -1148,7 +1134,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
- {t('hosts.tunnelForwardDescription', {
+ {t('hosts.tunnelForwardDescription', {
sourcePort: form.watch(`tunnelConnections.${index}.sourcePort`) || '22',
endpointPort: form.watch(`tunnelConnections.${index}.endpointPort`) || '224'
})}
diff --git a/src/ui/Desktop/Apps/Host Manager/HostManagerViewer.tsx b/src/ui/Desktop/Apps/Host Manager/HostManagerViewer.tsx
index 0b480961..dfb0a73d 100644
--- a/src/ui/Desktop/Apps/Host Manager/HostManagerViewer.tsx
+++ b/src/ui/Desktop/Apps/Host Manager/HostManagerViewer.tsx
@@ -1,5 +1,4 @@
import React, {useState, useEffect, useMemo, useRef} from "react";
-import {Card, CardContent} from "@/components/ui/card.tsx";
import {Button} from "@/components/ui/button.tsx";
import {Badge} from "@/components/ui/badge.tsx";
import {ScrollArea} from "@/components/ui/scroll-area.tsx";
@@ -22,14 +21,12 @@ import {
FileEdit,
Search,
Upload,
- Info,
X,
Check,
Pencil,
FolderMinus
} from "lucide-react";
-import {Separator} from "@/components/ui/separator.tsx";
-import type { SSHHost, SSHManagerHostViewerProps } from '../../../../types/index.js';
+import type {SSHHost, SSHManagerHostViewerProps} from '../../../../types/index.js';
export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
const {t} = useTranslation();
@@ -48,16 +45,15 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
useEffect(() => {
fetchHosts();
-
- // Listen for refresh events from other components
+
const handleHostsRefresh = () => {
fetchHosts();
};
-
+
window.addEventListener('hosts:refresh', handleHostsRefresh);
window.addEventListener('ssh-hosts:changed', handleHostsRefresh);
window.addEventListener('folders:changed', handleHostsRefresh);
-
+
return () => {
window.removeEventListener('hosts:refresh', handleHostsRefresh);
window.removeEventListener('ssh-hosts:changed', handleHostsRefresh);
@@ -69,9 +65,9 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
try {
setLoading(true);
const data = await getSSHHosts();
-
+
const cleanedHosts = data.map(host => {
- const cleanedHost = { ...host };
+ const cleanedHost = {...host};
if (cleanedHost.credentialId && cleanedHost.key) {
cleanedHost.key = undefined;
cleanedHost.keyPassword = undefined;
@@ -86,7 +82,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
}
return cleanedHost;
});
-
+
setHosts(cleanedHosts);
setError(null);
} catch (err) {
@@ -98,11 +94,11 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
const handleDelete = async (hostId: number, hostName: string) => {
confirmWithToast(
- t('hosts.confirmDelete', { name: hostName }),
+ t('hosts.confirmDelete', {name: hostName}),
async () => {
try {
await deleteSSHHost(hostId);
- toast.success(t('hosts.hostDeletedSuccessfully', { name: hostName }));
+ toast.success(t('hosts.hostDeletedSuccessfully', {name: hostName}));
await fetchHosts();
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
} catch (err) {
@@ -115,41 +111,38 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
const handleExport = (host: SSHHost) => {
const actualAuthType = host.credentialId ? 'credential' : (host.key ? 'key' : 'password');
-
- // Check if host uses sensitive authentication data
+
if (actualAuthType === 'credential') {
- const confirmMessage = t('hosts.exportCredentialWarning', {
- name: host.name || `${host.username}@${host.ip}`
+ const confirmMessage = t('hosts.exportCredentialWarning', {
+ name: host.name || `${host.username}@${host.ip}`
});
-
+
confirmWithToast(confirmMessage, () => {
performExport(host, actualAuthType);
});
return;
} else if (actualAuthType === 'password' || actualAuthType === 'key') {
- const confirmMessage = t('hosts.exportSensitiveDataWarning', {
- name: host.name || `${host.username}@${host.ip}`
+ const confirmMessage = t('hosts.exportSensitiveDataWarning', {
+ name: host.name || `${host.username}@${host.ip}`
});
-
+
confirmWithToast(confirmMessage, () => {
performExport(host, actualAuthType);
});
return;
}
-
- // No sensitive data, proceed directly
+
performExport(host, actualAuthType);
};
const performExport = (host: SSHHost, actualAuthType: string) => {
- // Create export data with sensitive fields excluded
const exportData: any = {
name: host.name,
ip: host.ip,
port: host.port,
username: host.username,
- authType: actualAuthType, // Use the determined authType, not the stored one
+ authType: actualAuthType,
folder: host.folder,
tags: host.tags,
pin: host.pin,
@@ -160,18 +153,16 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
tunnelConnections: host.tunnelConnections,
};
- // Only include credentialId if actualAuthType is credential, but set it to null for security
if (actualAuthType === 'credential') {
- exportData.credentialId = null; // Set to null instead of undefined so it's included but empty
+ exportData.credentialId = null;
}
- // Remove undefined values from export, but keep null values
const cleanExportData = Object.fromEntries(
Object.entries(exportData).filter(([_, value]) => value !== undefined)
);
- const blob = new Blob([JSON.stringify(cleanExportData, null, 2)], { type: 'application/json' });
+ const blob = new Blob([JSON.stringify(cleanExportData, null, 2)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
@@ -193,13 +184,13 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
const handleRemoveFromFolder = async (host: SSHHost) => {
confirmWithToast(
- t('hosts.confirmRemoveFromFolder', { name: host.name || `${host.username}@${host.ip}`, folder: host.folder }),
+ t('hosts.confirmRemoveFromFolder', {name: host.name || `${host.username}@${host.ip}`, folder: host.folder}),
async () => {
try {
setOperationLoading(true);
- const updatedHost = { ...host, folder: '' };
+ const updatedHost = {...host, folder: ''};
await updateSSHHost(host.id, updatedHost);
- toast.success(t('hosts.removedFromFolder', { name: host.name || `${host.username}@${host.ip}` }));
+ toast.success(t('hosts.removedFromFolder', {name: host.name || `${host.username}@${host.ip}`}));
await fetchHosts();
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
} catch (err) {
@@ -221,7 +212,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
try {
setOperationLoading(true);
await renameFolder(oldName, editingFolderName.trim());
- toast.success(t('hosts.folderRenamed', { oldName, newName: editingFolderName.trim() }));
+ toast.success(t('hosts.folderRenamed', {oldName, newName: editingFolderName.trim()}));
await fetchHosts();
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
setEditingFolder(null);
@@ -243,11 +234,10 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
setEditingFolderName('');
};
- // Drag and drop handlers
const handleDragStart = (e: React.DragEvent, host: SSHHost) => {
setDraggedHost(host);
e.dataTransfer.effectAllowed = 'move';
- e.dataTransfer.setData('text/plain', ''); // Required for Firefox
+ e.dataTransfer.setData('text/plain', '');
};
const handleDragEnd = () => {
@@ -282,7 +272,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
if (!draggedHost) return;
const newFolder = targetFolder === t('hosts.uncategorized') ? '' : targetFolder;
-
+
if (draggedHost.folder === newFolder) {
setDraggedHost(null);
return;
@@ -290,11 +280,11 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
try {
setOperationLoading(true);
- const updatedHost = { ...draggedHost, folder: newFolder };
+ const updatedHost = {...draggedHost, folder: newFolder};
await updateSSHHost(draggedHost.id, updatedHost);
- toast.success(t('hosts.movedToFolder', {
+ toast.success(t('hosts.movedToFolder', {
name: draggedHost.name || `${draggedHost.username}@${draggedHost.ip}`,
- folder: targetFolder
+ folder: targetFolder
}));
await fetchHosts();
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
@@ -332,7 +322,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
const result = await bulkImportSSHHosts(hostsArray);
if (result.success > 0) {
- toast.success(t('hosts.importCompleted', { success: result.success, failed: result.failed }));
+ toast.success(t('hosts.importCompleted', {success: result.success, failed: result.failed}));
if (result.errors.length > 0) {
toast.error(`Import errors: ${result.errors.join(', ')}`);
}
@@ -436,7 +426,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
{t('hosts.sshHosts')}
- {t('hosts.hostsCount', { count: 0 })}
+ {t('hosts.hostsCount', {count: 0})}
@@ -469,66 +459,66 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
variant="outline"
size="sm"
onClick={() => {
- const sampleData = {
- hosts: [
- {
- name: "Web Server - Production",
- ip: "192.168.1.100",
- port: 22,
- username: "admin",
- authType: "password",
- password: "your_secure_password_here",
- folder: "Production",
- tags: ["web", "production", "nginx"],
- pin: true,
- enableTerminal: true,
- enableTunnel: false,
- enableFileManager: true,
- defaultPath: "/var/www"
- },
- {
- name: "Database Server",
- ip: "192.168.1.101",
- port: 22,
- username: "dbadmin",
- authType: "key",
- key: "-----BEGIN OPENSSH PRIVATE KEY-----\nYour SSH private key content here\n-----END OPENSSH PRIVATE KEY-----",
- keyPassword: "optional_key_passphrase",
- keyType: "ssh-ed25519",
- folder: "Production",
- tags: ["database", "production", "postgresql"],
- pin: false,
- enableTerminal: true,
- enableTunnel: true,
- enableFileManager: false,
- tunnelConnections: [
+ const sampleData = {
+ hosts: [
{
- sourcePort: 5432,
- endpointPort: 5432,
- endpointHost: "Web Server - Production",
- maxRetries: 3,
- retryInterval: 10,
- autoStart: true
+ name: "Web Server - Production",
+ ip: "192.168.1.100",
+ port: 22,
+ username: "admin",
+ authType: "password",
+ password: "your_secure_password_here",
+ folder: "Production",
+ tags: ["web", "production", "nginx"],
+ pin: true,
+ enableTerminal: true,
+ enableTunnel: false,
+ enableFileManager: true,
+ defaultPath: "/var/www"
+ },
+ {
+ name: "Database Server",
+ ip: "192.168.1.101",
+ port: 22,
+ username: "dbadmin",
+ authType: "key",
+ key: "-----BEGIN OPENSSH PRIVATE KEY-----\nYour SSH private key content here\n-----END OPENSSH PRIVATE KEY-----",
+ keyPassword: "optional_key_passphrase",
+ keyType: "ssh-ed25519",
+ folder: "Production",
+ tags: ["database", "production", "postgresql"],
+ pin: false,
+ enableTerminal: true,
+ enableTunnel: true,
+ enableFileManager: false,
+ tunnelConnections: [
+ {
+ sourcePort: 5432,
+ endpointPort: 5432,
+ endpointHost: "Web Server - Production",
+ maxRetries: 3,
+ retryInterval: 10,
+ autoStart: true
+ }
+ ]
+ },
+ {
+ name: "Development Server",
+ ip: "192.168.1.102",
+ port: 2222,
+ username: "developer",
+ authType: "credential",
+ credentialId: 1,
+ folder: "Development",
+ tags: ["dev", "testing"],
+ pin: false,
+ enableTerminal: true,
+ enableTunnel: false,
+ enableFileManager: true,
+ defaultPath: "/home/developer"
}
]
- },
- {
- name: "Development Server",
- ip: "192.168.1.102",
- port: 2222,
- username: "developer",
- authType: "credential",
- credentialId: 1,
- folder: "Development",
- tags: ["dev", "testing"],
- pin: false,
- enableTerminal: true,
- enableTunnel: false,
- enableFileManager: true,
- defaultPath: "/home/developer"
- }
- ]
- };
+ };
const blob = new Blob([JSON.stringify(sampleData, null, 2)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
@@ -590,7 +580,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
{t('hosts.sshHosts')}
- {t('hosts.hostsCount', { count: filteredAndSortedHosts.length })}
+ {t('hosts.hostsCount', {count: filteredAndSortedHosts.length})}
@@ -738,8 +728,8 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
{Object.entries(hostsByFolder).map(([folder, folderHosts]) => (
-
{editingFolder === folder ? (
-
e.stopPropagation()}>
+
e.stopPropagation()}>
setEditingFolderName(e.target.value)}
@@ -794,8 +785,8 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
) : (
<>
-
{
e.stopPropagation();
if (folder !== t('hosts.uncategorized')) {
@@ -851,143 +842,149 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
{host.name || `${host.username}@${host.ip}`}
-
- {host.ip}:{host.port}
-
-
- {host.username}
-
-
-
- {host.folder && host.folder !== '' && (
-
-
- {
- e.stopPropagation();
- handleRemoveFromFolder(host);
- }}
- className="h-5 w-5 p-0 text-orange-500 hover:text-orange-700 hover:bg-orange-500/10"
- disabled={operationLoading}
- >
-
-
-
-
- Remove from folder "{host.folder}"
-
-
- )}
-
-
- {
- e.stopPropagation();
- handleEdit(host);
- }}
- className="h-5 w-5 p-0 hover:bg-blue-500/10"
- >
-
-
-
-
- Edit host
-
-
-
-
- {
- e.stopPropagation();
- handleDelete(host.id, host.name || `${host.username}@${host.ip}`);
- }}
- className="h-5 w-5 p-0 text-red-500 hover:text-red-700 hover:bg-red-500/10"
- >
-
-
-
-
- Delete host
-
-
-
-
- {
- e.stopPropagation();
- handleExport(host);
- }}
- className="h-5 w-5 p-0 text-blue-500 hover:text-blue-700 hover:bg-blue-500/10"
- >
-
-
-
-
- Export host
-
-
+
+ {host.ip}:{host.port}
+
+
+ {host.username}
+
+
+
+ {host.folder && host.folder !== '' && (
+
+
+ {
+ e.stopPropagation();
+ handleRemoveFromFolder(host);
+ }}
+ className="h-5 w-5 p-0 text-orange-500 hover:text-orange-700 hover:bg-orange-500/10"
+ disabled={operationLoading}
+ >
+
+
+
+
+ Remove from folder
+ "{host.folder}"
+
+
+ )}
+
+
+ {
+ e.stopPropagation();
+ handleEdit(host);
+ }}
+ className="h-5 w-5 p-0 hover:bg-blue-500/10"
+ >
+
+
+
+
+ Edit host
+
+
+
+
+ {
+ e.stopPropagation();
+ handleDelete(host.id, host.name || `${host.username}@${host.ip}`);
+ }}
+ className="h-5 w-5 p-0 text-red-500 hover:text-red-700 hover:bg-red-500/10"
+ >
+
+
+
+
+ Delete host
+
+
+
+
+ {
+ e.stopPropagation();
+ handleExport(host);
+ }}
+ className="h-5 w-5 p-0 text-blue-500 hover:text-blue-700 hover:bg-blue-500/10"
+ >
+
+
+
+
+ Export host
+
+
-
-
+
+
-
- {host.tags && host.tags.length > 0 && (
-
- {host.tags.slice(0, 6).map((tag, index) => (
-
-
- {tag}
-
- ))}
- {host.tags.length > 6 && (
-
- +{host.tags.length - 6}
-
- )}
-
- )}
-
-
- {host.enableTerminal && (
-
-
- {t('hosts.terminalBadge')}
-
- )}
- {host.enableTunnel && (
-
-
- {t('hosts.tunnelBadge')}
- {host.tunnelConnections && host.tunnelConnections.length > 0 && (
- ({host.tunnelConnections.length})
+
+ {host.tags && host.tags.length > 0 && (
+
+ {host.tags.slice(0, 6).map((tag, index) => (
+
+
+ {tag}
+
+ ))}
+ {host.tags.length > 6 && (
+
+ +{host.tags.length - 6}
+
+ )}
+
)}
-
- )}
- {host.enableFileManager && (
-
-
- {t('hosts.fileManagerBadge')}
-
- )}
-
-
-
-
+
+
+ {host.enableTerminal && (
+
+
+ {t('hosts.terminalBadge')}
+
+ )}
+ {host.enableTunnel && (
+
+
+ {t('hosts.tunnelBadge')}
+ {host.tunnelConnections && host.tunnelConnections.length > 0 && (
+ ({host.tunnelConnections.length})
+ )}
+
+ )}
+ {host.enableFileManager && (
+
+
+ {t('hosts.fileManagerBadge')}
+
+ )}
+
+