diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index 3d633dea..fe780ebe 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -84,6 +84,10 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; } location ~ ^/database(/.*)?$ { diff --git a/docker/nginx.conf b/docker/nginx.conf index d31ea155..19583dee 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -72,6 +72,10 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; } location ~ ^/database(/.*)?$ { diff --git a/src/backend/database/routes/credentials.ts b/src/backend/database/routes/credentials.ts index fcb0f936..ac022f87 100644 --- a/src/backend/database/routes/credentials.ts +++ b/src/backend/database/routes/credentials.ts @@ -1217,7 +1217,7 @@ async function deploySSHKeyToHost( await new Promise((resolveAdd, rejectAdd) => { const addTimeout = setTimeout(() => { rejectAdd(new Error("Key add timeout")); - }, 10000); + }, 30000); let actualPublicKey = publicKey; try { diff --git a/src/ui/Desktop/Apps/File Manager/FileManager.tsx b/src/ui/Desktop/Apps/File Manager/FileManager.tsx index faa8a036..1eb0a41f 100644 --- a/src/ui/Desktop/Apps/File Manager/FileManager.tsx +++ b/src/ui/Desktop/Apps/File Manager/FileManager.tsx @@ -86,7 +86,9 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { const [currentHost, setCurrentHost] = useState( initialHost || null, ); - const [currentPath, setCurrentPath] = useState("/"); + const [currentPath, setCurrentPath] = useState( + initialHost?.defaultPath || "/" + ); const [files, setFiles] = useState([]); const [isLoading, setIsLoading] = useState(false); const [sshSessionId, setSshSessionId] = useState(null); @@ -754,8 +756,14 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { await recordRecentFile(file); const windowCount = Date.now() % 10; - const offsetX = 120 + windowCount * 30; - const offsetY = 120 + windowCount * 30; + const baseOffsetX = 120 + windowCount * 30; + const baseOffsetY = 120 + windowCount * 30; + + const maxOffsetX = Math.max(0, window.innerWidth - 800 - 100); + const maxOffsetY = Math.max(0, window.innerHeight - 600 - 100); + + const offsetX = Math.min(baseOffsetX, maxOffsetX); + const offsetY = Math.min(baseOffsetY, maxOffsetY); const windowTitle = file.name; diff --git a/src/ui/Desktop/Apps/File Manager/components/DraggableWindow.tsx b/src/ui/Desktop/Apps/File Manager/components/DraggableWindow.tsx index 99e12a9d..d214ab93 100644 --- a/src/ui/Desktop/Apps/File Manager/components/DraggableWindow.tsx +++ b/src/ui/Desktop/Apps/File Manager/components/DraggableWindow.tsx @@ -332,7 +332,7 @@ export function DraggableWindow({
{children} diff --git a/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx b/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx index dbd1d339..46185ccd 100644 --- a/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx +++ b/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx @@ -90,6 +90,7 @@ interface FileViewerProps { isEditable?: boolean; onContentChange?: (content: string) => void; onSave?: (content: string) => void; + onRevert?: () => void; onDownload?: () => void; onMediaDimensionsChange?: (dimensions: { width: number; @@ -304,6 +305,7 @@ export function FileViewer({ isEditable = false, onContentChange, onSave, + onRevert, onDownload, onMediaDimensionsChange, }: FileViewerProps) { @@ -352,7 +354,12 @@ export function FileViewer({ } else { setShowLargeFileWarning(false); } - }, [content, savedContent, fileTypeInfo.type, isLargeFile, forceShowAsText]); + + if (fileTypeInfo.type === "image" && file.name.toLowerCase().endsWith('.svg') && content) { + setImageLoading(false); + setImageLoadError(false); + } + }, [content, savedContent, fileTypeInfo.type, isLargeFile, forceShowAsText, file.name]); const handleContentChange = (newContent: string) => { setEditedContent(newContent); @@ -365,9 +372,12 @@ export function FileViewer({ }; const handleRevert = () => { - setEditedContent(savedContent); - setHasChanges(false); - onContentChange?.(savedContent); + if (onRevert) { + onRevert(); + } else { + setEditedContent(savedContent); + setHasChanges(false); + } }; useEffect(() => { @@ -696,6 +706,16 @@ export function FileViewer({ )}
+ ) : file.name.toLowerCase().endsWith('.svg') ? ( +
{ + setImageLoading(false); + setImageLoadError(false); + }} + /> ) : ( diff --git a/src/ui/Desktop/Apps/File Manager/components/FileWindow.tsx b/src/ui/Desktop/Apps/File Manager/components/FileWindow.tsx index b45b0b81..cd18e164 100644 --- a/src/ui/Desktop/Apps/File Manager/components/FileWindow.tsx +++ b/src/ui/Desktop/Apps/File Manager/components/FileWindow.tsx @@ -211,6 +211,37 @@ export function FileWindow({ loadFileContent(); }, [file, sshSessionId, sshHost]); + const handleRevert = async () => { + const loadFileContent = async () => { + if (file.type !== "file") return; + + try { + setIsLoading(true); + + await ensureSSHConnection(); + + const response = await readSSHFile(sshSessionId, file.path); + const fileContent = response.content || ""; + setContent(fileContent); + setPendingContent(""); + + if (!file.size) { + const contentSize = new Blob([fileContent]).size; + file.size = contentSize; + } + } catch (error: any) { + console.error("Failed to load file content:", error); + toast.error( + `${t("fileManager.failedToLoadFile")}: ${error.message || t("fileManager.unknownError")}`, + ); + } finally { + setIsLoading(false); + } + }; + + loadFileContent(); + }; + const handleSave = async (newContent: string) => { try { setIsLoading(true); @@ -252,17 +283,20 @@ export function FileWindow({ if (autoSaveTimerRef.current) { clearTimeout(autoSaveTimerRef.current); + autoSaveTimerRef.current = null; } - autoSaveTimerRef.current = setTimeout(async () => { - try { - await handleSave(newContent); - toast.success(t("fileManager.fileAutoSaved")); - } catch (error) { - console.error("Auto-save failed:", error); - toast.error(t("fileManager.autoSaveFailed")); - } - }, 60000); + if (newContent !== content) { + autoSaveTimerRef.current = setTimeout(async () => { + try { + await handleSave(newContent); + toast.success(t("fileManager.fileAutoSaved")); + } catch (error) { + console.error("Auto-save failed:", error); + toast.error(t("fileManager.autoSaveFailed")); + } + }, 60000); + } }; useEffect(() => { @@ -363,6 +397,7 @@ export function FileWindow({ content={pendingContent || content} savedContent={content} isLoading={isLoading} + onRevert={handleRevert} isEditable={isEditable} onContentChange={handleContentChange} onSave={(newContent) => handleSave(newContent)} diff --git a/src/ui/Desktop/Apps/File Manager/components/TerminalWindow.tsx b/src/ui/Desktop/Apps/File Manager/components/TerminalWindow.tsx index e5a62564..6069e8a7 100644 --- a/src/ui/Desktop/Apps/File Manager/components/TerminalWindow.tsx +++ b/src/ui/Desktop/Apps/File Manager/components/TerminalWindow.tsx @@ -79,7 +79,6 @@ export function TerminalWindow({ minWidth={600} minHeight={400} onClose={handleClose} - onMinimize={handleMinimize} onMaximize={handleMaximize} onFocus={handleFocus} isMaximized={currentWindow.isMaximized} diff --git a/src/ui/Desktop/Apps/File Manager/components/WindowManager.tsx b/src/ui/Desktop/Apps/File Manager/components/WindowManager.tsx index afaee80b..ce200d64 100644 --- a/src/ui/Desktop/Apps/File Manager/components/WindowManager.tsx +++ b/src/ui/Desktop/Apps/File Manager/components/WindowManager.tsx @@ -40,9 +40,15 @@ export function WindowManager({ children }: WindowManagerProps) { const id = `window-${++windowCounter.current}`; const zIndex = ++nextZIndex.current; - const offset = (windows.length % 5) * 30; - const adjustedX = windowData.x + offset; - const adjustedY = windowData.y + offset; + const offset = (windows.length % 5) * 20; + let adjustedX = windowData.x + offset; + let adjustedY = windowData.y + offset; + + const maxX = Math.max(0, window.innerWidth - windowData.width - 20); + const maxY = Math.max(0, window.innerHeight - windowData.height - 20); + + adjustedX = Math.max(20, Math.min(adjustedX, maxX)); + adjustedY = Math.max(20, Math.min(adjustedY, maxY)); const newWindow: WindowInstance = { ...windowData, diff --git a/src/ui/Desktop/Apps/File Manager/hooks/useFileSelection.ts b/src/ui/Desktop/Apps/File Manager/hooks/useFileSelection.ts index eaf88115..0d0a8152 100644 --- a/src/ui/Desktop/Apps/File Manager/hooks/useFileSelection.ts +++ b/src/ui/Desktop/Apps/File Manager/hooks/useFileSelection.ts @@ -75,10 +75,6 @@ export function useFileSelection() { }, [selectedFiles]); const setSelection = useCallback((files: FileItem[]) => { - console.log( - "Setting selection to:", - files.map((f) => f.name), - ); setSelectedFiles(files); }, []); diff --git a/src/ui/Desktop/Apps/Terminal/Terminal.tsx b/src/ui/Desktop/Apps/Terminal/Terminal.tsx index 662f7b98..f66a4d49 100644 --- a/src/ui/Desktop/Apps/Terminal/Terminal.tsx +++ b/src/ui/Desktop/Apps/Terminal/Terminal.tsx @@ -692,10 +692,10 @@ export const Terminal = forwardRef(function SSHTerminal( }, [splitScreen, isVisible, terminal]); return ( -
+
{ if (terminal && !splitScreen) { terminal.focus(); diff --git a/src/ui/Desktop/DesktopApp.tsx b/src/ui/Desktop/DesktopApp.tsx index 6af4f825..1374cfcd 100644 --- a/src/ui/Desktop/DesktopApp.tsx +++ b/src/ui/Desktop/DesktopApp.tsx @@ -143,11 +143,12 @@ function AppContent() { isAdmin={isAdmin} username={username} > - {showTerminalView && ( -
- -
- )} +
+ +
{showHome && (
diff --git a/src/ui/Desktop/Homepage/HomepageAuth.tsx b/src/ui/Desktop/Homepage/HomepageAuth.tsx index 7184e7a7..07db0f8b 100644 --- a/src/ui/Desktop/Homepage/HomepageAuth.tsx +++ b/src/ui/Desktop/Homepage/HomepageAuth.tsx @@ -23,6 +23,7 @@ import { getCookie, getServerConfig, isElectron, + logoutUser, } from "../../main-axios.ts"; import { ServerConfig as ServerConfigComponent } from "@/ui/Desktop/Electron Only/ServerConfig.tsx"; @@ -101,6 +102,17 @@ export function HomepageAuth({ setInternalLoggedIn(loggedIn); }, [loggedIn]); + useEffect(() => { + const clearJWTOnLoad = async () => { + try { + await logoutUser(); + } catch (error) { + } + }; + + clearJWTOnLoad(); + }, []); + useEffect(() => { getRegistrationAllowed().then((res) => { setRegistrationAllowed(res.allowed); diff --git a/src/ui/Desktop/Navigation/AppView.tsx b/src/ui/Desktop/Navigation/AppView.tsx index 58eb7d79..11074cac 100644 --- a/src/ui/Desktop/Navigation/AppView.tsx +++ b/src/ui/Desktop/Navigation/AppView.tsx @@ -140,10 +140,10 @@ export function AppView({ const isFileManagerTab = mainTab.type === "file_manager"; styles[mainTab.id] = { position: "absolute", - top: isFileManagerTab ? 0 : 2, - left: isFileManagerTab ? 0 : 2, - right: isFileManagerTab ? 0 : 2, - bottom: isFileManagerTab ? 0 : 2, + top: isFileManagerTab ? 0 : 4, + left: isFileManagerTab ? 0 : 4, + right: isFileManagerTab ? 0 : 4, + bottom: isFileManagerTab ? 0 : 4, zIndex: 20, display: "block", pointerEvents: "auto", @@ -156,10 +156,10 @@ export function AppView({ if (rect && parentRect) { styles[t.id] = { position: "absolute", - top: rect.top - parentRect.top + HEADER_H + 2, - left: rect.left - parentRect.left + 2, - width: rect.width - 4, - height: rect.height - HEADER_H - 4, + top: rect.top - parentRect.top + HEADER_H + 4, + left: rect.left - parentRect.left + 4, + width: rect.width - 8, + height: rect.height - HEADER_H - 8, zIndex: 20, display: "block", pointerEvents: "auto", diff --git a/src/ui/Mobile/Homepage/HomepageAuth.tsx b/src/ui/Mobile/Homepage/HomepageAuth.tsx index d740573f..fb790b8c 100644 --- a/src/ui/Mobile/Homepage/HomepageAuth.tsx +++ b/src/ui/Mobile/Homepage/HomepageAuth.tsx @@ -21,6 +21,7 @@ import { verifyTOTPLogin, setCookie, getCookie, + logoutUser, } from "@/ui/main-axios.ts"; import { PasswordInput } from "@/components/ui/password-input.tsx"; @@ -88,6 +89,18 @@ export function HomepageAuth({ setInternalLoggedIn(loggedIn); }, [loggedIn]); + useEffect(() => { + const clearJWTOnLoad = async () => { + try { + await logoutUser(); + } catch (error) { + console.log("JWT cleanup on HomepageAuth load:", error); + } + }; + + clearJWTOnLoad(); + }, []); + useEffect(() => { getRegistrationAllowed().then((res) => { setRegistrationAllowed(res.allowed);