feat(02-03): add SSH profile management to ConfigManager
- Add SSH connection profile management methods to ConfigManager - addSshProfile(): Store connection profiles for quick reconnection - removeSshProfile(): Remove profiles by ID - updateSshProfile(): Update existing profiles - getSshProfiles(): Retrieve all profiles - setLastActiveContextId(): Persist last active context for app restart - profiles array and lastActiveContextId field in ssh config section - Context API already existed from plan 02-02 with all required IPC handlers and preload bridge
This commit is contained in:
parent
4aafc9c4fc
commit
4921c610c4
3 changed files with 126 additions and 0 deletions
|
|
@ -18,6 +18,7 @@ import * as path from 'path';
|
|||
import { DEFAULT_TRIGGERS, TriggerManager } from './TriggerManager';
|
||||
|
||||
import type { TriggerColor } from '@shared/constants/triggerColors';
|
||||
import type { SshConnectionProfile } from '@shared/types/api';
|
||||
|
||||
const logger = createLogger('Service:ConfigManager');
|
||||
|
||||
|
|
@ -199,6 +200,8 @@ export interface SshPersistConfig {
|
|||
privateKeyPath?: string;
|
||||
} | null;
|
||||
autoReconnect: boolean;
|
||||
profiles: SshConnectionProfile[];
|
||||
lastActiveContextId: string;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
|
|
@ -247,6 +250,8 @@ const DEFAULT_CONFIG: AppConfig = {
|
|||
ssh: {
|
||||
lastConnection: null,
|
||||
autoReconnect: false,
|
||||
profiles: [],
|
||||
lastActiveContextId: 'local',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -664,6 +669,77 @@ export class ConfigManager {
|
|||
this.saveConfig();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// SSH Profile Management
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Adds an SSH connection profile.
|
||||
* @param profile - The SSH connection profile to add
|
||||
*/
|
||||
addSshProfile(profile: SshConnectionProfile): void {
|
||||
// Check for duplicates by ID
|
||||
if (this.config.ssh.profiles.some((p) => p.id === profile.id)) {
|
||||
logger.warn(`SSH profile with ID ${profile.id} already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.config.ssh.profiles.push(profile);
|
||||
this.saveConfig();
|
||||
logger.info(`SSH profile added: ${profile.name} (${profile.id})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an SSH connection profile by ID.
|
||||
* @param profileId - The profile ID to remove
|
||||
*/
|
||||
removeSshProfile(profileId: string): void {
|
||||
const index = this.config.ssh.profiles.findIndex((p) => p.id === profileId);
|
||||
if (index === -1) {
|
||||
logger.warn(`SSH profile not found: ${profileId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const removed = this.config.ssh.profiles.splice(index, 1)[0];
|
||||
this.saveConfig();
|
||||
logger.info(`SSH profile removed: ${removed.name} (${profileId})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing SSH connection profile.
|
||||
* @param profileId - The profile ID to update
|
||||
* @param updates - Partial profile data to merge
|
||||
*/
|
||||
updateSshProfile(profileId: string, updates: Partial<SshConnectionProfile>): void {
|
||||
const profile = this.config.ssh.profiles.find((p) => p.id === profileId);
|
||||
if (!profile) {
|
||||
logger.warn(`SSH profile not found: ${profileId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(profile, updates);
|
||||
this.saveConfig();
|
||||
logger.info(`SSH profile updated: ${profile.name} (${profileId})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all SSH connection profiles.
|
||||
* @returns Array of SSH connection profiles
|
||||
*/
|
||||
getSshProfiles(): SshConnectionProfile[] {
|
||||
return this.deepClone(this.config.ssh.profiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last active context ID (for restoration on app restart).
|
||||
* @param contextId - The context ID that was active
|
||||
*/
|
||||
setLastActiveContextId(contextId: string): void {
|
||||
this.config.ssh.lastActiveContextId = contextId;
|
||||
this.saveConfig();
|
||||
logger.info(`Last active context ID saved: ${contextId}`);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Utility Methods
|
||||
// ===========================================================================
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import { WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL } from '@shared/constants';
|
|||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
import {
|
||||
CONTEXT_CHANGED,
|
||||
CONTEXT_GET_ACTIVE,
|
||||
CONTEXT_LIST,
|
||||
CONTEXT_SWITCH,
|
||||
SSH_CONNECT,
|
||||
SSH_DISCONNECT,
|
||||
SSH_GET_CONFIG_HOSTS,
|
||||
|
|
@ -38,6 +42,7 @@ import {
|
|||
|
||||
import type {
|
||||
AppConfig,
|
||||
ContextInfo,
|
||||
ElectronAPI,
|
||||
NotificationTrigger,
|
||||
SessionsPaginationOptions,
|
||||
|
|
@ -363,6 +368,31 @@ const electronAPI: ElectronAPI = {
|
|||
};
|
||||
},
|
||||
},
|
||||
|
||||
// Context API
|
||||
context: {
|
||||
list: async (): Promise<ContextInfo[]> => {
|
||||
return invokeIpcWithResult<ContextInfo[]>(CONTEXT_LIST);
|
||||
},
|
||||
getActive: async (): Promise<string> => {
|
||||
return invokeIpcWithResult<string>(CONTEXT_GET_ACTIVE);
|
||||
},
|
||||
switch: async (contextId: string): Promise<{ contextId: string }> => {
|
||||
return invokeIpcWithResult<{ contextId: string }>(CONTEXT_SWITCH, contextId);
|
||||
},
|
||||
onChanged: (callback: (event: unknown, data: ContextInfo) => void): (() => void) => {
|
||||
ipcRenderer.on(
|
||||
CONTEXT_CHANGED,
|
||||
callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
|
||||
);
|
||||
return (): void => {
|
||||
ipcRenderer.removeListener(
|
||||
CONTEXT_CHANGED,
|
||||
callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
|
||||
);
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Use contextBridge to securely expose the API to the renderer process
|
||||
|
|
|
|||
|
|
@ -144,6 +144,18 @@ export interface UpdaterAPI {
|
|||
onStatus: (callback: (event: unknown, status: unknown) => void) => () => void;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Context API
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Context information for listing available contexts.
|
||||
*/
|
||||
export interface ContextInfo {
|
||||
id: string;
|
||||
type: 'local' | 'ssh';
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SSH API
|
||||
// =============================================================================
|
||||
|
|
@ -314,6 +326,14 @@ export interface ElectronAPI {
|
|||
|
||||
// SSH API
|
||||
ssh: SshAPI;
|
||||
|
||||
// Context API
|
||||
context: {
|
||||
list: () => Promise<ContextInfo[]>;
|
||||
getActive: () => Promise<string>;
|
||||
switch: (contextId: string) => Promise<{ contextId: string }>;
|
||||
onChanged: (callback: (event: unknown, data: ContextInfo) => void) => () => void;
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue