feat: 添加挂起会话导出日志功能

This commit is contained in:
Baobhan Sith
2025-05-10 14:29:21 +08:00
parent d828de3ef8
commit f3b190bd24
7 changed files with 187 additions and 4 deletions
+5 -2
View File
@@ -1191,7 +1191,8 @@
},
"action": {
"resume": "Resume",
"remove": "Remove"
"remove": "Remove",
"exportLog": "Export Log"
}
},
"time": {
@@ -1221,7 +1222,9 @@
"defaultSessionName": "Session",
"resumeSuccess": "Session \"{name}\" resumed successfully.",
"resumeErrorBackend": "Backend failed to resume session: {error}",
"autoTerminated": "Suspended session \"{name}\" was auto-terminated by the backend due to: {reason}"
"autoTerminated": "Suspended session \"{name}\" was auto-terminated by the backend due to: {reason}",
"logExportSuccess": "Suspended session log {name} has started downloading.",
"logExportError": "Failed to export suspended session log: {error}"
}
}
}
+5 -2
View File
@@ -1193,7 +1193,8 @@
},
"action": {
"resume": "恢复",
"remove": "移除"
"remove": "移除",
"exportLog": "导出日志"
}
},
"time": {
@@ -1223,7 +1224,9 @@
"defaultSessionName": "会话",
"resumeSuccess": "会话 \"{name}\" 已成功恢复。",
"resumeErrorBackend": "后端恢复会话失败: {error}",
"autoTerminated": "已挂起的会话 \"{name}\" 因以下原因被后端自动终止: {reason}"
"autoTerminated": "已挂起的会话 \"{name}\" 因以下原因被后端自动终止: {reason}",
"logExportSuccess": "已挂起会话日志 {name} 已开始下载。",
"logExportError": "导出已挂起会话日志失败: {error}"
}
}
}
@@ -394,6 +394,79 @@ export const editSshSessionName = async (suspendSessionId: string, newCustomName
}
};
/**
* 请求导出指定挂起 SSH 会话的日志
* @param suspendSessionId 要导出日志的挂起会话 ID
*/
export const exportSshSessionLog = async (suspendSessionId: string): Promise<void> => {
const uiNotificationsStore = useUiNotificationsStore();
console.log(`[${t('term.sshSuspend')}] 请求导出挂起会话日志 (ID: ${suspendSessionId})`);
try {
// API 端点为 /api/v1/ssh-suspend/log/:suspendSessionId
// apiClient.get会自动处理Blob响应类型,并尝试触发下载
// 我们需要获取建议的文件名,后端会在 Content-Disposition 头中提供
const response = await apiClient.get<Blob>(`ssh-suspend/log/${suspendSessionId}`, {
responseType: 'blob', // 重要:期望响应为 Blob
// 我们可以传递一个 onDownloadProgress 回调(如果 apiClient 支持的话)
});
// 从 Content-Disposition 获取文件名
const contentDisposition = response.headers['content-disposition'];
let filename = `ssh_log_${suspendSessionId}.log`; // 默认文件名
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i);
if (filenameMatch && filenameMatch.length > 1) {
filename = filenameMatch[1];
}
}
// 创建一个下载链接并点击它
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename); // 设置下载文件名
document.body.appendChild(link);
link.click();
// 清理
link.parentNode?.removeChild(link);
window.URL.revokeObjectURL(url);
uiNotificationsStore.addNotification({
type: 'success',
message: t('sshSuspend.notifications.logExportSuccess', { name: filename }),
});
console.log(`[${t('term.sshSuspend')}] 挂起会话日志 ${filename} (ID: ${suspendSessionId}) 已开始下载。`);
} catch (error: any) {
console.error(`[${t('term.sshSuspend')}] 导出挂起会话日志 ${suspendSessionId} 失败:`, error);
let errorMessage = t('term.unknownError');
if (error.response && error.response.data) {
// 如果响应是 Blob 但我们期望 JSON 错误信息,需要特殊处理
// 假设错误时后端会返回 JSON
if (error.response.data instanceof Blob && error.response.headers['content-type']?.includes('application/json')) {
try {
const errorJson = JSON.parse(await error.response.data.text());
errorMessage = errorJson.message || errorMessage;
} catch (e) {
// Blob 不是有效的 JSON,使用通用错误
}
} else if (typeof error.response.data === 'object') {
errorMessage = error.response.data.message || error.message;
} else {
errorMessage = error.message;
}
} else {
errorMessage = error.message || String(error);
}
uiNotificationsStore.addNotification({
type: 'error',
message: t('sshSuspend.notifications.logExportError', { error: errorMessage }),
});
}
};
// --- S2C Message Handlers ---
// 旧的 handleSshSuspendStartedResp 不再需要,因为流程已改变
@@ -97,6 +97,15 @@
<i class="fas fa-trash-alt action-icon" style="color: white;"></i>
<span class="button-session-text">{{ $t('suspendedSshSessions.action.remove') }}</span>
</button>
<button
v-if="session.backendSshStatus === 'disconnected_by_backend' || session.backendSshStatus === 'hanging'"
@click="exportLog(session)"
:title="$t('suspendedSshSessions.action.exportLog')"
class="responsive-button-padding py-1.5 text-sm font-medium rounded-md text-button-text bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-150 inline-flex items-center"
>
<i class="fas fa-download action-icon" style="color: var(--button-text-color, white);"></i>
<span class="button-session-text">{{ $t('suspendedSshSessions.action.exportLog') }}</span>
</button>
</div>
</div>
</div>
@@ -250,6 +259,12 @@ const removeSession = (session: SuspendedSshSession) => { // 参数类型改为
emitWorkspaceEvent('suspendedSession:actionCompleted');
};
const exportLog = async (session: SuspendedSshSession) => {
console.log(`[SuspendedSshSessionsView] Attempting to export log for session ID: ${session.suspendSessionId}`);
await sessionStore.exportSshSessionLog(session.suspendSessionId);
// emitWorkspaceEvent
};
let fetchIntervalId: number | undefined;
onMounted(async () => {