From f93f50705bac85e61d8486ab2490ef9a99997915 Mon Sep 17 00:00:00 2001 From: thorved <54140516+thorved@users.noreply.github.com> Date: Sat, 4 Oct 2025 11:28:27 +0530 Subject: [PATCH 1/2] Adds camelCase support for encrypted field mappings Extends encrypted field mappings to include camelCase variants to support consistency and compatibility with different naming conventions. Updates reverse mappings for Drizzle ORM to allow conversion between camelCase and snake_case field names. Improves integration with systems using mixed naming styles. --- src/backend/utils/field-crypto.ts | 15 ++++++++++++++- src/backend/utils/lazy-field-encryption.ts | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/field-crypto.ts b/src/backend/utils/field-crypto.ts index 098b5b8e..2be3935e 100644 --- a/src/backend/utils/field-crypto.ts +++ b/src/backend/utils/field-crypto.ts @@ -17,18 +17,31 @@ class FieldCrypto { private static readonly ENCRYPTED_FIELDS = { users: new Set([ "password_hash", + "passwordHash", "client_secret", + "clientSecret", "totp_secret", + "totpSecret", "totp_backup_codes", + "totpBackupCodes", "oidc_identifier", + "oidcIdentifier", + ]), + ssh_data: new Set([ + "password", + "key", + "key_password", + "keyPassword", ]), - ssh_data: new Set(["password", "key", "key_password"]), ssh_credentials: new Set([ "password", "private_key", + "privateKey", "key_password", + "keyPassword", "key", "public_key", + "publicKey", ]), }; diff --git a/src/backend/utils/lazy-field-encryption.ts b/src/backend/utils/lazy-field-encryption.ts index 8eae9193..06c43d8c 100644 --- a/src/backend/utils/lazy-field-encryption.ts +++ b/src/backend/utils/lazy-field-encryption.ts @@ -6,6 +6,10 @@ export class LazyFieldEncryption { key_password: "keyPassword", private_key: "privateKey", public_key: "publicKey", + // Reverse mappings for Drizzle ORM (camelCase -> snake_case) + keyPassword: "key_password", + privateKey: "private_key", + publicKey: "public_key", }; static isPlaintextField(value: string): boolean { -- 2.49.1 From 8aa2ee67ae0cab911da75db438061a9df9117bd5 Mon Sep 17 00:00:00 2001 From: Ved Prakash <54140516+thorved@users.noreply.github.com> Date: Sun, 5 Oct 2025 05:38:55 +0530 Subject: [PATCH 2/2] Feature request: Add delete confirmation dialog to file manager (#344) * Feature request: Add delete confirmation dialog to file manager - Added confirmation dialog before deleting files/folders - Users must confirm deletion with a warning message - Works for both Delete key and right-click delete - Shows different messages for single file, folder, or multiple items - Includes permanent deletion warning - Follows existing design patterns using confirmWithToast * Adds confirmation for deletion of items including folders Updates the file deletion confirmation logic to distinguish between deleting multiple items with or without folders. Introduces a new translation string for a clearer user prompt when folders and their contents are included in the deletion. Improves clarity and reduces user error when performing bulk deletions. * feat: Add Chinese translations for delete confirmation messages --- src/locales/en/translation.json | 5 + src/locales/zh/translation.json | 5 + .../Desktop/Apps/File Manager/FileManager.tsx | 126 +++++++++++------- 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 0e6d735e..d3aff79d 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -844,8 +844,13 @@ "selectServerToEdit": "Select a server from the sidebar to start editing files", "fileOperations": "File Operations", "confirmDeleteMessage": "Are you sure you want to delete {{name}}?", + "confirmDeleteSingleItem": "Are you sure you want to permanently delete \"{{name}}\"?", + "confirmDeleteMultipleItems": "Are you sure you want to permanently delete {{count}} items?", + "confirmDeleteMultipleItemsWithFolders": "Are you sure you want to permanently delete {{count}} items? This includes folders and their contents.", + "confirmDeleteFolder": "Are you sure you want to permanently delete the folder \"{{name}}\" and all its contents?", "deleteDirectoryWarning": "This will delete the folder and all its contents.", "actionCannotBeUndone": "This action cannot be undone.", + "permanentDeleteWarning": "This action cannot be undone. The item(s) will be permanently deleted from the server.", "recent": "Recent", "pinned": "Pinned", "folderShortcuts": "Folder Shortcuts", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 62069e11..0cd92395 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -852,8 +852,13 @@ "selectServerToEdit": "从侧边栏选择服务器以开始编辑文件", "fileOperations": "文件操作", "confirmDeleteMessage": "确定要删除 {{name}} 吗?", + "confirmDeleteSingleItem": "确定要永久删除 \"{{name}}\" 吗?", + "confirmDeleteMultipleItems": "确定要永久删除 {{count}} 个项目吗?", + "confirmDeleteMultipleItemsWithFolders": "确定要永久删除 {{count}} 个项目吗?这包括文件夹及其内容。", + "confirmDeleteFolder": "确定要永久删除文件夹 \"{{name}}\" 及其所有内容吗?", "deleteDirectoryWarning": "这将删除文件夹及其所有内容。", "actionCannotBeUndone": "此操作无法撤销。", + "permanentDeleteWarning": "此操作无法撤销。项目将从服务器永久删除。", "dragSystemFilesToUpload": "拖拽系统文件到此处上传", "dragFilesToWindowToDownload": "拖拽文件到窗口外下载", "openTerminalHere": "在此处打开终端", diff --git a/src/ui/Desktop/Apps/File Manager/FileManager.tsx b/src/ui/Desktop/Apps/File Manager/FileManager.tsx index 0f05c589..73ac9bc6 100644 --- a/src/ui/Desktop/Apps/File Manager/FileManager.tsx +++ b/src/ui/Desktop/Apps/File Manager/FileManager.tsx @@ -9,6 +9,7 @@ import { FileWindow } from "./components/FileWindow"; import { DiffWindow } from "./components/DiffWindow"; import { useDragToDesktop } from "../../../hooks/useDragToDesktop"; import { useDragToSystemDesktop } from "../../../hooks/useDragToSystemDesktop"; +import { useConfirmation } from "@/hooks/use-confirmation.ts"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; @@ -82,6 +83,7 @@ function formatFileSize(bytes?: number): string { function FileManagerContent({ initialHost, onClose }: FileManagerProps) { const { openWindow } = useWindowManager(); const { t } = useTranslation(); + const { confirmWithToast } = useConfirmation(); const [currentHost, setCurrentHost] = useState( initialHost || null, @@ -587,54 +589,88 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { async function handleDeleteFiles(files: FileItem[]) { if (!sshSessionId || files.length === 0) return; - try { - await ensureSSHConnection(); - - for (const file of files) { - await deleteSSHItem( - sshSessionId, - file.path, - file.type === "directory", - currentHost?.id, - currentHost?.userId?.toString(), - ); - } - - const deletedFiles = files.map((file) => ({ - path: file.path, - name: file.name, - })); - - const undoAction: UndoAction = { - type: "delete", - description: t("fileManager.deletedItems", { count: files.length }), - data: { - operation: "cut", - deletedFiles, - targetDirectory: currentPath, - }, - timestamp: Date.now(), - }; - setUndoHistory((prev) => [...prev.slice(-9), undoAction]); - - toast.success( - t("fileManager.itemsDeletedSuccessfully", { count: files.length }), - ); - handleRefreshDirectory(); - clearSelection(); - } catch (error: any) { - if ( - error.message?.includes("connection") || - error.message?.includes("established") - ) { - toast.error( - `SSH connection failed. Please check your connection to ${currentHost?.name} (${currentHost?.ip}:${currentHost?.port})`, - ); + // Determine the confirmation message based on file count and type + let confirmMessage: string; + if (files.length === 1) { + const file = files[0]; + if (file.type === "directory") { + confirmMessage = t("fileManager.confirmDeleteFolder", { + name: file.name, + }); } else { - toast.error(t("fileManager.failedToDeleteItems")); + confirmMessage = t("fileManager.confirmDeleteSingleItem", { + name: file.name, + }); } - console.error("Delete failed:", error); + } else { + const hasDirectory = files.some((file) => file.type === "directory"); + const translationKey = hasDirectory + ? "fileManager.confirmDeleteMultipleItemsWithFolders" + : "fileManager.confirmDeleteMultipleItems"; + + confirmMessage = t(translationKey, { + count: files.length, + }); } + + // Add permanent deletion warning + const fullMessage = `${confirmMessage}\n\n${t("fileManager.permanentDeleteWarning")}`; + + // Show confirmation dialog + confirmWithToast( + fullMessage, + async () => { + try { + await ensureSSHConnection(); + + for (const file of files) { + await deleteSSHItem( + sshSessionId, + file.path, + file.type === "directory", + currentHost?.id, + currentHost?.userId?.toString(), + ); + } + + const deletedFiles = files.map((file) => ({ + path: file.path, + name: file.name, + })); + + const undoAction: UndoAction = { + type: "delete", + description: t("fileManager.deletedItems", { count: files.length }), + data: { + operation: "cut", + deletedFiles, + targetDirectory: currentPath, + }, + timestamp: Date.now(), + }; + setUndoHistory((prev) => [...prev.slice(-9), undoAction]); + + toast.success( + t("fileManager.itemsDeletedSuccessfully", { count: files.length }), + ); + handleRefreshDirectory(); + clearSelection(); + } catch (error: any) { + if ( + error.message?.includes("connection") || + error.message?.includes("established") + ) { + toast.error( + `SSH connection failed. Please check your connection to ${currentHost?.name} (${currentHost?.ip}:${currentHost?.port})`, + ); + } else { + toast.error(t("fileManager.failedToDeleteItems")); + } + console.error("Delete failed:", error); + } + }, + "destructive", + ); } function handleCreateNewFolder() { -- 2.49.1