wraith/src/stores/connection.store.ts
Vantz Stockwell 737491d3f0 feat: Phase 2 complete — SSH terminal + frontend UI
Rust SSH service: russh async client, DashMap session registry,
TOFU host key verification, CWD tracking via separate exec channel
(never touches terminal stream), base64 event emission for terminal
I/O. 52/52 tests passing.

Vue 3 frontend: ported from Wails v3 to Tauri v2 — useTerminal
composable with streaming TextDecoder + rAF batching, session store
with multi-connection support, connection store/tree, sidebar, tab
bar, status bar, keyboard shortcuts. All Wails imports replaced
with Tauri API equivalents.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:28:18 -04:00

111 lines
2.9 KiB
TypeScript

import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { invoke } from "@tauri-apps/api/core";
export interface Connection {
id: number;
name: string;
hostname: string;
port: number;
protocol: "ssh" | "rdp";
groupId: number | null;
credentialId?: number | null;
color?: string;
tags?: string[];
notes?: string;
options?: string;
sortOrder?: number;
lastConnected?: string | null;
createdAt?: string;
updatedAt?: string;
}
export interface Group {
id: number;
name: string;
parentId: number | null;
sortOrder?: number;
icon?: string;
children?: Group[];
}
/**
* Connection store.
* Manages connections, groups, and search state.
* Loads data from the Rust backend via Tauri invoke.
*/
export const useConnectionStore = defineStore("connection", () => {
const connections = ref<Connection[]>([]);
const groups = ref<Group[]>([]);
const searchQuery = ref("");
/** Filter connections by search query. */
const filteredConnections = computed(() => {
const q = searchQuery.value.toLowerCase().trim();
if (!q) return connections.value;
return connections.value.filter(
(c) =>
c.name.toLowerCase().includes(q) ||
c.hostname.toLowerCase().includes(q) ||
c.tags?.some((t) => t.toLowerCase().includes(q)),
);
});
/** Get connections belonging to a specific group. */
function connectionsByGroup(groupId: number): Connection[] {
const q = searchQuery.value.toLowerCase().trim();
const groupConns = connections.value.filter((c) => c.groupId === groupId);
if (!q) return groupConns;
return groupConns.filter(
(c) =>
c.name.toLowerCase().includes(q) ||
c.hostname.toLowerCase().includes(q) ||
c.tags?.some((t) => t.toLowerCase().includes(q)),
);
}
/** Check if a group has any matching connections (for search filtering). */
function groupHasResults(groupId: number): boolean {
return connectionsByGroup(groupId).length > 0;
}
/** Load connections from the Rust backend. */
async function loadConnections(): Promise<void> {
try {
const conns = await invoke<Connection[]>("list_connections");
connections.value = conns || [];
} catch (err) {
console.error("Failed to load connections:", err);
connections.value = [];
}
}
/** Load groups from the Rust backend. */
async function loadGroups(): Promise<void> {
try {
const grps = await invoke<Group[]>("list_groups");
groups.value = grps || [];
} catch (err) {
console.error("Failed to load groups:", err);
groups.value = [];
}
}
/** Load both connections and groups from the Rust backend. */
async function loadAll(): Promise<void> {
await Promise.all([loadConnections(), loadGroups()]);
}
return {
connections,
groups,
searchQuery,
filteredConnections,
connectionsByGroup,
groupHasResults,
loadConnections,
loadGroups,
loadAll,
};
});