All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
- ConnectSSH returns NO_CREDENTIALS error when no credential is stored - Frontend catches auth failures and prompts for username/password - ConnectSSHWithPassword method for ad-hoc password auth - Version loaded from Go backend (build-time -ldflags) in settings + unlock screen - Connection errors shown as alert() instead of silent console.error - Added UpdateService.CurrentVersion() and WraithApp.GetVersion() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
158 lines
4.6 KiB
TypeScript
158 lines
4.6 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 lastError = ref<string | null>(null);
|
|
|
|
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") {
|
|
let sessionId: string;
|
|
|
|
try {
|
|
// Try with stored credentials first
|
|
sessionId = await Call.ByName(
|
|
`${APP}.ConnectSSH`,
|
|
connectionId,
|
|
120, // cols (will be resized by xterm.js fit addon)
|
|
40, // rows
|
|
) as string;
|
|
} catch (sshErr: any) {
|
|
const errMsg = typeof sshErr === "string" ? sshErr : sshErr?.message ?? String(sshErr);
|
|
|
|
// If no credentials, prompt for username/password
|
|
if (errMsg.includes("NO_CREDENTIALS") || errMsg.includes("unable to authenticate")) {
|
|
const username = prompt(`Username for ${conn.hostname}:`, "root");
|
|
if (!username) throw new Error("Connection cancelled");
|
|
const password = prompt(`Password for ${username}@${conn.hostname}:`);
|
|
if (password === null) throw new Error("Connection cancelled");
|
|
|
|
sessionId = await Call.ByName(
|
|
`${APP}.ConnectSSHWithPassword`,
|
|
connectionId,
|
|
username,
|
|
password,
|
|
120,
|
|
40,
|
|
) as string;
|
|
} else {
|
|
throw sshErr;
|
|
}
|
|
}
|
|
|
|
sessions.value.push({
|
|
id: sessionId,
|
|
connectionId,
|
|
name: conn.name,
|
|
protocol: "ssh",
|
|
active: true,
|
|
});
|
|
activeSessionId.value = sessionId;
|
|
} else if (conn.protocol === "rdp") {
|
|
// Call Go backend — resolves credentials, builds RDPConfig, returns sessionID
|
|
const sessionId = await Call.ByName(
|
|
`${APP}.ConnectRDP`,
|
|
connectionId,
|
|
1920, // initial width — resized by the RDP view on mount
|
|
1080, // initial height
|
|
) as string;
|
|
|
|
sessions.value.push({
|
|
id: sessionId,
|
|
connectionId,
|
|
name: conn.name,
|
|
protocol: "rdp",
|
|
active: true,
|
|
});
|
|
activeSessionId.value = sessionId;
|
|
}
|
|
} catch (err: any) {
|
|
const msg = typeof err === "string" ? err : err?.message ?? String(err);
|
|
console.error("Connection failed:", msg);
|
|
lastError.value = msg;
|
|
// Show error as native alert so it's visible without DevTools
|
|
alert(`Connection failed: ${msg}`);
|
|
} finally {
|
|
connecting.value = false;
|
|
}
|
|
}
|
|
|
|
return {
|
|
sessions,
|
|
activeSessionId,
|
|
activeSession,
|
|
sessionCount,
|
|
connecting,
|
|
lastError,
|
|
activateSession,
|
|
closeSession,
|
|
connect,
|
|
};
|
|
});
|