dev-1.7.0 #294
@@ -93,11 +93,11 @@ function scheduleSessionCleanup(sessionId: string) {
|
|||||||
// Clear existing timeout
|
// Clear existing timeout
|
||||||
if (session.timeout) clearTimeout(session.timeout);
|
if (session.timeout) clearTimeout(session.timeout);
|
||||||
|
|
||||||
// Set new timeout for 10 minutes of inactivity
|
// Increase timeout to 30 minutes of inactivity
|
||||||
session.timeout = setTimeout(() => {
|
session.timeout = setTimeout(() => {
|
||||||
fileLogger.info(`Cleaning up inactive SSH session: ${sessionId}`);
|
fileLogger.info(`Cleaning up inactive SSH session: ${sessionId}`);
|
||||||
cleanupSession(sessionId);
|
cleanupSession(sessionId);
|
||||||
}, 10 * 60 * 1000); // 10 minutes
|
}, 30 * 60 * 1000); // 30 minutes - increased from 10 minutes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +321,41 @@ app.get("/ssh/file_manager/ssh/status", (req, res) => {
|
|||||||
res.json({ status: "success", connected: isConnected });
|
res.json({ status: "success", connected: isConnected });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// SSH keepalive endpoint - extends session timeout and verifies connection
|
||||||
|
app.post("/ssh/file_manager/ssh/keepalive", (req, res) => {
|
||||||
|
const { sessionId } = req.body;
|
||||||
|
|
||||||
|
if (!sessionId) {
|
||||||
|
return res.status(400).json({ error: "Session ID is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = sshSessions[sessionId];
|
||||||
|
|
||||||
|
if (!session || !session.isConnected) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "SSH session not found or not connected",
|
||||||
|
connected: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last active time and reschedule cleanup
|
||||||
|
session.lastActive = Date.now();
|
||||||
|
scheduleSessionCleanup(sessionId);
|
||||||
|
|
||||||
|
fileLogger.debug(`SSH session keepalive: ${sessionId}`, {
|
||||||
|
operation: "ssh_keepalive",
|
||||||
|
sessionId,
|
||||||
|
lastActive: session.lastActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
status: "success",
|
||||||
|
connected: true,
|
||||||
|
message: "Session keepalive successful",
|
||||||
|
lastActive: session.lastActive
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.get("/ssh/file_manager/ssh/listFiles", (req, res) => {
|
app.get("/ssh/file_manager/ssh/listFiles", (req, res) => {
|
||||||
const sessionId = req.query.sessionId as string;
|
const sessionId = req.query.sessionId as string;
|
||||||
const sshConn = sshSessions[sessionId];
|
const sshConn = sshSessions[sessionId];
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
moveSSHItem,
|
moveSSHItem,
|
||||||
connectSSH,
|
connectSSH,
|
||||||
getSSHStatus,
|
getSSHStatus,
|
||||||
|
keepSSHAlive,
|
||||||
identifySSHSymlink,
|
identifySSHSymlink,
|
||||||
addRecentFile,
|
addRecentFile,
|
||||||
addPinnedFile,
|
addPinnedFile,
|
||||||
@@ -144,6 +145,36 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
sshHost: currentHost!,
|
sshHost: currentHost!,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// SSH keepalive function
|
||||||
|
const startKeepalive = useCallback(() => {
|
||||||
|
if (!sshSessionId) return;
|
||||||
|
|
||||||
|
// Clear existing timer
|
||||||
|
if (keepaliveTimerRef.current) {
|
||||||
|
clearInterval(keepaliveTimerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send keepalive every 5 minutes (300000ms)
|
||||||
|
keepaliveTimerRef.current = setInterval(async () => {
|
||||||
|
if (sshSessionId) {
|
||||||
|
try {
|
||||||
|
await keepSSHAlive(sshSessionId);
|
||||||
|
console.log("SSH keepalive sent successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("SSH keepalive failed:", error);
|
||||||
|
// If keepalive fails, session might be dead - could trigger reconnect here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 5 * 60 * 1000); // 5 minutes
|
||||||
|
}, [sshSessionId]);
|
||||||
|
|
||||||
|
const stopKeepalive = useCallback(() => {
|
||||||
|
if (keepaliveTimerRef.current) {
|
||||||
|
clearInterval(keepaliveTimerRef.current);
|
||||||
|
keepaliveTimerRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Initialize SSH connection
|
// Initialize SSH connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentHost) {
|
if (currentHost) {
|
||||||
@@ -151,6 +182,20 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
}
|
}
|
||||||
}, [currentHost]);
|
}, [currentHost]);
|
||||||
|
|
||||||
|
// Start/stop keepalive based on SSH session
|
||||||
|
useEffect(() => {
|
||||||
|
if (sshSessionId) {
|
||||||
|
startKeepalive();
|
||||||
|
} else {
|
||||||
|
stopKeepalive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
|
return () => {
|
||||||
|
stopKeepalive();
|
||||||
|
};
|
||||||
|
}, [sshSessionId, startKeepalive, stopKeepalive]);
|
||||||
|
|
||||||
// Track if initial directory load is done to prevent duplicate loading
|
// Track if initial directory load is done to prevent duplicate loading
|
||||||
const initialLoadDoneRef = useRef(false);
|
const initialLoadDoneRef = useRef(false);
|
||||||
// Track last path change to prevent rapid navigation issues
|
// Track last path change to prevent rapid navigation issues
|
||||||
@@ -158,6 +203,8 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
const pathChangeTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const pathChangeTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
// Track current loading request to handle cancellation
|
// Track current loading request to handle cancellation
|
||||||
const currentLoadingPathRef = useRef<string>("");
|
const currentLoadingPathRef = useRef<string>("");
|
||||||
|
// SSH keepalive timer
|
||||||
|
const keepaliveTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// Debounced directory loading for path changes
|
// Debounced directory loading for path changes
|
||||||
const debouncedLoadDirectory = useCallback((path: string) => {
|
const debouncedLoadDirectory = useCallback((path: string) => {
|
||||||
|
|||||||
@@ -1002,6 +1002,17 @@ export async function getSSHStatus(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function keepSSHAlive(sessionId: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await fileManagerApi.post("/ssh/keepalive", {
|
||||||
|
sessionId,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
handleApiError(error, "SSH keepalive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function listSSHFiles(
|
export async function listSSHFiles(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
path: string,
|
path: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user