mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-23 14:36:41 +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(
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user