General bug fixes in terminal and file manager and fixed credential errors in production
This commit is contained in:
@@ -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(/.*)?$ {
|
||||||
|
|||||||
@@ -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(/.*)?$ {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}`}>
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user