General bug fixes in terminal and file manager and fixed credential errors in production

This commit is contained in:
LukeGus
2025-09-28 23:54:12 -05:00
parent 63e776f183
commit 144171d1fa
15 changed files with 139 additions and 41 deletions

View File

@@ -84,6 +84,10 @@ http {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
} }
location ~ ^/database(/.*)?$ { location ~ ^/database(/.*)?$ {

View File

@@ -72,6 +72,10 @@ http {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
} }
location ~ ^/database(/.*)?$ { location ~ ^/database(/.*)?$ {

View File

@@ -1217,7 +1217,7 @@ async function deploySSHKeyToHost(
await new Promise<void>((resolveAdd, rejectAdd) => { await new Promise<void>((resolveAdd, rejectAdd) => {
const addTimeout = setTimeout(() => { const addTimeout = setTimeout(() => {
rejectAdd(new Error("Key add timeout")); rejectAdd(new Error("Key add timeout"));
}, 10000); }, 30000);
let actualPublicKey = publicKey; let actualPublicKey = publicKey;
try { try {

View File

@@ -86,7 +86,9 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
const [currentHost, setCurrentHost] = useState<SSHHost | null>( const [currentHost, setCurrentHost] = useState<SSHHost | null>(
initialHost || null, initialHost || null,
); );
const [currentPath, setCurrentPath] = useState("/"); const [currentPath, setCurrentPath] = useState(
initialHost?.defaultPath || "/"
);
const [files, setFiles] = useState<FileItem[]>([]); const [files, setFiles] = useState<FileItem[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [sshSessionId, setSshSessionId] = useState<string | null>(null); const [sshSessionId, setSshSessionId] = useState<string | null>(null);
@@ -754,8 +756,14 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
await recordRecentFile(file); await recordRecentFile(file);
const windowCount = Date.now() % 10; const windowCount = Date.now() % 10;
const offsetX = 120 + windowCount * 30; const baseOffsetX = 120 + windowCount * 30;
const offsetY = 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; const windowTitle = file.name;

View File

@@ -332,7 +332,7 @@ export function DraggableWindow({
</div> </div>
<div <div
className="flex-1 overflow-auto" className="flex-1 overflow-hidden"
style={{ height: "calc(100% - 40px)" }} style={{ height: "calc(100% - 40px)" }}
> >
{children} {children}

View File

@@ -90,6 +90,7 @@ interface FileViewerProps {
isEditable?: boolean; isEditable?: boolean;
onContentChange?: (content: string) => void; onContentChange?: (content: string) => void;
onSave?: (content: string) => void; onSave?: (content: string) => void;
onRevert?: () => void;
onDownload?: () => void; onDownload?: () => void;
onMediaDimensionsChange?: (dimensions: { onMediaDimensionsChange?: (dimensions: {
width: number; width: number;
@@ -304,6 +305,7 @@ export function FileViewer({
isEditable = false, isEditable = false,
onContentChange, onContentChange,
onSave, onSave,
onRevert,
onDownload, onDownload,
onMediaDimensionsChange, onMediaDimensionsChange,
}: FileViewerProps) { }: FileViewerProps) {
@@ -352,7 +354,12 @@ export function FileViewer({
} else { } else {
setShowLargeFileWarning(false); 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) => { const handleContentChange = (newContent: string) => {
setEditedContent(newContent); setEditedContent(newContent);
@@ -365,9 +372,12 @@ export function FileViewer({
}; };
const handleRevert = () => { const handleRevert = () => {
setEditedContent(savedContent); if (onRevert) {
setHasChanges(false); onRevert();
onContentChange?.(savedContent); } else {
setEditedContent(savedContent);
setHasChanges(false);
}
}; };
useEffect(() => { useEffect(() => {
@@ -696,6 +706,16 @@ export function FileViewer({
</Button> </Button>
)} )}
</div> </div>
) : file.name.toLowerCase().endsWith('.svg') ? (
<div
className="max-w-full max-h-full flex items-center justify-center"
style={{ maxHeight: "calc(100vh - 200px)" }}
dangerouslySetInnerHTML={{ __html: content }}
onLoad={() => {
setImageLoading(false);
setImageLoadError(false);
}}
/>
) : ( ) : (
<PhotoProvider maskOpacity={0.7}> <PhotoProvider maskOpacity={0.7}>
<PhotoView src={`data:image/*;base64,${content}`}> <PhotoView src={`data:image/*;base64,${content}`}>

View File

@@ -211,6 +211,37 @@ export function FileWindow({
loadFileContent(); loadFileContent();
}, [file, sshSessionId, sshHost]); }, [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) => { const handleSave = async (newContent: string) => {
try { try {
setIsLoading(true); setIsLoading(true);
@@ -252,17 +283,20 @@ export function FileWindow({
if (autoSaveTimerRef.current) { if (autoSaveTimerRef.current) {
clearTimeout(autoSaveTimerRef.current); clearTimeout(autoSaveTimerRef.current);
autoSaveTimerRef.current = null;
} }
autoSaveTimerRef.current = setTimeout(async () => { if (newContent !== content) {
try { autoSaveTimerRef.current = setTimeout(async () => {
await handleSave(newContent); try {
toast.success(t("fileManager.fileAutoSaved")); await handleSave(newContent);
} catch (error) { toast.success(t("fileManager.fileAutoSaved"));
console.error("Auto-save failed:", error); } catch (error) {
toast.error(t("fileManager.autoSaveFailed")); console.error("Auto-save failed:", error);
} toast.error(t("fileManager.autoSaveFailed"));
}, 60000); }
}, 60000);
}
}; };
useEffect(() => { useEffect(() => {
@@ -363,6 +397,7 @@ export function FileWindow({
content={pendingContent || content} content={pendingContent || content}
savedContent={content} savedContent={content}
isLoading={isLoading} isLoading={isLoading}
onRevert={handleRevert}
isEditable={isEditable} isEditable={isEditable}
onContentChange={handleContentChange} onContentChange={handleContentChange}
onSave={(newContent) => handleSave(newContent)} onSave={(newContent) => handleSave(newContent)}

View File

@@ -79,7 +79,6 @@ export function TerminalWindow({
minWidth={600} minWidth={600}
minHeight={400} minHeight={400}
onClose={handleClose} onClose={handleClose}
onMinimize={handleMinimize}
onMaximize={handleMaximize} onMaximize={handleMaximize}
onFocus={handleFocus} onFocus={handleFocus}
isMaximized={currentWindow.isMaximized} isMaximized={currentWindow.isMaximized}

View File

@@ -40,9 +40,15 @@ export function WindowManager({ children }: WindowManagerProps) {
const id = `window-${++windowCounter.current}`; const id = `window-${++windowCounter.current}`;
const zIndex = ++nextZIndex.current; const zIndex = ++nextZIndex.current;
const offset = (windows.length % 5) * 30; const offset = (windows.length % 5) * 20;
const adjustedX = windowData.x + offset; let adjustedX = windowData.x + offset;
const adjustedY = windowData.y + 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 = { const newWindow: WindowInstance = {
...windowData, ...windowData,

View File

@@ -75,10 +75,6 @@ export function useFileSelection() {
}, [selectedFiles]); }, [selectedFiles]);
const setSelection = useCallback((files: FileItem[]) => { const setSelection = useCallback((files: FileItem[]) => {
console.log(
"Setting selection to:",
files.map((f) => f.name),
);
setSelectedFiles(files); setSelectedFiles(files);
}, []); }, []);

View File

@@ -692,10 +692,10 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
}, [splitScreen, isVisible, terminal]); }, [splitScreen, isVisible, terminal]);
return ( return (
<div className="h-full w-full m-1 relative"> <div className="h-full w-full relative">
<div <div
ref={xtermRef} ref={xtermRef}
className={`h-full w-full transition-opacity duration-200 ${visible && isVisible && !isConnecting ? "opacity-100" : "opacity-0"} overflow-hidden`} className={`h-full w-full transition-opacity duration-200 ${visible && isVisible && !isConnecting ? "opacity-100" : "opacity-0"}`}
onClick={() => { onClick={() => {
if (terminal && !splitScreen) { if (terminal && !splitScreen) {
terminal.focus(); terminal.focus();

View File

@@ -143,11 +143,12 @@ function AppContent() {
isAdmin={isAdmin} isAdmin={isAdmin}
username={username} username={username}
> >
{showTerminalView && ( <div
<div className="h-screen w-full visible pointer-events-auto static overflow-hidden"> className="h-screen w-full visible pointer-events-auto static overflow-hidden"
<AppView isTopbarOpen={isTopbarOpen} /> style={{ display: showTerminalView ? "block" : "none" }}
</div> >
)} <AppView isTopbarOpen={isTopbarOpen} />
</div>
{showHome && ( {showHome && (
<div className="h-screen w-full visible pointer-events-auto static overflow-hidden"> <div className="h-screen w-full visible pointer-events-auto static overflow-hidden">

View File

@@ -23,6 +23,7 @@ import {
getCookie, getCookie,
getServerConfig, getServerConfig,
isElectron, isElectron,
logoutUser,
} from "../../main-axios.ts"; } from "../../main-axios.ts";
import { ServerConfig as ServerConfigComponent } from "@/ui/Desktop/Electron Only/ServerConfig.tsx"; import { ServerConfig as ServerConfigComponent } from "@/ui/Desktop/Electron Only/ServerConfig.tsx";
@@ -101,6 +102,17 @@ export function HomepageAuth({
setInternalLoggedIn(loggedIn); setInternalLoggedIn(loggedIn);
}, [loggedIn]); }, [loggedIn]);
useEffect(() => {
const clearJWTOnLoad = async () => {
try {
await logoutUser();
} catch (error) {
}
};
clearJWTOnLoad();
}, []);
useEffect(() => { useEffect(() => {
getRegistrationAllowed().then((res) => { getRegistrationAllowed().then((res) => {
setRegistrationAllowed(res.allowed); setRegistrationAllowed(res.allowed);

View File

@@ -140,10 +140,10 @@ export function AppView({
const isFileManagerTab = mainTab.type === "file_manager"; const isFileManagerTab = mainTab.type === "file_manager";
styles[mainTab.id] = { styles[mainTab.id] = {
position: "absolute", position: "absolute",
top: isFileManagerTab ? 0 : 2, top: isFileManagerTab ? 0 : 4,
left: isFileManagerTab ? 0 : 2, left: isFileManagerTab ? 0 : 4,
right: isFileManagerTab ? 0 : 2, right: isFileManagerTab ? 0 : 4,
bottom: isFileManagerTab ? 0 : 2, bottom: isFileManagerTab ? 0 : 4,
zIndex: 20, zIndex: 20,
display: "block", display: "block",
pointerEvents: "auto", pointerEvents: "auto",
@@ -156,10 +156,10 @@ export function AppView({
if (rect && parentRect) { if (rect && parentRect) {
styles[t.id] = { styles[t.id] = {
position: "absolute", position: "absolute",
top: rect.top - parentRect.top + HEADER_H + 2, top: rect.top - parentRect.top + HEADER_H + 4,
left: rect.left - parentRect.left + 2, left: rect.left - parentRect.left + 4,
width: rect.width - 4, width: rect.width - 8,
height: rect.height - HEADER_H - 4, height: rect.height - HEADER_H - 8,
zIndex: 20, zIndex: 20,
display: "block", display: "block",
pointerEvents: "auto", pointerEvents: "auto",

View File

@@ -21,6 +21,7 @@ import {
verifyTOTPLogin, verifyTOTPLogin,
setCookie, setCookie,
getCookie, getCookie,
logoutUser,
} from "@/ui/main-axios.ts"; } from "@/ui/main-axios.ts";
import { PasswordInput } from "@/components/ui/password-input.tsx"; import { PasswordInput } from "@/components/ui/password-input.tsx";
@@ -88,6 +89,18 @@ export function HomepageAuth({
setInternalLoggedIn(loggedIn); setInternalLoggedIn(loggedIn);
}, [loggedIn]); }, [loggedIn]);
useEffect(() => {
const clearJWTOnLoad = async () => {
try {
await logoutUser();
} catch (error) {
console.log("JWT cleanup on HomepageAuth load:", error);
}
};
clearJWTOnLoad();
}, []);
useEffect(() => { useEffect(() => {
getRegistrationAllowed().then((res) => { getRegistrationAllowed().then((res) => {
setRegistrationAllowed(res.allowed); setRegistrationAllowed(res.allowed);