When a credential's sshKeyId points to a deleted/missing SSH key row,
the connection attempt silently had zero auth methods. Now throws a
clear error explaining the SSH key is missing. Also catches the case
where a credential has neither password nor SSH key configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When SSH timed out, the onClose callback referenced `const sessionId`
before connect() resolved, causing a Temporal Dead Zone ReferenceError
that killed the process. Changed to `let` with try/catch so connection
failures send an error message to the client instead of crashing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Process-level uncaughtException/unhandledRejection handlers plus
try/catch around upgrade and connection handlers. This will log
whatever is crashing the server on browser WebSocket connections
before the process dies, instead of silently restarting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WsAdapter registered its upgrade handler first and destroyed sockets
for non-matching paths. Now we remove all existing upgrade listeners,
install ours first, and forward non-terminal/sftp upgrades to the
original WsAdapter handlers for RDP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The NestJS WsAdapter silently swallowed WebSocket connections through
NPM despite 101 responses in the access log. Replaced with manual
ws.Server instances using noServer mode and explicit HTTP upgrade
event handling. Gateways are now plain @Injectable services, not
@WebSocketGateway decorators.
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>
handleConnection never fires despite browser getting open event.
Adding server-level upgrade listener to see if upgrades reach NestJS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With the NestJS ws adapter, the JWT token URL is on the HTTP upgrade
request (second arg to handleConnection), not on the WebSocket client
object. client.url was undefined, new URL(undefined) threw, catch
returned null, and every connection got 4001 Unauthorized.
Fix: Pass the IncomingMessage req to validateClient and prefer req.url.
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>