mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
feat(file-manager): default to the remote login directory (#1512)
This commit is contained in:
1 parent
e69029fbf7
commit
fdb2f74259
6 files changed
+195
-9
No files matched your search
@@ -0,0 +1,35 @@
|
||||
import { execChannel, getSessionSftp, type SSHSession } from "./session.js";
|
||||
|
||||
/** The SFTP login directory also respects custom homes and chroot roots. */
|
||||
export async function resolveHomeDirectory(
|
||||
session: SSHSession,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const sftp = await getSessionSftp(session);
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
sftp.realpath(".", (error, path) => {
|
||||
if (error) reject(error);
|
||||
else if (!path) reject(new Error("Empty SFTP login directory"));
|
||||
else resolve(path);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
// Keep the existing shell-only file browser usable without SFTP.
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
execChannel(session, "pwd -P", (error, stream) => {
|
||||
if (error) return reject(error);
|
||||
let output = "";
|
||||
stream.on("data", (chunk: Buffer) => {
|
||||
output += chunk.toString();
|
||||
});
|
||||
stream.on("error", reject);
|
||||
stream.on("close", (code: number) => {
|
||||
const path = output.replace(/[\r\n]+$/, "");
|
||||
if (code !== 0 || !path.startsWith("/")) {
|
||||
reject(new Error("Could not resolve the remote login directory"));
|
||||
} else resolve(path);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveHomeDirectory } from "./home-directory.js";
|
||||
import { getErrorMessage } from "./error-message.js";
|
||||
import type { Express } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
@@ -52,10 +53,10 @@ export function registerFileListingRoutes(
|
||||
* 500:
|
||||
* description: Failed to list files.
|
||||
*/
|
||||
app.get("/listFiles", (req, res) => {
|
||||
app.get("/listFiles", async (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
const sshPath = (req.query.path as string) || "/";
|
||||
let sshPath = (req.query.path as string) || ".";
|
||||
const userId = ctx.currentActor()!;
|
||||
|
||||
if (!sessionId) {
|
||||
@@ -70,6 +71,16 @@ export function registerFileListingRoutes(
|
||||
return res.status(403).json({ error: "Session access denied" });
|
||||
}
|
||||
|
||||
if (sshPath === ".") {
|
||||
try {
|
||||
sshPath = await resolveHomeDirectory(sshConn);
|
||||
} catch (error) {
|
||||
return res.status(500).json({
|
||||
error: getErrorMessage(error, "Could not resolve home directory"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Drop concurrent requests for the same session+path — each would open
|
||||
// a new SSH channel and can exceed the server's per-connection channel limit.
|
||||
const listKey = `${sessionId}:${sshPath}`;
|
||||
|
||||
@@ -155,19 +155,19 @@ function FileManagerContent({
|
||||
const [currentPath, setCurrentPath] = useState(
|
||||
initialPath ||
|
||||
fileManagerHostSetting(initialHost, "defaultPath", "") ||
|
||||
"/",
|
||||
".",
|
||||
);
|
||||
const currentPathRef = useRef(currentPath);
|
||||
currentPathRef.current = currentPath;
|
||||
const lastSuccessfulPathRef = useRef(
|
||||
initialPath ||
|
||||
fileManagerHostSetting(initialHost, "defaultPath", "") ||
|
||||
"/",
|
||||
".",
|
||||
);
|
||||
const [navHistory, setNavHistory] = useState<string[]>([
|
||||
initialPath ||
|
||||
fileManagerHostSetting(initialHost, "defaultPath", "") ||
|
||||
"/",
|
||||
".",
|
||||
]);
|
||||
const [navIndex, setNavIndex] = useState(0);
|
||||
const [files, setFiles] = useState<FileItem[]>([]);
|
||||
@@ -689,7 +689,12 @@ function FileManagerContent({
|
||||
? response
|
||||
: response?.files || [];
|
||||
setFiles(files);
|
||||
lastSuccessfulPathRef.current = currentPath;
|
||||
const listedPath = response.path || currentPath;
|
||||
setCurrentPath(listedPath);
|
||||
setNavHistory((history) =>
|
||||
history.map((path) => (path === "." ? listedPath : path)),
|
||||
);
|
||||
lastSuccessfulPathRef.current = listedPath;
|
||||
clearSelection();
|
||||
initialLoadDoneRef.current = true;
|
||||
|
||||
@@ -799,7 +804,14 @@ function FileManagerContent({
|
||||
: response?.files || [];
|
||||
|
||||
setFiles(files);
|
||||
lastSuccessfulPathRef.current = resolvedPath;
|
||||
const listedPath = response.path || resolvedPath;
|
||||
if (listedPath !== resolvedPath) {
|
||||
setCurrentPath(listedPath);
|
||||
setNavHistory((history) =>
|
||||
history.map((entry) => (entry === path ? listedPath : entry)),
|
||||
);
|
||||
}
|
||||
lastSuccessfulPathRef.current = listedPath;
|
||||
clearSelection();
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -374,11 +374,11 @@ function RemotePane({
|
||||
...current,
|
||||
connectionState: "ready",
|
||||
sessionId: result.sessionId || String(host.id),
|
||||
path: fileManagerHostSetting(host, "defaultPath", "") || "/",
|
||||
path: fileManagerHostSetting(host, "defaultPath", "") || ".",
|
||||
}));
|
||||
await loadRemotePath(
|
||||
result.sessionId || String(host.id),
|
||||
fileManagerHostSetting(host, "defaultPath", "") || "/",
|
||||
fileManagerHostSetting(host, "defaultPath", "") || ".",
|
||||
);
|
||||
},
|
||||
[loadRemotePath, setPane],
|
||||
|
||||
Reference in new issue
Block a user