update
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
import { ref, readonly, watch, computed, type Ref, type ComputedRef } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useSettingsStore } from '../stores/settings.store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { WebSocketMessage, MessagePayload } from '../types/websocket.types';
|
||||
|
||||
// --- Interfaces (Copied from DockerManager.vue) ---
|
||||
interface PortInfo {
|
||||
IP?: string;
|
||||
PrivatePort: number;
|
||||
PublicPort?: number;
|
||||
Type: 'tcp' | 'udp' | string;
|
||||
}
|
||||
|
||||
export interface DockerContainer { // Exporting for potential use elsewhere
|
||||
id: string;
|
||||
Names: string[];
|
||||
Image: string;
|
||||
ImageID: string;
|
||||
Command: string;
|
||||
Created: number;
|
||||
State: 'created' | 'restarting' | 'running' | 'removing' | 'paused' | 'exited' | 'dead' | string;
|
||||
Status: string;
|
||||
Ports: PortInfo[];
|
||||
Labels: Record<string, string>;
|
||||
stats?: DockerStats | null;
|
||||
}
|
||||
|
||||
export interface DockerStats { // Exporting for potential use elsewhere
|
||||
ID: string;
|
||||
Name: string;
|
||||
CPUPerc: string;
|
||||
MemUsage: string;
|
||||
MemPerc: string;
|
||||
NetIO: string;
|
||||
BlockIO: string;
|
||||
PIDs: string;
|
||||
}
|
||||
|
||||
// --- WebSocket Dependencies Interface ---
|
||||
// Similar to other composables, defining dependencies for WS communication
|
||||
export interface DockerManagerDependencies {
|
||||
sendMessage: (message: WebSocketMessage) => void;
|
||||
onMessage: (type: string, handler: (payload: any, fullMessage?: WebSocketMessage) => void) => () => void;
|
||||
isConnected: ComputedRef<boolean>;
|
||||
// We might need isSshReady or similar if Docker commands depend on SSH being fully ready
|
||||
// For now, isConnected might suffice, assuming WS connection implies SSH readiness for Docker
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Docker manager instance for a specific session.
|
||||
* @param sessionId The unique identifier for the session.
|
||||
* @param wsDeps WebSocket dependencies object.
|
||||
* @param i18n The i18n instance (t function).
|
||||
* @returns Docker manager instance.
|
||||
*/
|
||||
export function createDockerManager(sessionId: string, wsDeps: DockerManagerDependencies, i18n: { t: (key: string, params?: any) => string }) {
|
||||
const { sendMessage, onMessage, isConnected } = wsDeps;
|
||||
const { t } = i18n; // Use the passed i18n instance
|
||||
|
||||
// --- State ---
|
||||
const containers = ref<DockerContainer[]>([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const isDockerAvailable = ref(true); // Assume available until checked
|
||||
const expandedContainerIds = ref<Set<string>>(new Set());
|
||||
const initialLoadDone = ref(false);
|
||||
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let wsUnsubscribeHooks: (() => void)[] = [];
|
||||
|
||||
// --- Settings Store ---
|
||||
// Settings need to be accessed here as well for default expansion
|
||||
const settingsStore = useSettingsStore();
|
||||
const { dockerDefaultExpandBoolean } = storeToRefs(settingsStore);
|
||||
|
||||
// --- Methods ---
|
||||
|
||||
// Clear existing WebSocket listeners
|
||||
const clearWsListeners = () => {
|
||||
if (wsUnsubscribeHooks.length > 0) {
|
||||
console.log(`[DockerManager ${sessionId}] Clearing ${wsUnsubscribeHooks.length} WebSocket listeners.`);
|
||||
wsUnsubscribeHooks.forEach(unsub => unsub());
|
||||
wsUnsubscribeHooks = [];
|
||||
}
|
||||
};
|
||||
|
||||
// Request Docker status via WebSocket
|
||||
const requestDockerStatus = () => {
|
||||
if (!isConnected.value) {
|
||||
console.log(`[DockerManager ${sessionId}] WebSocket not connected, skipping Docker status request.`);
|
||||
// Reset state if disconnected? Or rely on watch(isConnected)?
|
||||
// Let's reset here for immediate feedback if called manually while disconnected.
|
||||
containers.value = [];
|
||||
isLoading.value = false;
|
||||
error.value = t('dockerManager.error.sshDisconnected'); // Use a generic disconnected message
|
||||
isDockerAvailable.value = false;
|
||||
expandedContainerIds.value.clear();
|
||||
initialLoadDone.value = false;
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[DockerManager ${sessionId}] Requesting Docker status.`);
|
||||
isLoading.value = true;
|
||||
error.value = null; // Clear previous error
|
||||
sendMessage({ type: 'docker:get_status', sessionId }); // Ensure sessionId is included if needed by backend routing
|
||||
};
|
||||
|
||||
// Setup WebSocket listeners
|
||||
const setupWsListeners = () => {
|
||||
clearWsListeners(); // Clear previous listeners first
|
||||
if (!isConnected.value) {
|
||||
console.warn(`[DockerManager ${sessionId}] Cannot setup listeners, WebSocket not connected.`);
|
||||
return;
|
||||
}
|
||||
console.log(`[DockerManager ${sessionId}] Setting up WebSocket listeners.`);
|
||||
|
||||
const unsubStatus = onMessage('docker:status:update', (payload, message) => {
|
||||
if (message?.sessionId && message.sessionId !== sessionId) return; // Ignore messages for other sessions
|
||||
console.log(`[DockerManager ${sessionId}] Received docker:status:update`, payload);
|
||||
isLoading.value = false;
|
||||
|
||||
if (payload && typeof payload.available === 'boolean') {
|
||||
isDockerAvailable.value = payload.available;
|
||||
if (payload.available && Array.isArray(payload.containers)) {
|
||||
containers.value = payload.containers as DockerContainer[];
|
||||
error.value = null;
|
||||
|
||||
// Clean up expansion state
|
||||
const currentIds = new Set(containers.value.map(c => c.id));
|
||||
const idsToRemove = new Set<string>();
|
||||
expandedContainerIds.value.forEach(id => {
|
||||
if (!currentIds.has(id)) idsToRemove.add(id);
|
||||
});
|
||||
idsToRemove.forEach(id => expandedContainerIds.value.delete(id));
|
||||
|
||||
// Handle default expand on initial load
|
||||
if (!initialLoadDone.value && dockerDefaultExpandBoolean.value) {
|
||||
console.log(`[DockerManager ${sessionId}] Applying default expand setting.`);
|
||||
containers.value.forEach(container => {
|
||||
if (!expandedContainerIds.value.has(container.id)) {
|
||||
expandedContainerIds.value.add(container.id);
|
||||
}
|
||||
});
|
||||
initialLoadDone.value = true;
|
||||
}
|
||||
} else {
|
||||
containers.value = [];
|
||||
error.value = null;
|
||||
expandedContainerIds.value.clear();
|
||||
if (refreshInterval && !payload.available) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
console.log(`[DockerManager ${sessionId}] Stopped refresh interval due to remote Docker unavailability.`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isDockerAvailable.value = false;
|
||||
containers.value = [];
|
||||
error.value = t('dockerManager.error.invalidResponse');
|
||||
expandedContainerIds.value.clear();
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
});
|
||||
|
||||
const unsubStatusError = onMessage('docker:status:error', (payload, message) => {
|
||||
if (message?.sessionId && message.sessionId !== sessionId) return;
|
||||
console.error(`[DockerManager ${sessionId}] Received docker:status:error`, payload);
|
||||
isLoading.value = false;
|
||||
error.value = payload?.message || t('dockerManager.error.fetchFailed');
|
||||
isDockerAvailable.value = false;
|
||||
containers.value = [];
|
||||
expandedContainerIds.value.clear();
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
});
|
||||
|
||||
const unsubCommandError = onMessage('docker:command:error', (payload, message) => {
|
||||
if (message?.sessionId && message.sessionId !== sessionId) return;
|
||||
console.error(`[DockerManager ${sessionId}] Received docker:command:error`, payload);
|
||||
// How to notify UI? Maybe set an error ref? Or rely on status update?
|
||||
// For now, just log. UI component could show a generic error or use a notification system.
|
||||
// Consider adding a transient commandError ref if needed.
|
||||
alert(`${t('dockerManager.error.commandFailed', { command: payload?.command || '?' })}: ${payload?.message || 'Unknown error'}`);
|
||||
});
|
||||
|
||||
const unsubRequestUpdate = onMessage('request_docker_status_update', (payload, message) => {
|
||||
if (message?.sessionId && message.sessionId !== sessionId) return;
|
||||
console.log(`[DockerManager ${sessionId}] Received request_docker_status_update from backend.`);
|
||||
requestDockerStatus(); // Trigger a status refresh immediately
|
||||
});
|
||||
|
||||
wsUnsubscribeHooks.push(unsubStatus, unsubStatusError, unsubCommandError, unsubRequestUpdate);
|
||||
};
|
||||
|
||||
// Send command for a specific container via WebSocket
|
||||
const sendDockerCommand = (containerId: string, command: 'start' | 'stop' | 'restart' | 'remove') => {
|
||||
if (!isConnected.value) {
|
||||
console.warn(`[DockerManager ${sessionId}] Cannot send command, WebSocket not connected.`);
|
||||
alert(t('dockerManager.error.sshNotConnected')); // Use generic disconnected message
|
||||
return;
|
||||
}
|
||||
if (!isDockerAvailable.value) {
|
||||
console.warn(`[DockerManager ${sessionId}] Cannot send command, remote Docker is not available.`);
|
||||
alert(t('dockerManager.notAvailable'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[DockerManager ${sessionId}] Sending command '${command}' for container ${containerId}`);
|
||||
sendMessage({
|
||||
type: 'docker:command',
|
||||
sessionId, // Include sessionId if needed by backend routing
|
||||
payload: { containerId, command }
|
||||
});
|
||||
// Optionally trigger a status refresh sooner after a command
|
||||
// setTimeout(requestDockerStatus, 500);
|
||||
};
|
||||
|
||||
// Toggle expansion state for a container
|
||||
const toggleExpand = (containerId: string) => {
|
||||
if (expandedContainerIds.value.has(containerId)) {
|
||||
expandedContainerIds.value.delete(containerId);
|
||||
console.log(`[DockerManager ${sessionId}] Collapsed container ${containerId}.`);
|
||||
} else {
|
||||
expandedContainerIds.value.add(containerId);
|
||||
console.log(`[DockerManager ${sessionId}] Expanded container ${containerId}.`);
|
||||
}
|
||||
};
|
||||
|
||||
// --- Lifecycle Management ---
|
||||
|
||||
// Reset state function
|
||||
const resetStateAndInterval = () => {
|
||||
console.log(`[DockerManager ${sessionId}] Resetting state and clearing interval.`);
|
||||
containers.value = [];
|
||||
isLoading.value = false;
|
||||
error.value = null;
|
||||
isDockerAvailable.value = true; // Assume available until checked
|
||||
expandedContainerIds.value.clear();
|
||||
initialLoadDone.value = false;
|
||||
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
console.log(`[DockerManager ${sessionId}] Cleared main refresh interval.`);
|
||||
}
|
||||
clearWsListeners();
|
||||
};
|
||||
|
||||
// Watch for connection changes to manage listeners and interval
|
||||
watch(isConnected, (newIsConnected, oldIsConnected) => {
|
||||
console.log(`[DockerManager ${sessionId}] Connection status changed: ${oldIsConnected} -> ${newIsConnected}`);
|
||||
if (newIsConnected) {
|
||||
// Connection established
|
||||
setupWsListeners();
|
||||
requestDockerStatus(); // Fetch initial status
|
||||
|
||||
// Start refresh interval (consider if backend pushes updates reliably)
|
||||
if (!refreshInterval) {
|
||||
// Keep a safety interval
|
||||
refreshInterval = setInterval(requestDockerStatus, 15000); // Check every 15s
|
||||
console.log(`[DockerManager ${sessionId}] Main refresh interval started (15s).`);
|
||||
}
|
||||
} else {
|
||||
// Connection lost
|
||||
resetStateAndInterval();
|
||||
// Set error state to indicate disconnection
|
||||
error.value = t('dockerManager.error.sshDisconnected');
|
||||
isDockerAvailable.value = false; // Assume unavailable when disconnected
|
||||
}
|
||||
}, { immediate: false }); // Don't run immediately, let initial connect trigger it
|
||||
|
||||
// Cleanup function to be called when the session ends
|
||||
const cleanup = () => {
|
||||
console.log(`[DockerManager ${sessionId}] Cleaning up.`);
|
||||
resetStateAndInterval(); // Clears listeners and interval
|
||||
};
|
||||
|
||||
// --- Initial Setup ---
|
||||
// If already connected when this manager is created, set up listeners and fetch data.
|
||||
// This handles cases where the manager is created after the WS connection is live.
|
||||
if (isConnected.value) {
|
||||
console.log(`[DockerManager ${sessionId}] Initial setup: Already connected.`);
|
||||
setupWsListeners();
|
||||
requestDockerStatus();
|
||||
if (!refreshInterval) {
|
||||
refreshInterval = setInterval(requestDockerStatus, 15000);
|
||||
console.log(`[DockerManager ${sessionId}] Initial setup: Main refresh interval started (15s).`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[DockerManager ${sessionId}] Initial setup: Not connected yet.`);
|
||||
// Set initial state for disconnected status
|
||||
error.value = t('dockerManager.error.sshDisconnected');
|
||||
isDockerAvailable.value = false;
|
||||
}
|
||||
|
||||
|
||||
// --- Exposed Interface ---
|
||||
return {
|
||||
// Readonly State
|
||||
containers: readonly(containers),
|
||||
isLoading: readonly(isLoading),
|
||||
error: readonly(error),
|
||||
isDockerAvailable: readonly(isDockerAvailable),
|
||||
expandedContainerIds: readonly(expandedContainerIds), // UI needs this read-only
|
||||
|
||||
// Methods
|
||||
requestDockerStatus, // Might be useful for manual refresh button in UI
|
||||
sendDockerCommand,
|
||||
toggleExpand, // UI needs this to handle clicks
|
||||
|
||||
// Lifecycle
|
||||
cleanup,
|
||||
};
|
||||
}
|
||||
|
||||
// Export the type of the returned manager instance
|
||||
export type DockerManagerInstance = ReturnType<typeof createDockerManager>;
|
||||
Reference in New Issue
Block a user