fix file manager navigation after permission errors (#1103)

This commit is contained in:
ZacharyZcR
2026-07-28 01:48:07 +08:00
committed by GitHub
parent 6866176778
commit 697e363b74
2 changed files with 32 additions and 10 deletions
+23 -3
View File
@@ -106,6 +106,9 @@ function FileManagerContent({
const [currentPath, setCurrentPath] = useState(
initialPath || initialHost?.defaultPath || "/",
);
const lastSuccessfulPathRef = useRef(
initialPath || initialHost?.defaultPath || "/",
);
const [navHistory, setNavHistory] = useState<string[]>([
initialPath || initialHost?.defaultPath || "/",
]);
@@ -497,6 +500,7 @@ function FileManagerContent({
? response
: response?.files || [];
setFiles(files);
lastSuccessfulPathRef.current = currentPath;
clearSelection();
initialLoadDoneRef.current = true;
@@ -565,7 +569,7 @@ function FileManagerContent({
}
const loadDirectory = useCallback(
async (path: string): Promise<boolean> => {
async (path: string, conflictAttempt = 0): Promise<boolean> => {
if (!sshSessionId) {
console.error("Cannot load directory: no SSH session ID");
return false;
@@ -595,6 +599,7 @@ function FileManagerContent({
: response?.files || [];
setFiles(files);
lastSuccessfulPathRef.current = resolvedPath;
clearSelection();
return true;
} catch (error: unknown) {
@@ -617,12 +622,27 @@ function FileManagerContent({
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 (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;
}
if (apiError.response?.data?.needsSudo) {
const previousPath = lastSuccessfulPathRef.current;
lastPathChangeRef.current = previousPath;
setCurrentPath((current) =>
current === resolvedPath ? previousPath : current,
);
if (!sudoDialogOpen) {
setPendingSudoOperation({ type: "navigate", path: resolvedPath });
setSudoDialogOpen(true);
@@ -700,7 +720,7 @@ function FileManagerContent({
}
}
},
[sshSessionId, isLoading, clearSelection, t, sudoDialogOpen, currentHost],
[sshSessionId, clearSelection, t, sudoDialogOpen, currentHost],
);
const debouncedLoadDirectory = useCallback(
@@ -228,8 +228,8 @@ export function FileManagerSidebar({
* Called the first time a folder is expanded via FolderTree's onSelect.
*/
const loadSubdirectory = useCallback(
async (folderId: string, folderPath: string) => {
if (!sshSessionId) return;
async (folderId: string, folderPath: string): Promise<boolean> => {
if (!sshSessionId) return false;
try {
const subResponse = await listSSHFiles(sshSessionId, folderPath);
@@ -262,16 +262,20 @@ export function FileManagerSidebar({
});
return updateChildren(prevTree);
});
loadedFoldersRef.current.add(folderPath);
return true;
} catch (error: unknown) {
const status =
(error as { status?: number })?.status ||
(error as { response?: { status?: number } })?.response?.status;
if (status === 409) {
// Another request was listing this path — retry after the lock clears
setTimeout(() => loadSubdirectory(folderId, folderPath), 600);
return;
setTimeout(() => void loadSubdirectory(folderId, folderPath), 600);
return false;
}
loadedFoldersRef.current.delete(folderPath);
console.error("Failed to load subdirectory:", error);
return false;
}
},
[sshSessionId],
@@ -309,8 +313,7 @@ export function FileManagerSidebar({
const parent = findByPath(directoryTree);
if (parent && !loadedFoldersRef.current.has(parent.path)) {
loadedFoldersRef.current.add(parent.path);
loadSubdirectory(parent.id, parent.path);
void loadSubdirectory(parent.id, parent.path);
}
}, [currentPath, directoryTree, loadSubdirectory, sshSessionId]);
@@ -416,7 +419,6 @@ export function FileManagerSidebar({
item.path !== "/" &&
!loadedFoldersRef.current.has(item.path)
) {
loadedFoldersRef.current.add(item.path);
await loadSubdirectory(id, item.path);
}
},