mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
fix file manager navigation after permission errors (#1103)
This commit is contained in:
@@ -106,6 +106,9 @@ function FileManagerContent({
|
|||||||
const [currentPath, setCurrentPath] = useState(
|
const [currentPath, setCurrentPath] = useState(
|
||||||
initialPath || initialHost?.defaultPath || "/",
|
initialPath || initialHost?.defaultPath || "/",
|
||||||
);
|
);
|
||||||
|
const lastSuccessfulPathRef = useRef(
|
||||||
|
initialPath || initialHost?.defaultPath || "/",
|
||||||
|
);
|
||||||
const [navHistory, setNavHistory] = useState<string[]>([
|
const [navHistory, setNavHistory] = useState<string[]>([
|
||||||
initialPath || initialHost?.defaultPath || "/",
|
initialPath || initialHost?.defaultPath || "/",
|
||||||
]);
|
]);
|
||||||
@@ -497,6 +500,7 @@ function FileManagerContent({
|
|||||||
? response
|
? response
|
||||||
: response?.files || [];
|
: response?.files || [];
|
||||||
setFiles(files);
|
setFiles(files);
|
||||||
|
lastSuccessfulPathRef.current = currentPath;
|
||||||
clearSelection();
|
clearSelection();
|
||||||
initialLoadDoneRef.current = true;
|
initialLoadDoneRef.current = true;
|
||||||
|
|
||||||
@@ -565,7 +569,7 @@ function FileManagerContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadDirectory = useCallback(
|
const loadDirectory = useCallback(
|
||||||
async (path: string): Promise<boolean> => {
|
async (path: string, conflictAttempt = 0): Promise<boolean> => {
|
||||||
if (!sshSessionId) {
|
if (!sshSessionId) {
|
||||||
console.error("Cannot load directory: no SSH session ID");
|
console.error("Cannot load directory: no SSH session ID");
|
||||||
return false;
|
return false;
|
||||||
@@ -595,6 +599,7 @@ function FileManagerContent({
|
|||||||
: response?.files || [];
|
: response?.files || [];
|
||||||
|
|
||||||
setFiles(files);
|
setFiles(files);
|
||||||
|
lastSuccessfulPathRef.current = resolvedPath;
|
||||||
clearSelection();
|
clearSelection();
|
||||||
return true;
|
return true;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@@ -617,12 +622,27 @@ function FileManagerContent({
|
|||||||
|
|
||||||
const httpStatus = apiError.status ?? apiError.response?.status;
|
const httpStatus = apiError.status ?? apiError.response?.status;
|
||||||
|
|
||||||
// 409 = concurrent request already in flight — silently drop
|
// The sidebar may be listing the same path to populate its tree.
|
||||||
|
// Retry instead of leaving the breadcrumb and visible files out of sync.
|
||||||
if (httpStatus === 409) {
|
if (httpStatus === 409) {
|
||||||
|
if (conflictAttempt < 3) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||||
|
return loadDirectory(resolvedPath, conflictAttempt + 1);
|
||||||
|
}
|
||||||
|
const previousPath = lastSuccessfulPathRef.current;
|
||||||
|
lastPathChangeRef.current = previousPath;
|
||||||
|
setCurrentPath((current) =>
|
||||||
|
current === resolvedPath ? previousPath : current,
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiError.response?.data?.needsSudo) {
|
if (apiError.response?.data?.needsSudo) {
|
||||||
|
const previousPath = lastSuccessfulPathRef.current;
|
||||||
|
lastPathChangeRef.current = previousPath;
|
||||||
|
setCurrentPath((current) =>
|
||||||
|
current === resolvedPath ? previousPath : current,
|
||||||
|
);
|
||||||
if (!sudoDialogOpen) {
|
if (!sudoDialogOpen) {
|
||||||
setPendingSudoOperation({ type: "navigate", path: resolvedPath });
|
setPendingSudoOperation({ type: "navigate", path: resolvedPath });
|
||||||
setSudoDialogOpen(true);
|
setSudoDialogOpen(true);
|
||||||
@@ -700,7 +720,7 @@ function FileManagerContent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[sshSessionId, isLoading, clearSelection, t, sudoDialogOpen, currentHost],
|
[sshSessionId, clearSelection, t, sudoDialogOpen, currentHost],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debouncedLoadDirectory = useCallback(
|
const debouncedLoadDirectory = useCallback(
|
||||||
|
|||||||
@@ -228,8 +228,8 @@ export function FileManagerSidebar({
|
|||||||
* Called the first time a folder is expanded via FolderTree's onSelect.
|
* Called the first time a folder is expanded via FolderTree's onSelect.
|
||||||
*/
|
*/
|
||||||
const loadSubdirectory = useCallback(
|
const loadSubdirectory = useCallback(
|
||||||
async (folderId: string, folderPath: string) => {
|
async (folderId: string, folderPath: string): Promise<boolean> => {
|
||||||
if (!sshSessionId) return;
|
if (!sshSessionId) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subResponse = await listSSHFiles(sshSessionId, folderPath);
|
const subResponse = await listSSHFiles(sshSessionId, folderPath);
|
||||||
@@ -262,16 +262,20 @@ export function FileManagerSidebar({
|
|||||||
});
|
});
|
||||||
return updateChildren(prevTree);
|
return updateChildren(prevTree);
|
||||||
});
|
});
|
||||||
|
loadedFoldersRef.current.add(folderPath);
|
||||||
|
return true;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const status =
|
const status =
|
||||||
(error as { status?: number })?.status ||
|
(error as { status?: number })?.status ||
|
||||||
(error as { response?: { status?: number } })?.response?.status;
|
(error as { response?: { status?: number } })?.response?.status;
|
||||||
if (status === 409) {
|
if (status === 409) {
|
||||||
// Another request was listing this path — retry after the lock clears
|
// Another request was listing this path — retry after the lock clears
|
||||||
setTimeout(() => loadSubdirectory(folderId, folderPath), 600);
|
setTimeout(() => void loadSubdirectory(folderId, folderPath), 600);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
loadedFoldersRef.current.delete(folderPath);
|
||||||
console.error("Failed to load subdirectory:", error);
|
console.error("Failed to load subdirectory:", error);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[sshSessionId],
|
[sshSessionId],
|
||||||
@@ -309,8 +313,7 @@ export function FileManagerSidebar({
|
|||||||
|
|
||||||
const parent = findByPath(directoryTree);
|
const parent = findByPath(directoryTree);
|
||||||
if (parent && !loadedFoldersRef.current.has(parent.path)) {
|
if (parent && !loadedFoldersRef.current.has(parent.path)) {
|
||||||
loadedFoldersRef.current.add(parent.path);
|
void loadSubdirectory(parent.id, parent.path);
|
||||||
loadSubdirectory(parent.id, parent.path);
|
|
||||||
}
|
}
|
||||||
}, [currentPath, directoryTree, loadSubdirectory, sshSessionId]);
|
}, [currentPath, directoryTree, loadSubdirectory, sshSessionId]);
|
||||||
|
|
||||||
@@ -416,7 +419,6 @@ export function FileManagerSidebar({
|
|||||||
item.path !== "/" &&
|
item.path !== "/" &&
|
||||||
!loadedFoldersRef.current.has(item.path)
|
!loadedFoldersRef.current.has(item.path)
|
||||||
) {
|
) {
|
||||||
loadedFoldersRef.current.add(item.path);
|
|
||||||
await loadSubdirectory(id, item.path);
|
await loadSubdirectory(id, item.path);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user