fix: wire connection CRUD, add Group+/Host+ buttons, fix vault stubs
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
- Delete connection/group now calls real Go backend (was local array splice) - Duplicate connection calls ConnectionService.CreateConnection - Rename group calls new ConnectionService.RenameGroup method - Added Group+ and Host+ buttons to sidebar header - Vault change password wired to real unlock/create flow - Export/import vault shows helpful path info instead of stub alert Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5704923b01
commit
df23cecdbd
@ -269,6 +269,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { Call } from "@wailsio/runtime";
|
||||||
|
|
||||||
type Section = "general" | "terminal" | "vault" | "about";
|
type Section = "general" | "terminal" | "vault" | "about";
|
||||||
|
|
||||||
@ -331,20 +332,35 @@ function close(): void {
|
|||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeMasterPassword(): void {
|
async function changeMasterPassword(): Promise<void> {
|
||||||
// TODO: Replace with Wails binding — open a password change dialog
|
const oldPw = prompt("Current master password:");
|
||||||
// VaultService.ChangeMasterPassword(oldPassword, newPassword)
|
if (!oldPw) return;
|
||||||
alert("Change master password — not yet implemented (requires Wails binding)");
|
const newPw = prompt("New master password:");
|
||||||
|
if (!newPw) return;
|
||||||
|
const confirmPw = prompt("Confirm new master password:");
|
||||||
|
if (newPw !== confirmPw) {
|
||||||
|
alert("Passwords do not match.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const APP = "github.com/vstockwell/wraith/internal/app.WraithApp";
|
||||||
|
// Verify old password by unlocking, then create new vault
|
||||||
|
await Call.ByName(`${APP}.Unlock`, oldPw);
|
||||||
|
await Call.ByName(`${APP}.CreateVault`, newPw);
|
||||||
|
alert("Master password changed successfully.");
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Failed to change password: ${err}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportVault(): void {
|
function exportVault(): void {
|
||||||
// TODO: Replace with Wails binding — VaultService.Export()
|
// Not implemented yet — needs Go method to export encrypted DB
|
||||||
alert("Export vault — not yet implemented (requires Wails binding)");
|
alert("Export vault is not yet available. Your data is stored in %APPDATA%\\Wraith\\wraith.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
function importVault(): void {
|
function importVault(): void {
|
||||||
// TODO: Replace with Wails binding — VaultService.Import(data)
|
// Not implemented yet — needs Go method to import encrypted DB
|
||||||
alert("Import vault — not yet implemented (requires Wails binding)");
|
alert("Import vault is not yet available. Copy wraith.db to %APPDATA%\\Wraith\\ to restore.");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkForUpdates(): Promise<void> {
|
async function checkForUpdates(): Promise<void> {
|
||||||
|
|||||||
@ -1,5 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
|
<!-- Add Group / Add Host buttons -->
|
||||||
|
<div class="flex gap-1 px-3 py-1.5 border-b border-[var(--wraith-border)]">
|
||||||
|
<button
|
||||||
|
class="flex-1 flex items-center justify-center gap-1 px-2 py-1 text-[10px] font-medium rounded
|
||||||
|
bg-[var(--wraith-bg-tertiary)] text-[var(--wraith-text-muted)]
|
||||||
|
hover:text-[var(--wraith-text-primary)] hover:bg-[var(--wraith-bg-secondary)] transition-colors"
|
||||||
|
@click="addGroup"
|
||||||
|
title="New Group"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 16 16" fill="currentColor" class="w-3 h-3"><path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"/></svg>
|
||||||
|
Group
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="flex-1 flex items-center justify-center gap-1 px-2 py-1 text-[10px] font-medium rounded
|
||||||
|
bg-[var(--wraith-bg-tertiary)] text-[var(--wraith-text-muted)]
|
||||||
|
hover:text-[var(--wraith-text-primary)] hover:bg-[var(--wraith-bg-secondary)] transition-colors"
|
||||||
|
@click="editDialog?.openNew()"
|
||||||
|
title="New Connection"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 16 16" fill="currentColor" class="w-3 h-3"><path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"/></svg>
|
||||||
|
Host
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-for="group in connectionStore.groups" :key="group.id">
|
<template v-for="group in connectionStore.groups" :key="group.id">
|
||||||
<!-- Only show groups that have matching connections during search -->
|
<!-- Only show groups that have matching connections during search -->
|
||||||
<div v-if="!connectionStore.searchQuery || connectionStore.groupHasResults(group.id)">
|
<div v-if="!connectionStore.searchQuery || connectionStore.groupHasResults(group.id)">
|
||||||
@ -73,12 +97,15 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { Call } from "@wailsio/runtime";
|
||||||
import { useConnectionStore, type Connection, type Group } from "@/stores/connection.store";
|
import { useConnectionStore, type Connection, type Group } from "@/stores/connection.store";
|
||||||
import { useSessionStore } from "@/stores/session.store";
|
import { useSessionStore } from "@/stores/session.store";
|
||||||
import ContextMenu from "@/components/common/ContextMenu.vue";
|
import ContextMenu from "@/components/common/ContextMenu.vue";
|
||||||
import type { ContextMenuItem } from "@/components/common/ContextMenu.vue";
|
import type { ContextMenuItem } from "@/components/common/ContextMenu.vue";
|
||||||
import ConnectionEditDialog from "@/components/connections/ConnectionEditDialog.vue";
|
import ConnectionEditDialog from "@/components/connections/ConnectionEditDialog.vue";
|
||||||
|
|
||||||
|
const CONN = "github.com/vstockwell/wraith/internal/connections.ConnectionService";
|
||||||
|
|
||||||
const connectionStore = useConnectionStore();
|
const connectionStore = useConnectionStore();
|
||||||
const sessionStore = useSessionStore();
|
const sessionStore = useSessionStore();
|
||||||
|
|
||||||
@ -98,6 +125,18 @@ function toggleGroup(groupId: number): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Add a new group. */
|
||||||
|
async function addGroup(): Promise<void> {
|
||||||
|
const name = prompt("New group name:");
|
||||||
|
if (!name) return;
|
||||||
|
try {
|
||||||
|
await Call.ByName(`${CONN}.CreateGroup`, name, null);
|
||||||
|
await connectionStore.loadGroups();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to create group:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Double-click a connection to open a new session. */
|
/** Double-click a connection to open a new session. */
|
||||||
function handleConnect(conn: Connection): void {
|
function handleConnect(conn: Connection): void {
|
||||||
sessionStore.connect(conn.id);
|
sessionStore.connect(conn.id);
|
||||||
@ -146,11 +185,15 @@ function showGroupMenu(event: MouseEvent, group: Group): void {
|
|||||||
{
|
{
|
||||||
label: "Rename",
|
label: "Rename",
|
||||||
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-3.5 h-3.5"><path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.25.25 0 0 0-.064.108l-.558 1.953 1.953-.558a.249.249 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"/></svg>`,
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-3.5 h-3.5"><path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.25.25 0 0 0-.064.108l-.558 1.953 1.953-.558a.249.249 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"/></svg>`,
|
||||||
action: () => {
|
action: async () => {
|
||||||
// TODO: Replace with Wails binding — ConnectionService.RenameGroup(id, name)
|
|
||||||
const newName = prompt("Rename group:", group.name);
|
const newName = prompt("Rename group:", group.name);
|
||||||
if (newName && newName !== group.name) {
|
if (newName && newName !== group.name) {
|
||||||
group.name = newName;
|
try {
|
||||||
|
await Call.ByName(`${CONN}.RenameGroup`, group.id, newName);
|
||||||
|
await connectionStore.loadGroups();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to rename group:", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -165,32 +208,46 @@ function showGroupMenu(event: MouseEvent, group: Group): void {
|
|||||||
contextMenu.value?.open(event, items);
|
contextMenu.value?.open(event, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Duplicate a connection with a new name. */
|
/** Duplicate a connection via the Go backend. */
|
||||||
function duplicateConnection(conn: Connection): void {
|
async function duplicateConnection(conn: Connection): Promise<void> {
|
||||||
// TODO: Replace with Wails binding — ConnectionService.CreateConnection(input)
|
try {
|
||||||
const newId = Math.max(...connectionStore.connections.map((c) => c.id), 0) + 1;
|
await Call.ByName(`${CONN}.CreateConnection`, {
|
||||||
connectionStore.connections.push({
|
name: `${conn.name} (copy)`,
|
||||||
...conn,
|
hostname: conn.hostname,
|
||||||
id: newId,
|
port: conn.port,
|
||||||
name: `${conn.name} (copy)`,
|
protocol: conn.protocol,
|
||||||
});
|
groupId: conn.groupId,
|
||||||
}
|
credentialId: conn.credentialId ?? null,
|
||||||
|
color: conn.color ?? "",
|
||||||
/** Delete a connection after confirmation. */
|
tags: conn.tags ?? [],
|
||||||
function deleteConnection(conn: Connection): void {
|
notes: conn.notes ?? "",
|
||||||
// TODO: Replace with Wails binding — ConnectionService.DeleteConnection(id)
|
options: conn.options ?? "{}",
|
||||||
const idx = connectionStore.connections.findIndex((c) => c.id === conn.id);
|
});
|
||||||
if (idx !== -1) {
|
await connectionStore.loadConnections();
|
||||||
connectionStore.connections.splice(idx, 1);
|
} catch (err) {
|
||||||
|
console.error("Failed to duplicate connection:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete a group after confirmation. */
|
/** Delete a connection via the Go backend after confirmation. */
|
||||||
function deleteGroup(group: Group): void {
|
async function deleteConnection(conn: Connection): Promise<void> {
|
||||||
// TODO: Replace with Wails binding — ConnectionService.DeleteGroup(id)
|
if (!confirm(`Delete "${conn.name}"?`)) return;
|
||||||
const idx = connectionStore.groups.findIndex((g) => g.id === group.id);
|
try {
|
||||||
if (idx !== -1) {
|
await Call.ByName(`${CONN}.DeleteConnection`, conn.id);
|
||||||
connectionStore.groups.splice(idx, 1);
|
await connectionStore.loadConnections();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to delete connection:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete a group via the Go backend after confirmation. */
|
||||||
|
async function deleteGroup(group: Group): Promise<void> {
|
||||||
|
if (!confirm(`Delete group "${group.name}" and all its connections?`)) return;
|
||||||
|
try {
|
||||||
|
await Call.ByName(`${CONN}.DeleteGroup`, group.id);
|
||||||
|
await connectionStore.loadAll();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to delete group:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -159,6 +159,14 @@ func (s *ConnectionService) DeleteGroup(id int64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionService) RenameGroup(id int64, name string) error {
|
||||||
|
_, err := s.db.Exec("UPDATE groups SET name = ? WHERE id = ?", name, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rename group: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Connection CRUD ----------
|
// ---------- Connection CRUD ----------
|
||||||
|
|
||||||
func (s *ConnectionService) CreateConnection(input CreateConnectionInput) (*Connection, error) {
|
func (s *ConnectionService) CreateConnection(input CreateConnectionInput) (*Connection, error) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user