P4: SFTP provider, connection store, connection manager UI
- SftpProvider (ssh2): password + key auth, keepalive, clean-dispose vs unexpected-close distinction, POSIX path helpers, s->ms attr normalization - ConnectionStore: plain JSON at appDataDir, 0600, atomic writes, folders - connect/disconnect + store CRUD handlers; sessionClosed push -> panels fall back to local and in-flight jobs fail - UI: session dropdown per panel (local/saved connections/manage), disconnect button, connection manager modal (folders, edit form, save/connect) - electrobun bundler: external cpu-features/*.node (ssh2 optional natives) - RpcService: gate requests on RPC socket open; defer initial panel loads past first render - verified against 192.168.10.122: full op matrix, upload/download/remote-remote recursive transfers, roundtrip integrity, recursive remote delete, store CRUD Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,9 @@ import {
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import type { FileEntry } from '@shared/types';
|
||||
import type { FileEntry, SavedConnection } from '@shared/types';
|
||||
import { ConnectionsService } from '../connections.service';
|
||||
import { DialogsService } from '../dialogs.service';
|
||||
import { FileActionsService } from '../file-actions.service';
|
||||
import { formatDate, formatSize } from '../format';
|
||||
import { PanelsService, type Side, type SortKey } from '../panels.service';
|
||||
@@ -27,10 +29,14 @@ export class FilePanel {
|
||||
readonly panels = inject(PanelsService);
|
||||
readonly actions = inject(FileActionsService);
|
||||
readonly transfers = inject(TransfersService);
|
||||
readonly connections = inject(ConnectionsService);
|
||||
readonly dialogs = inject(DialogsService);
|
||||
readonly panel = computed(() => this.panels.get(this.side()));
|
||||
readonly isActive = computed(() => this.panels.activeSide() === this.side());
|
||||
readonly isRemote = computed(() => this.panel().sessionId() !== 'local');
|
||||
|
||||
readonly pathDraft = signal('');
|
||||
readonly menuOpen = signal(false);
|
||||
|
||||
private readonly listEl = viewChild.required<ElementRef<HTMLDivElement>>('list');
|
||||
private lastClickedIndex = 0;
|
||||
@@ -52,6 +58,27 @@ export class FilePanel {
|
||||
this.panels.activeSide.set(this.side());
|
||||
}
|
||||
|
||||
pickLocal(): void {
|
||||
this.menuOpen.set(false);
|
||||
void this.panels.disconnectPanel(this.side());
|
||||
}
|
||||
|
||||
pickConnection(connection: SavedConnection): void {
|
||||
this.menuOpen.set(false);
|
||||
this.activate();
|
||||
void this.panels.connectPanel(this.side(), connection, connection.name);
|
||||
}
|
||||
|
||||
openManager(): void {
|
||||
this.menuOpen.set(false);
|
||||
this.activate();
|
||||
this.dialogs.manager.set(true);
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
void this.panels.disconnectPanel(this.side());
|
||||
}
|
||||
|
||||
iconFor(entry: FileEntry): string {
|
||||
return entry.type === 'dir' ? 'mynaFolder' : 'mynaFile';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user