Critical path wired end-to-end:
- ConnectSSH on WraithApp resolves credentials from vault, builds auth methods
- SSH output handler emits Wails events (base64) to frontend
- useTerminal.ts forwards keystrokes to SSHService.Write, resize to SSHService.Resize
- useTerminal.ts listens for ssh:data:{sessionId} events and writes to xterm.js
- session.store.ts connect() calls real Go ConnectSSH, not mock
- useSftp.ts calls real SFTPService.List instead of hardcoded mock data
- SFTP client auto-registered on SSH connection via pkg/sftp
- DisconnectSession cleans up both SSH and SFTP
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
import { defineStore } from "pinia";
|
|
import { ref, computed } from "vue";
|
|
import { Call } from "@wailsio/runtime";
|
|
import { useConnectionStore } from "@/stores/connection.store";
|
|
|
|
const APP = "github.com/vstockwell/wraith/internal/app.WraithApp";
|
|
|
|
export interface Session {
|
|
id: string;
|
|
connectionId: number;
|
|
name: string;
|
|
protocol: "ssh" | "rdp";
|
|
active: boolean;
|
|
}
|
|
|
|
export const useSessionStore = defineStore("session", () => {
|
|
const sessions = ref<Session[]>([]);
|
|
const activeSessionId = ref<string | null>(null);
|
|
const connecting = ref(false);
|
|
|
|
const activeSession = computed(() =>
|
|
sessions.value.find((s) => s.id === activeSessionId.value) ?? null,
|
|
);
|
|
|
|
const sessionCount = computed(() => sessions.value.length);
|
|
|
|
function activateSession(id: string): void {
|
|
activeSessionId.value = id;
|
|
}
|
|
|
|
async function closeSession(id: string): Promise<void> {
|
|
const idx = sessions.value.findIndex((s) => s.id === id);
|
|
if (idx === -1) return;
|
|
|
|
const session = sessions.value[idx];
|
|
|
|
// Disconnect the backend session
|
|
try {
|
|
await Call.ByName(`${APP}.DisconnectSession`, session.id);
|
|
} catch (err) {
|
|
console.error("Failed to disconnect session:", err);
|
|
}
|
|
|
|
sessions.value.splice(idx, 1);
|
|
|
|
if (activeSessionId.value === id) {
|
|
if (sessions.value.length === 0) {
|
|
activeSessionId.value = null;
|
|
} else {
|
|
const nextIdx = Math.min(idx, sessions.value.length - 1);
|
|
activeSessionId.value = sessions.value[nextIdx].id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect to a server by connection ID.
|
|
* Calls the real Go backend to establish an SSH or RDP session.
|
|
*/
|
|
async function connect(connectionId: number): Promise<void> {
|
|
const connectionStore = useConnectionStore();
|
|
const conn = connectionStore.connections.find((c) => c.id === connectionId);
|
|
if (!conn) return;
|
|
|
|
// Check if there's already an active session for this connection
|
|
const existing = sessions.value.find((s) => s.connectionId === connectionId);
|
|
if (existing) {
|
|
activeSessionId.value = existing.id;
|
|
return;
|
|
}
|
|
|
|
connecting.value = true;
|
|
try {
|
|
if (conn.protocol === "ssh") {
|
|
// Call Go backend — resolves credentials, builds auth, returns sessionID
|
|
const sessionId = await Call.ByName(
|
|
`${APP}.ConnectSSH`,
|
|
connectionId,
|
|
120, // cols (will be resized by xterm.js fit addon)
|
|
40, // rows
|
|
) as string;
|
|
|
|
sessions.value.push({
|
|
id: sessionId,
|
|
connectionId,
|
|
name: conn.name,
|
|
protocol: "ssh",
|
|
active: true,
|
|
});
|
|
activeSessionId.value = sessionId;
|
|
} else if (conn.protocol === "rdp") {
|
|
// TODO: Wire RDP connect when ready
|
|
console.warn("RDP connections not yet wired");
|
|
}
|
|
} catch (err) {
|
|
console.error("Connection failed:", err);
|
|
// TODO: Show error toast in UI
|
|
} finally {
|
|
connecting.value = false;
|
|
}
|
|
}
|
|
|
|
return {
|
|
sessions,
|
|
activeSessionId,
|
|
activeSession,
|
|
sessionCount,
|
|
connecting,
|
|
activateSession,
|
|
closeSession,
|
|
connect,
|
|
};
|
|
});
|