General bug fixes in terminal and file manager and fixed credential errors in production
This commit is contained in:
@@ -1217,7 +1217,7 @@ async function deploySSHKeyToHost(
|
||||
await new Promise<void>((resolveAdd, rejectAdd) => {
|
||||
const addTimeout = setTimeout(() => {
|
||||
rejectAdd(new Error("Key add timeout"));
|
||||
}, 10000);
|
||||
}, 30000);
|
||||
|
||||
let actualPublicKey = publicKey;
|
||||
try {
|
||||
|
||||
@@ -86,7 +86,9 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
const [currentHost, setCurrentHost] = useState<SSHHost | null>(
|
||||
initialHost || null,
|
||||
);
|
||||
const [currentPath, setCurrentPath] = useState("/");
|
||||
const [currentPath, setCurrentPath] = useState(
|
||||
initialHost?.defaultPath || "/"
|
||||
);
|
||||
const [files, setFiles] = useState<FileItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sshSessionId, setSshSessionId] = useState<string | null>(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;
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ export function DraggableWindow({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex-1 overflow-auto"
|
||||
className="flex-1 overflow-hidden"
|
||||
style={{ height: "calc(100% - 40px)" }}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -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({
|
||||
</Button>
|
||||
)}
|
||||
</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}>
|
||||
<PhotoView src={`data:image/*;base64,${content}`}>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -79,7 +79,6 @@ export function TerminalWindow({
|
||||
minWidth={600}
|
||||
minHeight={400}
|
||||
onClose={handleClose}
|
||||
onMinimize={handleMinimize}
|
||||
onMaximize={handleMaximize}
|
||||
onFocus={handleFocus}
|
||||
isMaximized={currentWindow.isMaximized}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -692,10 +692,10 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
||||
}, [splitScreen, isVisible, terminal]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full m-1 relative">
|
||||
<div className="h-full w-full relative">
|
||||
<div
|
||||
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={() => {
|
||||
if (terminal && !splitScreen) {
|
||||
terminal.focus();
|
||||
|
||||
@@ -143,11 +143,12 @@ function AppContent() {
|
||||
isAdmin={isAdmin}
|
||||
username={username}
|
||||
>
|
||||
{showTerminalView && (
|
||||
<div className="h-screen w-full visible pointer-events-auto static overflow-hidden">
|
||||
<AppView isTopbarOpen={isTopbarOpen} />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="h-screen w-full visible pointer-events-auto static overflow-hidden"
|
||||
style={{ display: showTerminalView ? "block" : "none" }}
|
||||
>
|
||||
<AppView isTopbarOpen={isTopbarOpen} />
|
||||
</div>
|
||||
|
||||
{showHome && (
|
||||
<div className="h-screen w-full visible pointer-events-auto static overflow-hidden">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user