agent-ecosystem/src/renderer/store/slices/connectionSlice.ts
matt 7fa2f96ed4 feat(http): implement HTTP server and route handlers for configuration, notifications, projects, sessions, and SSH management
- Introduced an HTTP server to facilitate communication with the application.
- Added route handlers for managing application configuration, including getting and updating settings.
- Implemented notification operations with routes for retrieving, marking, and deleting notifications.
- Created project and session management routes to list projects, sessions, and their details.
- Developed SSH connection management routes for connecting, disconnecting, and retrieving SSH state and configuration.
- Enhanced the application architecture to support real-time event streaming via Server-Sent Events (SSE).

This commit significantly expands the application's capabilities by integrating an HTTP server and various management routes, improving user interaction and functionality.
2026-02-12 15:04:56 +09:00

181 lines
5.2 KiB
TypeScript

/**
* Connection Slice - Manages SSH connection state.
*
* Tracks connection mode (local/ssh), connection state,
* and provides actions for connecting/disconnecting.
*/
import { api } from '@renderer/api';
import { getFullResetState } from '../utils/stateResetHelpers';
import type { AppState } from '../types';
import type {
SshConfigHostEntry,
SshConnectionConfig,
SshConnectionState,
SshLastConnection,
} from '@shared/types';
import type { StateCreator } from 'zustand';
// =============================================================================
// Slice Interface
// =============================================================================
export interface ConnectionSlice {
// State
connectionMode: 'local' | 'ssh';
connectionState: SshConnectionState;
connectedHost: string | null;
connectionError: string | null;
sshConfigHosts: SshConfigHostEntry[];
lastSshConfig: SshLastConnection | null;
// Actions
connectSsh: (config: SshConnectionConfig) => Promise<void>;
disconnectSsh: () => Promise<void>;
testConnection: (config: SshConnectionConfig) => Promise<{ success: boolean; error?: string }>;
setConnectionStatus: (
state: SshConnectionState,
host: string | null,
error: string | null
) => void;
fetchSshConfigHosts: () => Promise<void>;
resolveConfigHost: (alias: string) => Promise<SshConfigHostEntry | null>;
loadLastConnection: () => Promise<void>;
}
// =============================================================================
// Slice Creator
// =============================================================================
export const createConnectionSlice: StateCreator<AppState, [], [], ConnectionSlice> = (
set,
get
) => ({
// Initial state
connectionMode: 'local',
connectionState: 'disconnected',
connectedHost: null,
connectionError: null,
sshConfigHosts: [],
lastSshConfig: null,
// Actions
connectSsh: async (config: SshConnectionConfig): Promise<void> => {
set({
connectionState: 'connecting',
connectedHost: config.host,
connectionError: null,
});
try {
const status = await api.ssh.connect(config);
set({
connectionMode: status.state === 'connected' ? 'ssh' : 'local',
connectionState: status.state,
connectedHost: status.host,
connectionError: status.error,
// Clear stale local selections so dashboard shows fresh remote data
...(status.state === 'connected' ? getFullResetState() : {}),
});
// Re-fetch all data and persist config when connected
if (status.state === 'connected') {
const state = get();
void state.fetchProjects();
void state.fetchRepositoryGroups();
// Save connection config (without password) for form pre-fill on next launch
const saved: SshLastConnection = {
host: config.host,
port: config.port,
username: config.username,
authMethod: config.authMethod,
privateKeyPath: config.privateKeyPath,
};
set({ lastSshConfig: saved });
void api.ssh.saveLastConnection(saved);
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
set({
connectionState: 'error',
connectionError: message,
});
}
},
disconnectSsh: async (): Promise<void> => {
try {
const status = await api.ssh.disconnect();
set({
connectionMode: 'local',
connectionState: status.state,
connectedHost: null,
connectionError: null,
// Clear stale remote selections so dashboard shows fresh local data
...getFullResetState(),
});
// Re-fetch local data
const state = get();
void state.fetchProjects();
void state.fetchRepositoryGroups();
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
set({ connectionError: message });
}
},
testConnection: async (
config: SshConnectionConfig
): Promise<{ success: boolean; error?: string }> => {
try {
return await api.ssh.test(config);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return { success: false, error: message };
}
},
setConnectionStatus: (
state: SshConnectionState,
host: string | null,
error: string | null
): void => {
set({
connectionState: state,
connectionMode: state === 'connected' ? 'ssh' : 'local',
connectedHost: host,
connectionError: error,
});
},
fetchSshConfigHosts: async (): Promise<void> => {
try {
const hosts = await api.ssh.getConfigHosts();
set({ sshConfigHosts: hosts });
} catch {
// Gracefully ignore - SSH config may not exist
set({ sshConfigHosts: [] });
}
},
resolveConfigHost: async (alias: string): Promise<SshConfigHostEntry | null> => {
try {
return await api.ssh.resolveHost(alias);
} catch {
return null;
}
},
loadLastConnection: async (): Promise<void> => {
try {
const saved = await api.ssh.getLastConnection();
set({ lastSshConfig: saved });
} catch {
// Gracefully ignore - no saved connection
}
},
});