When SSH connection fails, close the WebSocket immediately and
auto-remove the pending session after 3 seconds so the user sees
the error message before the panel clears. Prevents stuck sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NPM forwards /api/* correctly but silently drops WebSocket upgrades on
/ws/* despite toggle being enabled and custom nginx config. Moving
gateways to /api/ws/terminal and /api/ws/sftp so they ride the same
proxy rules that already work for REST endpoints.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: TerminalInstance.onMounted() called sessions.removeSession()
on the pending session, dropping sessions.length to 0. SessionContainer's
v-if="hasSessions" went false, unmounting the entire terminal UI before
the WebSocket could establish and add the real session.
Fix: Added replaceSession() to session store. TerminalInstance no longer
removes the pending session — instead passes its ID to connectToHost(),
which swaps it in-place when the backend responds with the real session ID.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save editingHost.id to local var before nulling the ref.
Prevents TypeError on Array.find during post-save refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Edit now populates name, hostname, port, protocol, group, credential, tags
- Modal title shows "Edit Host" vs "New Host" appropriately
- Save calls updateHost for existing hosts, createHost for new
- Added group dropdown selector to host modal
- Button shows "Update" when editing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added credential dropdown to New Host modal (loads from vault API)
- Fixed xterm.js "Cannot read dimensions" crash by guarding fitAddon.fit()
with requestAnimationFrame and container dimension checks
- Added WebGL context loss handler
- credentialId now passed when creating hosts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Tags field added to New Host modal (comma separated)
- Clicking + on a group now passes groupId to the new host
- Shows "Adding to: GroupName" in modal when creating from a group
- Refreshes tree after host creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Settings now opens as a slide-over sidebar from nav bar (no page nav)
- Removed App Theme toggle (dark mode only — no working light mode)
- Terminal theme picker, font size, scrollback all in sidebar
- Removed standalone /settings page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows × on hover next to the + button. Confirms before deleting.
Hosts in deleted groups become ungrouped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Left sidebar:
- Groups now show recursive host count badges
- Hosts in tree show up to 3 tags inline
Right sidebar (Host Details panel):
- Click any host card to open details panel on the right
- Shows address, port, protocol, group, credential, tags, color, notes
- Connect, Edit, Delete action buttons at bottom
- Selected card gets ring highlight
Terminal themes (10 prebuilt):
- Wraith (default), Dracula, Nord, Solarized Dark, Monokai, One Dark,
Gruvbox Dark, Tokyo Night, Catppuccin Mocha, Cyberpunk
- Visual theme picker in Settings with color preview + sample text
- Persisted to /api/settings and localStorage for immediate use
- useTerminal reads theme on terminal creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause of hosts not displaying: Nuxt auto-imports components in
subdirectories with the directory name as prefix. HostCard → ConnectionsHostCard,
HostTree → ConnectionsHostTree, QuickConnect → ConnectionsQuickConnect.
Unprefixed names silently resolve to nothing — no errors, just invisible components.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same root cause as host/group dialogs — components in subdirectories
with Teleport don't render at runtime in Nuxt SPA builds. Inlined
CredentialForm and KeyImportDialog directly into their parent pages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dialogs: bypassed component-based dialogs entirely — inlined modals
directly in index.vue with inline style fallbacks for z-index/colors.
If button clicks work, we see the modal. Period.
Profile 500: created UpdateProfileDto with class-validator decorators
so ValidationPipe processes it correctly. Added error logging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mismatched div count was silently breaking the component in
production builds. 18 opens, 19 closes → now 18/18.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PrimeVue Dialog wasn't rendering regardless of theme config.
Rewrote both Host and Group edit dialogs using Teleport + Tailwind,
matching the rest of the app's styling pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
theme: 'none' meant Dialog/InputText/Select/Button had zero CSS.
Configured Aura preset with sky primary + dark mode selector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Home/Profile links to nav bar alongside Vault/Settings/Logout
- Profile page: change email, display name, password
- TOTP 2FA: setup with QR code, verify, disable with password
- Login flow: two-step TOTP challenge when 2FA is enabled
- Backend: new endpoints PUT /profile, POST /totp/setup|verify|disable
- Migration: add totp_secret and totp_enabled columns to users
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>