import { Component, computed, inject, signal } from '@angular/core'; import { NgIcon } from '@ng-icons/core'; import type { SavedConnection } from '@shared/types'; import { ConnectionsService } from '../connections.service'; import { DialogsService } from '../dialogs.service'; import { PanelsService } from '../panels.service'; interface Draft { id: string; name: string; folderId: string | null; host: string; port: number; username: string; authKind: 'password' | 'key'; password: string; privateKeyPath: string; passphrase: string; initialPath: string; } function toDraft(c: SavedConnection): Draft { return { id: c.id, name: c.name, folderId: c.folderId, host: c.host, port: c.port, username: c.username, authKind: c.auth.kind, password: c.auth.kind === 'password' ? c.auth.password : '', privateKeyPath: c.auth.kind === 'key' ? c.auth.privateKeyPath : '', passphrase: c.auth.kind === 'key' ? (c.auth.passphrase ?? '') : '', initialPath: c.initialPath ?? '', }; } function fromDraft(d: Draft): SavedConnection { return { id: d.id, name: d.name.trim() || `${d.username}@${d.host}`, folderId: d.folderId, host: d.host.trim(), port: d.port || 22, username: d.username.trim(), auth: d.authKind === 'password' ? { kind: 'password', password: d.password } : { kind: 'key', privateKeyPath: d.privateKeyPath.trim(), ...(d.passphrase ? { passphrase: d.passphrase } : {}), }, initialPath: d.initialPath.trim() || null, }; } @Component({ selector: 'app-connection-manager', imports: [NgIcon], templateUrl: './connection-manager.html', }) export class ConnectionManager { readonly store = inject(ConnectionsService); readonly dialogs = inject(DialogsService); readonly panels = inject(PanelsService); readonly draft = signal(null); readonly busy = signal(false); readonly valid = computed(() => { const d = this.draft(); if (!d) return false; if (!d.host.trim() || !d.username.trim()) return false; if (d.authKind === 'key' && !d.privateKeyPath.trim()) return false; return true; }); patch(key: K, value: Draft[K]): void { this.draft.update((d) => (d ? { ...d, [key]: value } : d)); } newConnection(): void { this.draft.set({ id: '', name: '', folderId: null, host: '', port: 22, username: 'root', authKind: 'password', password: '', privateKeyPath: '', passphrase: '', initialPath: '', }); } edit(connection: SavedConnection): void { this.draft.set(toDraft(connection)); } close(): void { this.dialogs.manager.set(false); this.panels.refocus(); } async save(): Promise { const d = this.draft(); if (!d || !this.valid()) return null; const id = await this.store.upsert(fromDraft(d)); if (id) this.patch('id', id); return id; } async saveAndConnect(): Promise { const d = this.draft(); if (!d) return; this.busy.set(true); try { const id = await this.save(); if (!id) return; const connection = fromDraft({ ...d, id }); const ok = await this.panels.connectPanel( this.panels.activeSide(), connection, connection.name, ); if (ok) this.close(); } finally { this.busy.set(false); } } deleteCurrent(): void { const d = this.draft(); if (!d?.id) { this.draft.set(null); return; } this.dialogs.confirm.set({ title: 'Delete connection', body: `Delete "${d.name || d.host}"?`, confirmLabel: 'Delete', destructive: true, onConfirm: async () => { await this.store.remove(d.id); this.draft.set(null); }, }); } newFolder(): void { this.dialogs.prompt.set({ title: 'New folder', placeholder: 'folder name', confirmLabel: 'Create', onConfirm: (name) => void this.store.upsertFolder({ id: '', name }), }); } deleteFolder(id: string, name: string): void { this.dialogs.confirm.set({ title: 'Delete folder', body: `Delete folder "${name}"? Its connections move to the root.`, confirmLabel: 'Delete', destructive: true, onConfirm: () => void this.store.removeFolder(id), }); } }