This commit is contained in:
Baobhan Sith
2025-04-23 21:25:44 +08:00
parent f7fe1904cf
commit 1b943f7614
2 changed files with 98 additions and 46 deletions
+98 -45
View File
@@ -195,11 +195,60 @@ const fetchRemoteDockerStatus = async (state: ClientState): Promise<{ available:
let allContainers: DockerContainer[] = []; let allContainers: DockerContainer[] = [];
const statsMap = new Map<string, DockerStats>(); const statsMap = new Map<string, DockerStats>();
let isDockerCmdAvailable = true; // Assume available initially let isDockerCmdAvailable = false; // Start assuming unavailable until version check passes
// 1. Get basic container info // --- 1. Check Docker Availability with 'docker version' ---
try {
const versionCommand = "docker version --format '{{.Server.Version}}'";
console.log(`[fetchRemoteDockerStatus] Executing: ${versionCommand} on session ${state.ws.sessionId}`);
const { stdout: versionStdout, stderr: versionStderr } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
let stdout = '';
let stderr = '';
state.sshClient.exec(versionCommand, { pty: false }, (err, stream) => {
if (err) return reject(err);
stream.on('data', (data: Buffer) => { stdout += data.toString(); });
stream.stderr.on('data', (data: Buffer) => { stderr += data.toString(); });
stream.on('close', (code: number | null) => {
// Resolve even if code is non-zero, check stderr
resolve({ stdout, stderr });
});
stream.on('error', (execErr: Error) => reject(execErr));
});
});
// Check stderr for common errors indicating Docker is unavailable or inaccessible
if (versionStderr.includes('command not found') ||
versionStderr.includes('permission denied') ||
versionStderr.includes('Cannot connect to the Docker daemon')) {
console.warn(`[fetchRemoteDockerStatus] Docker version check failed on session ${state.ws.sessionId}. Docker unavailable or inaccessible. Stderr: ${versionStderr.trim()}`);
return { available: false, containers: [] }; // Docker not available
} else if (versionStderr) {
// Log other stderr outputs as warnings but proceed
console.warn(`[fetchRemoteDockerStatus] Docker version command stderr on session ${state.ws.sessionId}: ${versionStderr.trim()}`);
}
// If stdout has content (version number), Docker is likely available
if (versionStdout.trim()) {
console.log(`[fetchRemoteDockerStatus] Docker version check successful on session ${state.ws.sessionId}. Version: ${versionStdout.trim()}`);
isDockerCmdAvailable = true;
} else {
// If stdout is empty but no critical error in stderr, still assume unavailable
console.warn(`[fetchRemoteDockerStatus] Docker version check on session ${state.ws.sessionId} produced no output, assuming Docker unavailable.`);
return { available: false, containers: [] };
}
} catch (error: any) {
console.error(`[fetchRemoteDockerStatus] Error executing docker version for session ${state.ws.sessionId}:`, error);
// Treat any error during version check as Docker being unavailable
return { available: false, containers: [] };
}
// If version check failed, we already returned. If it passed, isDockerCmdAvailable is true.
// --- 2. Get basic container info ---
try { try {
const psCommand = "docker ps -a --no-trunc --format '{{json .}}'"; const psCommand = "docker ps -a --no-trunc --format '{{json .}}'";
console.log(`[fetchRemoteDockerStatus] Executing: ${psCommand} on session ${state.ws.sessionId}`);
const { stdout: psStdout, stderr: psStderr } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { const { stdout: psStdout, stderr: psStderr } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
let stdout = ''; let stdout = '';
let stderr = ''; let stderr = '';
@@ -215,17 +264,20 @@ const fetchRemoteDockerStatus = async (state: ClientState): Promise<{ available:
}); });
}); });
if (psStderr.includes('command not found') || psStderr.includes('Cannot connect to the Docker daemon')) { // Although version check should catch most, double-check ps stderr
console.warn(`[fetchRemoteDockerStatus] Docker ps command failed on session ${state.ws.sessionId}. Docker unavailable. Stderr: ${psStderr}`); if (psStderr.includes('command not found') || // Should not happen if version check passed
isDockerCmdAvailable = false; psStderr.includes('permission denied') || // Could still happen if permissions differ
psStderr.includes('Cannot connect to the Docker daemon')) { // Should not happen
console.warn(`[fetchRemoteDockerStatus] Docker ps command failed unexpectedly after version check on session ${state.ws.sessionId}. Stderr: ${psStderr.trim()}`);
// Report as available=false, as ps failed critically
return { available: false, containers: [] }; return { available: false, containers: [] };
} else if (psStderr) { } else if (psStderr) {
console.warn(`[fetchRemoteDockerStatus] Docker ps command stderr on session ${state.ws.sessionId}: ${psStderr}`); console.warn(`[fetchRemoteDockerStatus] Docker ps command stderr on session ${state.ws.sessionId}: ${psStderr.trim()}`);
// Continue execution but log the warning // Continue execution but log the warning
} }
// If stdout is empty, there are no containers, which is valid
const lines = psStdout.trim().split('\n'); const lines = psStdout.trim() ? psStdout.trim().split('\n') : [];
allContainers = lines allContainers = lines
.map(line => { .map(line => {
try { try {
@@ -254,26 +306,23 @@ const fetchRemoteDockerStatus = async (state: ClientState): Promise<{ available:
} catch (error: any) { } catch (error: any) {
console.error(`[fetchRemoteDockerStatus] Error executing docker ps for session ${state.ws.sessionId}:`, error); console.error(`[fetchRemoteDockerStatus] Error executing docker ps for session ${state.ws.sessionId}:`, error);
// Check if error indicates docker unavailable // If ps command fails after version check, report as unavailable
const errorMessage = error.message || ''; return { available: false, containers: [] };
if (errorMessage.includes('command not found') || errorMessage.includes('Cannot connect to the Docker daemon')) { // Rethrowing might be too aggressive here, better to report unavailability
isDockerCmdAvailable = false; // throw new Error(`Failed to get remote Docker container list: ${error.message || error}`);
return { available: false, containers: [] };
}
// Otherwise, throw to indicate a more general fetch error
throw new Error(`Failed to get remote Docker container list: ${errorMessage}`);
} }
// If docker ps failed indicating unavailability, return early // --- 3. Get stats for running containers (only if ps was successful) ---
if (!isDockerCmdAvailable) { // Check if there are any containers before running stats
return { available: false, containers: [] }; const runningContainerIds = allContainers.filter(c => c.State === 'running').map(c => c.id);
}
// 2. Get stats for running containers if (runningContainerIds.length > 0) {
try { try {
const statsCommand = "docker stats --no-stream --format '{{json .}}'"; // Construct command to get stats only for running containers
const { stdout: statsStdout, stderr: statsStderr } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { const statsCommand = `docker stats ${runningContainerIds.join(' ')} --no-stream --format '{{json .}}'`;
let stdout = ''; console.log(`[fetchRemoteDockerStatus] Executing: ${statsCommand} on session ${state.ws.sessionId}`);
const { stdout: statsStdout, stderr: statsStderr } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
let stdout = '';
let stderr = ''; let stderr = '';
state.sshClient.exec(statsCommand, { pty: false }, (err, stream) => { state.sshClient.exec(statsCommand, { pty: false }, (err, stream) => {
if (err) return reject(err); if (err) return reject(err);
@@ -287,29 +336,32 @@ const fetchRemoteDockerStatus = async (state: ClientState): Promise<{ available:
}); });
}); });
if (statsStderr) { if (statsStderr) {
// Log stats errors but don't necessarily fail the whole process // Log stats errors but don't necessarily fail the whole process
console.warn(`[fetchRemoteDockerStatus] Docker stats command stderr on session ${state.ws.sessionId}: ${statsStderr}`); console.warn(`[fetchRemoteDockerStatus] Docker stats command stderr on session ${state.ws.sessionId}: ${statsStderr.trim()}`);
}
const statsLines = statsStdout.trim().split('\n');
statsLines.forEach(line => {
try {
const statsData = JSON.parse(line) as DockerStats;
if (statsData.ID) {
// Use the ID from stats data (usually short ID) as the key
statsMap.set(statsData.ID, statsData);
}
} catch (parseError) {
console.error(`[fetchRemoteDockerStatus] Failed to parse stats JSON line for session ${state.ws.sessionId}: ${line}`, parseError);
} }
});
} catch (error: any) { const statsLines = statsStdout.trim() ? statsStdout.trim().split('\n') : [];
// Failure to get stats is not critical, just log and continue statsLines.forEach(line => {
console.warn(`[fetchRemoteDockerStatus] Error executing docker stats for session ${state.ws.sessionId}:`, error); try {
const statsData = JSON.parse(line) as DockerStats;
if (statsData.ID) {
// Use the ID from stats data (usually short ID) as the key
statsMap.set(statsData.ID, statsData);
}
} catch (parseError) {
console.error(`[fetchRemoteDockerStatus] Failed to parse stats JSON line for session ${state.ws.sessionId}: ${line}`, parseError);
}
});
} catch (error: any) {
// Failure to get stats is not critical, just log and continue
console.warn(`[fetchRemoteDockerStatus] Error executing docker stats for session ${state.ws.sessionId}:`, error);
}
} else {
console.log(`[fetchRemoteDockerStatus] No running containers found on session ${state.ws.sessionId}, skipping docker stats.`);
} }
// 3. Merge stats into containers // --- 4. Merge stats into containers ---
allContainers.forEach(container => { allContainers.forEach(container => {
const shortId = container.id.substring(0, 12); // docker stats often uses short ID const shortId = container.id.substring(0, 12); // docker stats often uses short ID
const stats = statsMap.get(container.id) || statsMap.get(shortId); // Try matching long and short ID const stats = statsMap.get(container.id) || statsMap.get(shortId); // Try matching long and short ID
@@ -318,6 +370,7 @@ const fetchRemoteDockerStatus = async (state: ClientState): Promise<{ available:
} }
}); });
// If we reached here, Docker is considered available (version check passed)
return { available: true, containers: allContainers }; return { available: true, containers: allContainers };
}; };
// --- End fetchRemoteDockerStatus function --- // --- End fetchRemoteDockerStatus function ---
@@ -169,7 +169,6 @@ const currentTab = ref<'ui' | 'terminal' | 'background' | 'other'>('ui'); // <--
const handleSaveUiTheme = async () => { const handleSaveUiTheme = async () => {
try { try {
await appearanceStore.saveCustomUiTheme(editableUiTheme.value); await appearanceStore.saveCustomUiTheme(editableUiTheme.value);
alert(t('styleCustomizer.uiThemeSaved')); // 简单提示
} catch (error: any) { } catch (error: any) {
console.error("保存 UI 主题失败:", error); console.error("保存 UI 主题失败:", error);
alert(t('styleCustomizer.uiThemeSaveFailed', { message: error.message })); alert(t('styleCustomizer.uiThemeSaveFailed', { message: error.message }));