fix: wire settings modal, import dialog, and connection edit into the UI
- Settings button (gear icon) now opens SettingsModal with General, Terminal, Vault, About sections - File menu added to toolbar with New Connection, Import MobaXTerm, Settings, Exit - Command Palette "Settings" action now opens the settings modal - Command Palette "New SSH/RDP Connection" actions now open ConnectionEditDialog - ConnectionEditDialog mounted in MainLayout for File menu / palette access - All Wails binding calls left as TODO with mock behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5cdb96ffb8
commit
473a25cf2a
@ -117,6 +117,8 @@ const sessionStore = useSessionStore();
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "open-import"): void;
|
(e: "open-import"): void;
|
||||||
|
(e: "open-settings"): void;
|
||||||
|
(e: "open-new-connection", protocol?: "ssh" | "rdp"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const actions: PaletteAction[] = [
|
const actions: PaletteAction[] = [
|
||||||
@ -125,7 +127,7 @@ const actions: PaletteAction[] = [
|
|||||||
label: "New SSH Connection",
|
label: "New SSH Connection",
|
||||||
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"><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>`,
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"><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>`,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
// TODO: Open new connection dialog for SSH
|
emit("open-new-connection", "ssh");
|
||||||
close();
|
close();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -134,7 +136,7 @@ const actions: PaletteAction[] = [
|
|||||||
label: "New RDP Connection",
|
label: "New RDP Connection",
|
||||||
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"><path d="M1.75 2.5h12.5a.25.25 0 0 1 .25.25v7.5a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25v-7.5a.25.25 0 0 1 .25-.25ZM14.25 1H1.75A1.75 1.75 0 0 0 0 2.75v7.5C0 11.216.784 12 1.75 12h4.388l-.533 1.5H4a.75.75 0 0 0 0 1.5h8a.75.75 0 0 0 0-1.5h-1.605l-.533-1.5h4.388A1.75 1.75 0 0 0 16 10.25v-7.5A1.75 1.75 0 0 0 14.25 1ZM9.112 13.5H6.888l.533-1.5h1.158l.533 1.5Z"/></svg>`,
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"><path d="M1.75 2.5h12.5a.25.25 0 0 1 .25.25v7.5a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25v-7.5a.25.25 0 0 1 .25-.25ZM14.25 1H1.75A1.75 1.75 0 0 0 0 2.75v7.5C0 11.216.784 12 1.75 12h4.388l-.533 1.5H4a.75.75 0 0 0 0 1.5h8a.75.75 0 0 0 0-1.5h-1.605l-.533-1.5h4.388A1.75 1.75 0 0 0 16 10.25v-7.5A1.75 1.75 0 0 0 14.25 1ZM9.112 13.5H6.888l.533-1.5h1.158l.533 1.5Z"/></svg>`,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
// TODO: Open new connection dialog for RDP
|
emit("open-new-connection", "rdp");
|
||||||
close();
|
close();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -152,7 +154,7 @@ const actions: PaletteAction[] = [
|
|||||||
label: "Settings",
|
label: "Settings",
|
||||||
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"><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>`,
|
icon: `<svg viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"><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>`,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
// TODO: Open settings dialog
|
emit("open-settings");
|
||||||
close();
|
close();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
314
frontend/src/components/common/SettingsModal.vue
Normal file
314
frontend/src/components/common/SettingsModal.vue
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
<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)]">0.1.0-dev</span>
|
||||||
|
<!-- TODO: Fetch from Wails binding — AppService.GetVersion() -->
|
||||||
|
</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 v2</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>
|
||||||
|
|
||||||
|
<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 } from "vue";
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
function open(): void {
|
||||||
|
visible.value = true;
|
||||||
|
activeSection.value = "general";
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeMasterPassword(): void {
|
||||||
|
// TODO: Replace with Wails binding — open a password change dialog
|
||||||
|
// VaultService.ChangeMasterPassword(oldPassword, newPassword)
|
||||||
|
alert("Change master password — not yet implemented (requires Wails binding)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportVault(): void {
|
||||||
|
// TODO: Replace with Wails binding — VaultService.Export()
|
||||||
|
alert("Export vault — not yet implemented (requires Wails binding)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function importVault(): void {
|
||||||
|
// TODO: Replace with Wails binding — VaultService.Import(data)
|
||||||
|
alert("Import vault — not yet implemented (requires Wails binding)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLink(target: string): void {
|
||||||
|
// TODO: Replace with Wails runtime.BrowserOpenURL(url)
|
||||||
|
const urls: Record<string, string> = {
|
||||||
|
docs: "https://github.com/wraith/docs",
|
||||||
|
repo: "https://github.com/wraith",
|
||||||
|
};
|
||||||
|
console.log("Open link:", urls[target] ?? target);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close, visible });
|
||||||
|
</script>
|
||||||
@ -5,10 +5,61 @@
|
|||||||
class="h-10 flex items-center justify-between px-4 bg-[var(--wraith-bg-secondary)] border-b border-[var(--wraith-border)] shrink-0"
|
class="h-10 flex items-center justify-between px-4 bg-[var(--wraith-bg-secondary)] border-b border-[var(--wraith-border)] shrink-0"
|
||||||
style="--wails-draggable: drag"
|
style="--wails-draggable: drag"
|
||||||
>
|
>
|
||||||
<span class="text-sm font-bold tracking-widest text-[var(--wraith-accent-blue)]">
|
<div class="flex items-center gap-3" style="--wails-draggable: no-drag">
|
||||||
|
<span class="text-sm font-bold tracking-widest text-[var(--wraith-accent-blue)]" style="--wails-draggable: drag">
|
||||||
WRAITH
|
WRAITH
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<!-- File menu -->
|
||||||
|
<div class="relative">
|
||||||
|
<button
|
||||||
|
class="text-xs text-[var(--wraith-text-secondary)] hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer px-2 py-1 rounded hover:bg-[var(--wraith-bg-tertiary)]"
|
||||||
|
@click="showFileMenu = !showFileMenu"
|
||||||
|
@blur="closeFileMenuDeferred"
|
||||||
|
>
|
||||||
|
File
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="showFileMenu"
|
||||||
|
class="absolute top-full left-0 mt-0.5 w-56 bg-[#161b22] border border-[#30363d] rounded-lg shadow-2xl overflow-hidden z-50 py-1"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full flex items-center gap-3 px-4 py-2 text-xs text-left text-[var(--wraith-text-secondary)] hover:bg-[#30363d] hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||||
|
@mousedown.prevent="handleFileMenuAction('new-connection')"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="currentColor"><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>
|
||||||
|
<span class="flex-1">New Connection</span>
|
||||||
|
<kbd class="text-[10px] text-[var(--wraith-text-muted)]">Ctrl+N</kbd>
|
||||||
|
</button>
|
||||||
|
<div class="border-t border-[#30363d] my-1" />
|
||||||
|
<button
|
||||||
|
class="w-full flex items-center gap-3 px-4 py-2 text-xs text-left text-[var(--wraith-text-secondary)] hover:bg-[#30363d] hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||||
|
@mousedown.prevent="handleFileMenuAction('import')"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="currentColor"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14ZM11.78 4.72a.749.749 0 1 1-1.06 1.06L8.75 3.81V9.5a.75.75 0 0 1-1.5 0V3.81L5.28 5.78a.749.749 0 1 1-1.06-1.06l3.25-3.25a.749.749 0 0 1 1.06 0l3.25 3.25Z"/></svg>
|
||||||
|
<span class="flex-1">Import from MobaXTerm</span>
|
||||||
|
</button>
|
||||||
|
<div class="border-t border-[#30363d] my-1" />
|
||||||
|
<button
|
||||||
|
class="w-full flex items-center gap-3 px-4 py-2 text-xs text-left text-[var(--wraith-text-secondary)] hover:bg-[#30363d] hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||||
|
@mousedown.prevent="handleFileMenuAction('settings')"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="currentColor"><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>
|
||||||
|
<span class="flex-1">Settings</span>
|
||||||
|
</button>
|
||||||
|
<div class="border-t border-[#30363d] my-1" />
|
||||||
|
<button
|
||||||
|
class="w-full flex items-center gap-3 px-4 py-2 text-xs text-left text-[var(--wraith-text-secondary)] hover:bg-[#30363d] hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||||
|
@mousedown.prevent="handleFileMenuAction('exit')"
|
||||||
|
>
|
||||||
|
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2.75C2 1.784 2.784 1 3.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5a.75.75 0 0 1-1.5 0v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h3.5a.75.75 0 0 1 0 1.5h-3.5A1.75 1.75 0 0 1 2 13.25Zm10.44 4.5-1.97-1.97a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l1.97-1.97H6.75a.75.75 0 0 1 0-1.5Z"/></svg>
|
||||||
|
<span class="flex-1">Exit</span>
|
||||||
|
<kbd class="text-[10px] text-[var(--wraith-text-muted)]">Alt+F4</kbd>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Quick Connect -->
|
<!-- Quick Connect -->
|
||||||
<div class="flex-1 max-w-xs mx-4" style="--wails-draggable: no-drag">
|
<div class="flex-1 max-w-xs mx-4" style="--wails-draggable: no-drag">
|
||||||
<input
|
<input
|
||||||
@ -60,6 +111,7 @@
|
|||||||
<button
|
<button
|
||||||
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||||
title="Settings"
|
title="Settings"
|
||||||
|
@click="settingsModal?.open()"
|
||||||
>
|
>
|
||||||
⚙
|
⚙
|
||||||
</button>
|
</button>
|
||||||
@ -137,13 +189,24 @@
|
|||||||
<StatusBar ref="statusBar" @open-theme-picker="themePicker?.open()" />
|
<StatusBar ref="statusBar" @open-theme-picker="themePicker?.open()" />
|
||||||
|
|
||||||
<!-- Command Palette (Ctrl+K) -->
|
<!-- Command Palette (Ctrl+K) -->
|
||||||
<CommandPalette ref="commandPalette" @open-import="importDialog?.open()" />
|
<CommandPalette
|
||||||
|
ref="commandPalette"
|
||||||
|
@open-import="importDialog?.open()"
|
||||||
|
@open-settings="settingsModal?.open()"
|
||||||
|
@open-new-connection="connectionEditDialog?.openNew()"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Theme Picker -->
|
<!-- Theme Picker -->
|
||||||
<ThemePicker ref="themePicker" @select="handleThemeSelect" />
|
<ThemePicker ref="themePicker" @select="handleThemeSelect" />
|
||||||
|
|
||||||
<!-- Import Dialog -->
|
<!-- Import Dialog -->
|
||||||
<ImportDialog ref="importDialog" />
|
<ImportDialog ref="importDialog" />
|
||||||
|
|
||||||
|
<!-- Settings Modal -->
|
||||||
|
<SettingsModal ref="settingsModal" />
|
||||||
|
|
||||||
|
<!-- Connection Edit Dialog (for File menu / Command Palette new connection) -->
|
||||||
|
<ConnectionEditDialog ref="connectionEditDialog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -164,6 +227,8 @@ import EditorWindow from "@/components/editor/EditorWindow.vue";
|
|||||||
import CommandPalette from "@/components/common/CommandPalette.vue";
|
import CommandPalette from "@/components/common/CommandPalette.vue";
|
||||||
import ThemePicker from "@/components/common/ThemePicker.vue";
|
import ThemePicker from "@/components/common/ThemePicker.vue";
|
||||||
import ImportDialog from "@/components/common/ImportDialog.vue";
|
import ImportDialog from "@/components/common/ImportDialog.vue";
|
||||||
|
import SettingsModal from "@/components/common/SettingsModal.vue";
|
||||||
|
import ConnectionEditDialog from "@/components/connections/ConnectionEditDialog.vue";
|
||||||
import CopilotPanel from "@/components/copilot/CopilotPanel.vue";
|
import CopilotPanel from "@/components/copilot/CopilotPanel.vue";
|
||||||
import type { ThemeDefinition } from "@/components/common/ThemePicker.vue";
|
import type { ThemeDefinition } from "@/components/common/ThemePicker.vue";
|
||||||
import type { SidebarTab } from "@/components/sidebar/SidebarToggle.vue";
|
import type { SidebarTab } from "@/components/sidebar/SidebarToggle.vue";
|
||||||
@ -184,8 +249,38 @@ const editorFile = ref<{ content: string; path: string; sessionId: string } | nu
|
|||||||
const commandPalette = ref<InstanceType<typeof CommandPalette> | null>(null);
|
const commandPalette = ref<InstanceType<typeof CommandPalette> | null>(null);
|
||||||
const themePicker = ref<InstanceType<typeof ThemePicker> | null>(null);
|
const themePicker = ref<InstanceType<typeof ThemePicker> | null>(null);
|
||||||
const importDialog = ref<InstanceType<typeof ImportDialog> | null>(null);
|
const importDialog = ref<InstanceType<typeof ImportDialog> | null>(null);
|
||||||
|
const settingsModal = ref<InstanceType<typeof SettingsModal> | null>(null);
|
||||||
|
const connectionEditDialog = ref<InstanceType<typeof ConnectionEditDialog> | null>(null);
|
||||||
const statusBar = ref<InstanceType<typeof StatusBar> | null>(null);
|
const statusBar = ref<InstanceType<typeof StatusBar> | null>(null);
|
||||||
|
|
||||||
|
/** File menu dropdown state. */
|
||||||
|
const showFileMenu = ref(false);
|
||||||
|
|
||||||
|
/** Close the file menu after a short delay (allows click events to fire first). */
|
||||||
|
function closeFileMenuDeferred(): void {
|
||||||
|
setTimeout(() => { showFileMenu.value = false; }, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle file menu item clicks. */
|
||||||
|
function handleFileMenuAction(action: string): void {
|
||||||
|
showFileMenu.value = false;
|
||||||
|
switch (action) {
|
||||||
|
case "new-connection":
|
||||||
|
connectionEditDialog.value?.openNew();
|
||||||
|
break;
|
||||||
|
case "import":
|
||||||
|
importDialog.value?.open();
|
||||||
|
break;
|
||||||
|
case "settings":
|
||||||
|
settingsModal.value?.open();
|
||||||
|
break;
|
||||||
|
case "exit":
|
||||||
|
// TODO: Replace with Wails runtime.Quit()
|
||||||
|
window.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Handle file open from SFTP sidebar -- loads mock content for now. */
|
/** Handle file open from SFTP sidebar -- loads mock content for now. */
|
||||||
function handleOpenFile(entry: FileEntry): void {
|
function handleOpenFile(entry: FileEntry): void {
|
||||||
if (!sessionStore.activeSession) return;
|
if (!sessionStore.activeSession) return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user