All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
The "click to download" button in Settings > About only logged to console. Now calls DownloadUpdate + ApplyUpdate via Wails bindings, matching the working flow in StatusBar.vue. Added "downloading" state with spinner. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
466 lines
22 KiB
Vue
466 lines
22 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="visible"
|
|
class="fixed inset-0 z-50 flex items-center justify-center"
|
|
@click.self="close"
|
|
@keydown.esc="close"
|
|
>
|
|
<!-- Backdrop -->
|
|
<div class="absolute inset-0 bg-black/50" @click="close" />
|
|
|
|
<!-- Modal -->
|
|
<div class="relative w-full max-w-lg bg-[#161b22] border border-[#30363d] rounded-lg shadow-2xl overflow-hidden">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between px-4 py-3 border-b border-[#30363d]">
|
|
<h3 class="text-sm font-semibold text-[var(--wraith-text-primary)]">Settings</h3>
|
|
<button
|
|
class="text-[var(--wraith-text-muted)] hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
|
@click="close"
|
|
>
|
|
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.749.749 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.749.749 0 1 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="flex min-h-[400px] max-h-[70vh]">
|
|
<!-- Section tabs (left sidebar) -->
|
|
<div class="w-36 border-r border-[#30363d] py-2 shrink-0">
|
|
<button
|
|
v-for="section in sections"
|
|
:key="section.id"
|
|
class="w-full flex items-center gap-2 px-4 py-2 text-xs text-left transition-colors cursor-pointer"
|
|
:class="activeSection === section.id
|
|
? 'bg-[#1f6feb]/20 text-[var(--wraith-text-primary)] border-r-2 border-[#1f6feb]'
|
|
: 'text-[var(--wraith-text-secondary)] hover:bg-[#30363d] hover:text-[var(--wraith-text-primary)]'
|
|
"
|
|
@click="activeSection = section.id"
|
|
>
|
|
<span v-html="section.icon" />
|
|
{{ section.label }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Section content (right panel) -->
|
|
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
|
|
|
<!-- General -->
|
|
<template v-if="activeSection === 'general'">
|
|
<h4 class="text-xs font-semibold text-[var(--wraith-text-muted)] uppercase tracking-wider mb-3">General</h4>
|
|
|
|
<!-- Default protocol -->
|
|
<div>
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">Default Protocol</label>
|
|
<select
|
|
v-model="settings.defaultProtocol"
|
|
class="w-full px-3 py-2 text-sm rounded bg-[#0d1117] border border-[#30363d] text-[var(--wraith-text-primary)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors cursor-pointer"
|
|
>
|
|
<option value="ssh">SSH</option>
|
|
<option value="rdp">RDP</option>
|
|
</select>
|
|
<!-- TODO: Persist via Wails binding — SettingsService.SetDefaultProtocol(value) -->
|
|
</div>
|
|
|
|
<!-- Sidebar width -->
|
|
<div>
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">
|
|
Sidebar Width: {{ settings.sidebarWidth }}px
|
|
</label>
|
|
<input
|
|
v-model.number="settings.sidebarWidth"
|
|
type="range"
|
|
min="180"
|
|
max="400"
|
|
step="10"
|
|
class="w-full accent-[var(--wraith-accent-blue)]"
|
|
/>
|
|
<!-- TODO: Persist via Wails binding — SettingsService.SetSidebarWidth(value) -->
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Terminal -->
|
|
<template v-if="activeSection === 'terminal'">
|
|
<h4 class="text-xs font-semibold text-[var(--wraith-text-muted)] uppercase tracking-wider mb-3">Terminal</h4>
|
|
|
|
<!-- Default theme -->
|
|
<div>
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">Default Theme</label>
|
|
<select
|
|
v-model="settings.terminalTheme"
|
|
class="w-full px-3 py-2 text-sm rounded bg-[#0d1117] border border-[#30363d] text-[var(--wraith-text-primary)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors cursor-pointer"
|
|
>
|
|
<option v-for="theme in themeNames" :key="theme" :value="theme">{{ theme }}</option>
|
|
</select>
|
|
<!-- TODO: Persist via Wails binding — SettingsService.SetTerminalTheme(value) -->
|
|
</div>
|
|
|
|
<!-- Font size -->
|
|
<div>
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">Font Size</label>
|
|
<input
|
|
v-model.number="settings.fontSize"
|
|
type="number"
|
|
min="8"
|
|
max="32"
|
|
class="w-full px-3 py-2 text-sm rounded bg-[#0d1117] border border-[#30363d] text-[var(--wraith-text-primary)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors"
|
|
/>
|
|
<!-- TODO: Persist via Wails binding — SettingsService.SetFontSize(value) -->
|
|
</div>
|
|
|
|
<!-- Scrollback buffer -->
|
|
<div>
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">Scrollback Buffer (lines)</label>
|
|
<input
|
|
v-model.number="settings.scrollbackBuffer"
|
|
type="number"
|
|
min="100"
|
|
max="100000"
|
|
step="100"
|
|
class="w-full px-3 py-2 text-sm rounded bg-[#0d1117] border border-[#30363d] text-[var(--wraith-text-primary)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors"
|
|
/>
|
|
<!-- TODO: Persist via Wails binding — SettingsService.SetScrollbackBuffer(value) -->
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Vault -->
|
|
<template v-if="activeSection === 'vault'">
|
|
<h4 class="text-xs font-semibold text-[var(--wraith-text-muted)] uppercase tracking-wider mb-3">Vault</h4>
|
|
|
|
<!-- Change master password -->
|
|
<div>
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-2">Master Password</label>
|
|
<button
|
|
class="px-3 py-2 text-xs text-[var(--wraith-text-primary)] rounded border border-[#30363d] hover:bg-[#30363d] transition-colors cursor-pointer"
|
|
@click="changeMasterPassword"
|
|
>
|
|
Change Master Password
|
|
</button>
|
|
<!-- TODO: Open a password change dialog via Wails binding — VaultService.ChangeMasterPassword(old, new) -->
|
|
</div>
|
|
|
|
<!-- Export vault -->
|
|
<div class="pt-2">
|
|
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-2">Backup</label>
|
|
<div class="flex gap-2">
|
|
<button
|
|
class="px-3 py-2 text-xs text-[var(--wraith-text-primary)] rounded border border-[#30363d] hover:bg-[#30363d] transition-colors cursor-pointer"
|
|
@click="exportVault"
|
|
>
|
|
Export Vault
|
|
</button>
|
|
<button
|
|
class="px-3 py-2 text-xs text-[var(--wraith-text-primary)] rounded border border-[#30363d] hover:bg-[#30363d] transition-colors cursor-pointer"
|
|
@click="importVault"
|
|
>
|
|
Import Vault
|
|
</button>
|
|
</div>
|
|
<!-- TODO: Wails bindings — VaultService.Export() / VaultService.Import(data) -->
|
|
</div>
|
|
</template>
|
|
|
|
<!-- About -->
|
|
<template v-if="activeSection === 'about'">
|
|
<h4 class="text-xs font-semibold text-[var(--wraith-text-muted)] uppercase tracking-wider mb-3">About</h4>
|
|
|
|
<div class="space-y-3">
|
|
<div class="bg-[#0d1117] rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold tracking-widest text-[var(--wraith-accent-blue)] mb-1">WRAITH</div>
|
|
<div class="text-xs text-[var(--wraith-text-secondary)]">Connection Manager</div>
|
|
</div>
|
|
|
|
<div class="space-y-2 text-xs">
|
|
<div class="flex justify-between py-1.5 border-b border-[#30363d]">
|
|
<span class="text-[var(--wraith-text-secondary)]">Version</span>
|
|
<span class="text-[var(--wraith-text-primary)]">{{ currentVersion }}</span>
|
|
</div>
|
|
<div class="flex justify-between py-1.5 border-b border-[#30363d]">
|
|
<span class="text-[var(--wraith-text-secondary)]">License</span>
|
|
<span class="text-[var(--wraith-text-primary)]">Proprietary</span>
|
|
</div>
|
|
<div class="flex justify-between py-1.5 border-b border-[#30363d]">
|
|
<span class="text-[var(--wraith-text-secondary)]">Runtime</span>
|
|
<span class="text-[var(--wraith-text-primary)]">Wails v3</span>
|
|
</div>
|
|
<div class="flex justify-between py-1.5">
|
|
<span class="text-[var(--wraith-text-secondary)]">Frontend</span>
|
|
<span class="text-[var(--wraith-text-primary)]">Vue 3 + TypeScript</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Check for Updates -->
|
|
<div class="pt-2">
|
|
<button
|
|
class="w-full px-3 py-2 text-xs rounded border transition-colors cursor-pointer flex items-center justify-center gap-2"
|
|
:class="updateCheckState === 'found'
|
|
? 'border-[#3fb950] text-[#3fb950] hover:bg-[#3fb950]/10'
|
|
: 'border-[#30363d] text-[var(--wraith-text-primary)] hover:bg-[#30363d]'"
|
|
:disabled="updateCheckState === 'checking' || updateCheckState === 'downloading'"
|
|
@click="checkForUpdates"
|
|
>
|
|
<template v-if="updateCheckState === 'idle'">
|
|
<svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z" />
|
|
</svg>
|
|
Check for Updates
|
|
</template>
|
|
<template v-else-if="updateCheckState === 'checking'">
|
|
<svg class="w-3.5 h-3.5 animate-spin" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM1.5 8a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Z" opacity=".25" />
|
|
<path d="M8 0a8 8 0 0 1 8 8h-1.5A6.5 6.5 0 0 0 8 1.5V0Z" />
|
|
</svg>
|
|
Checking...
|
|
</template>
|
|
<template v-else-if="updateCheckState === 'found'">
|
|
<svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2Zm.75 3.5v3.69l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06l1.72 1.72V5.5a.75.75 0 0 1 1.5 0Z" />
|
|
</svg>
|
|
v{{ latestVersion }} available — click to download
|
|
</template>
|
|
<template v-else-if="updateCheckState === 'downloading'">
|
|
<svg class="w-3.5 h-3.5 animate-spin" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM1.5 8a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Z" opacity=".25" />
|
|
<path d="M8 0a8 8 0 0 1 8 8h-1.5A6.5 6.5 0 0 0 8 1.5V0Z" />
|
|
</svg>
|
|
Downloading...
|
|
</template>
|
|
<template v-else-if="updateCheckState === 'up-to-date'">
|
|
<svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" />
|
|
</svg>
|
|
You're up to date
|
|
</template>
|
|
<template v-else-if="updateCheckState === 'error'">
|
|
Could not check for updates
|
|
</template>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex gap-2 pt-2">
|
|
<a
|
|
href="#"
|
|
class="flex-1 px-3 py-2 text-xs text-center text-[var(--wraith-accent-blue)] rounded border border-[#30363d] hover:bg-[#30363d] transition-colors cursor-pointer"
|
|
@click.prevent="openLink('docs')"
|
|
>
|
|
Documentation
|
|
</a>
|
|
<a
|
|
href="#"
|
|
class="flex-1 px-3 py-2 text-xs text-center text-[var(--wraith-accent-blue)] rounded border border-[#30363d] hover:bg-[#30363d] transition-colors cursor-pointer"
|
|
@click.prevent="openLink('repo')"
|
|
>
|
|
Source Code
|
|
</a>
|
|
</div>
|
|
<!-- TODO: Wails binding — runtime.BrowserOpenURL(url) -->
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="flex items-center justify-end px-4 py-3 border-t border-[#30363d]">
|
|
<button
|
|
class="px-3 py-1.5 text-xs text-white bg-[#1f6feb] hover:bg-[#388bfd] rounded transition-colors cursor-pointer"
|
|
@click="close"
|
|
>
|
|
Done
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, onMounted } from "vue";
|
|
import { Call, Browser } from "@wailsio/runtime";
|
|
|
|
const SETTINGS = "github.com/vstockwell/wraith/internal/settings.SettingsService";
|
|
const UPDATER = "github.com/vstockwell/wraith/internal/updater.UpdateService";
|
|
|
|
type Section = "general" | "terminal" | "vault" | "about";
|
|
|
|
const visible = ref(false);
|
|
const activeSection = ref<Section>("general");
|
|
|
|
const sections = [
|
|
{
|
|
id: "general" as const,
|
|
label: "General",
|
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-3.5 h-3.5"><path d="M8 0a8.2 8.2 0 0 1 .701.031C8.955.017 9.209 0 9.466 0a1.934 1.934 0 0 1 1.466.665c.33.367.51.831.54 1.316a7.96 7.96 0 0 1 .82.4c.463-.207.97-.29 1.476-.19.504.1.963.37 1.3.77.339.404.516.91.5 1.423a1.94 1.94 0 0 1-.405 1.168 8.02 8.02 0 0 1 .356.9 1.939 1.939 0 0 1 1.48.803 1.941 1.941 0 0 1 0 2.29 1.939 1.939 0 0 1-1.48.803c-.095.316-.215.622-.357.9a1.94 1.94 0 0 1-.094 2.59 1.94 1.94 0 0 1-2.776.22 7.96 7.96 0 0 1-.82.4 1.94 1.94 0 0 1-2.006 1.98A8.2 8.2 0 0 1 8 16a8.2 8.2 0 0 1-.701-.031 1.938 1.938 0 0 1-2.005-1.98 7.96 7.96 0 0 1-.82-.4 1.94 1.94 0 0 1-2.776-.22 1.94 1.94 0 0 1-.094-2.59 8.02 8.02 0 0 1-.357-.9A1.939 1.939 0 0 1 0 8.945a1.941 1.941 0 0 1 0-2.29 1.939 1.939 0 0 1 1.247-.803c.095-.316.215-.622.357-.9a1.94 1.94 0 0 1 .094-2.59 1.94 1.94 0 0 1 2.776-.22c.258-.157.532-.293.82-.4A1.934 1.934 0 0 1 6.834.665 1.934 1.934 0 0 1 8.3.03 8.2 8.2 0 0 1 8 0ZM8 5a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg>`,
|
|
},
|
|
{
|
|
id: "terminal" as const,
|
|
label: "Terminal",
|
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-3.5 h-3.5"><path d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25Zm1.75-.25a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25ZM7.25 8a.749.749 0 0 1-.22.53l-2.25 2.25a.749.749 0 1 1-1.06-1.06L5.44 8 3.72 6.28a.749.749 0 1 1 1.06-1.06l2.25 2.25c.141.14.22.331.22.53Zm1.5 1.5h3a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5Z"/></svg>`,
|
|
},
|
|
{
|
|
id: "vault" as const,
|
|
label: "Vault",
|
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-3.5 h-3.5"><path d="M4 4v2h-.25A1.75 1.75 0 0 0 2 7.75v5.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0 0 14 13.25v-5.5A1.75 1.75 0 0 0 12.25 6H12V4a4 4 0 1 0-8 0Zm6.5 2V4a2.5 2.5 0 0 0-5 0v2ZM8 9.5a1.5 1.5 0 0 1 .5 2.915V13.5a.5.5 0 0 1-1 0v-1.085A1.5 1.5 0 0 1 8 9.5Z"/></svg>`,
|
|
},
|
|
{
|
|
id: "about" as const,
|
|
label: "About",
|
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-3.5 h-3.5"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/></svg>`,
|
|
},
|
|
];
|
|
|
|
const themeNames = [
|
|
"Dracula",
|
|
"Nord",
|
|
"Monokai",
|
|
"One Dark",
|
|
"Solarized Dark",
|
|
"Gruvbox Dark",
|
|
"MobaXTerm Classic",
|
|
];
|
|
|
|
const settings = ref({
|
|
defaultProtocol: "ssh" as "ssh" | "rdp",
|
|
sidebarWidth: 240,
|
|
terminalTheme: "Dracula",
|
|
fontSize: 14,
|
|
scrollbackBuffer: 5000,
|
|
});
|
|
|
|
/** Load saved settings from Go backend on mount. */
|
|
onMounted(async () => {
|
|
try {
|
|
const [protocol, sidebarW, theme, fontSize, scrollback] = await Promise.all([
|
|
Call.ByName(`${SETTINGS}.Get`, "default_protocol") as Promise<string>,
|
|
Call.ByName(`${SETTINGS}.Get`, "sidebar_width") as Promise<string>,
|
|
Call.ByName(`${SETTINGS}.Get`, "terminal_theme") as Promise<string>,
|
|
Call.ByName(`${SETTINGS}.Get`, "font_size") as Promise<string>,
|
|
Call.ByName(`${SETTINGS}.Get`, "scrollback_buffer") as Promise<string>,
|
|
]);
|
|
if (protocol) settings.value.defaultProtocol = protocol as "ssh" | "rdp";
|
|
if (sidebarW) settings.value.sidebarWidth = Number(sidebarW);
|
|
if (theme) settings.value.terminalTheme = theme;
|
|
if (fontSize) settings.value.fontSize = Number(fontSize);
|
|
if (scrollback) settings.value.scrollbackBuffer = Number(scrollback);
|
|
// Load version from Go backend
|
|
const ver = await Call.ByName(`${APP}.GetVersion`) as string;
|
|
if (ver) currentVersion.value = ver;
|
|
} catch (err) {
|
|
console.error("Failed to load settings:", err);
|
|
}
|
|
});
|
|
|
|
/** Persist settings changes to Go backend as they change. */
|
|
watch(() => settings.value.defaultProtocol, (val) => {
|
|
Call.ByName(`${SETTINGS}.Set`, "default_protocol", val).catch(console.error);
|
|
});
|
|
watch(() => settings.value.sidebarWidth, (val) => {
|
|
Call.ByName(`${SETTINGS}.Set`, "sidebar_width", String(val)).catch(console.error);
|
|
});
|
|
watch(() => settings.value.terminalTheme, (val) => {
|
|
Call.ByName(`${SETTINGS}.Set`, "terminal_theme", val).catch(console.error);
|
|
});
|
|
watch(() => settings.value.fontSize, (val) => {
|
|
Call.ByName(`${SETTINGS}.Set`, "font_size", String(val)).catch(console.error);
|
|
});
|
|
watch(() => settings.value.scrollbackBuffer, (val) => {
|
|
Call.ByName(`${SETTINGS}.Set`, "scrollback_buffer", String(val)).catch(console.error);
|
|
});
|
|
|
|
// --- Update check state ---
|
|
type UpdateCheckState = "idle" | "checking" | "found" | "downloading" | "up-to-date" | "error";
|
|
const updateCheckState = ref<UpdateCheckState>("idle");
|
|
const APP = "github.com/vstockwell/wraith/internal/app.WraithApp";
|
|
const currentVersion = ref("loading...");
|
|
const latestVersion = ref("");
|
|
const updateInfo = ref<{ available: boolean; currentVersion: string; latestVersion: string; downloadUrl: string; sha256: string } | null>(null);
|
|
|
|
function open(): void {
|
|
visible.value = true;
|
|
activeSection.value = "general";
|
|
}
|
|
|
|
function close(): void {
|
|
visible.value = false;
|
|
}
|
|
|
|
async function changeMasterPassword(): Promise<void> {
|
|
const oldPw = prompt("Current master password:");
|
|
if (!oldPw) return;
|
|
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 {
|
|
// Not implemented yet — needs Go method to export encrypted DB
|
|
alert("Export vault is not yet available. Your data is stored in %APPDATA%\\Wraith\\wraith.db");
|
|
}
|
|
|
|
function importVault(): void {
|
|
// Not implemented yet — needs Go method to import encrypted DB
|
|
alert("Import vault is not yet available. Copy wraith.db to %APPDATA%\\Wraith\\ to restore.");
|
|
}
|
|
|
|
async function checkForUpdates(): Promise<void> {
|
|
if (updateCheckState.value === "checking") return;
|
|
|
|
if (updateCheckState.value === "found" && updateInfo.value) {
|
|
updateCheckState.value = "downloading";
|
|
try {
|
|
const path = await Call.ByName(`${UPDATER}.DownloadUpdate`, updateInfo.value) as string;
|
|
await Call.ByName(`${UPDATER}.ApplyUpdate`, path);
|
|
} catch (err) {
|
|
console.error("Update failed:", err);
|
|
updateCheckState.value = "found";
|
|
}
|
|
return;
|
|
}
|
|
|
|
updateCheckState.value = "checking";
|
|
try {
|
|
const info = await Call.ByName(`${UPDATER}.CheckForUpdate`) as {
|
|
available: boolean;
|
|
currentVersion: string;
|
|
latestVersion: string;
|
|
downloadUrl: string;
|
|
sha256: string;
|
|
};
|
|
currentVersion.value = info.currentVersion || currentVersion.value;
|
|
if (info.available) {
|
|
latestVersion.value = info.latestVersion;
|
|
updateInfo.value = info;
|
|
updateCheckState.value = "found";
|
|
} else {
|
|
updateCheckState.value = "up-to-date";
|
|
}
|
|
} catch {
|
|
updateCheckState.value = "error";
|
|
}
|
|
}
|
|
|
|
function openLink(target: string): void {
|
|
const urls: Record<string, string> = {
|
|
docs: "https://github.com/wraith/docs",
|
|
repo: "https://github.com/wraith",
|
|
};
|
|
const url = urls[target] ?? target;
|
|
Browser.OpenURL(url).catch(console.error);
|
|
}
|
|
|
|
defineExpose({ open, close, visible });
|
|
</script>
|