feat: persist RDP/SSH passwords as base64, fix card layouts and modal UX
CI / Build & Test (push) Successful in 3m52s

- Encode passwords as base64 (passwordB64) before persisting RDP and SSH
  connections to native host state, decoded back on load/refresh
- Pre-fill password field when editing existing RDP or SSH connections
- Fix Quick Actions "Add SSH/RDP Connection" buttons to call openAddSshModal(null)
  / openAddRdpModal(null) so forms are properly reset instead of showing stale data
- Autofocus xterm.js terminal when SSH WebSocket connection is established
- Redesign SSH and RDP connection cards with a two-row layout: label + actions
  in the top row, full hs:// key in a wrapping monospace meta row below;
  removes all JS truncation and CSS ellipsis clipping
This commit is contained in:
Raven Scott
2026-02-28 19:51:16 -05:00
parent fe779bf2d7
commit 2a71311459
2 changed files with 93 additions and 57 deletions
+44 -12
View File
@@ -706,12 +706,18 @@
border-radius: var(--radius);
padding: 14px 16px;
display: flex;
align-items: center;
gap: 12px;
flex-direction: column;
gap: 10px;
transition: border-color var(--transition), background var(--transition);
cursor: default;
}
.rdp-conn-card:hover { border-color: var(--border2); background: var(--elevated); }
.rdp-conn-card-top {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.rdp-conn-icon {
width: 38px;
height: 38px;
@@ -725,10 +731,20 @@
}
.rdp-conn-icon svg { width: 18px; height: 18px; }
.rdp-conn-info { flex: 1; min-width: 0; }
.rdp-conn-label { font-size: 14px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rdp-conn-meta { font-size: 12px; color: var(--text3); margin-top: 2px; font-family: 'JetBrains Mono', monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rdp-conn-actions { display: flex; gap: 6px; flex-shrink: 0; }
.rdp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.rdp-conn-label { font-size: 14px; font-weight: 600; color: var(--text); word-break: break-word; }
.rdp-conn-actions { display: flex; gap: 6px; flex-shrink: 0; margin-left: auto; }
.rdp-conn-meta {
font-size: 11px;
color: var(--text3);
font-family: 'JetBrains Mono', monospace;
word-break: break-all;
line-height: 1.5;
padding: 6px 8px;
background: var(--base);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
}
.rdp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; }
/* ── RDP Viewer modal ───────────────────────────── */
.modal-rdp-viewer {
@@ -781,12 +797,18 @@
border-radius: var(--radius);
padding: 16px 18px;
display: flex;
align-items: center;
gap: 14px;
flex-direction: column;
gap: 10px;
transition: border-color var(--transition), background var(--transition);
cursor: default;
}
.ssh-conn-card:hover { border-color: var(--border2); background: var(--elevated); }
.ssh-conn-card-top {
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
}
.ssh-conn-icon {
width: 38px;
height: 38px;
@@ -801,10 +823,20 @@
}
.ssh-conn-icon svg { width: 18px; height: 18px; }
.ssh-conn-info { flex: 1; min-width: 0; }
.ssh-conn-label { font-size: 14px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ssh-conn-meta { font-size: 12px; color: var(--text3); margin-top: 2px; font-family: 'JetBrains Mono', monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ssh-conn-actions { display: flex; gap: 6px; flex-shrink: 0; }
.ssh-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.ssh-conn-label { font-size: 14px; font-weight: 600; color: var(--text); word-break: break-word; }
.ssh-conn-actions { display: flex; gap: 6px; flex-shrink: 0; margin-left: auto; }
.ssh-conn-meta {
font-size: 11px;
color: var(--text3);
font-family: 'JetBrains Mono', monospace;
word-break: break-all;
line-height: 1.5;
padding: 6px 8px;
background: var(--base);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
}
.ssh-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; }
/* ── SSH Terminal modal ──────────────────────────── */
.modal-terminal {
+8 -4
View File
@@ -260,10 +260,10 @@ function renderRdpGrid() {
const typeBadge = conn.type === 'rdp'
? `<span class="badge badge-amber" style="font-size:10px;padding:2px 7px;">RDP</span>`
: `<span class="badge badge-cyan" style="font-size:10px;padding:2px 7px;">VNC</span>`;
const meta = (conn.type === 'rdp' ? (conn.username ? conn.username + '@rdp' : 'rdp') : 'vnc')
+ ' · ' + escapeHtml(truncate(conn.hsUrl, 28));
const metaUser = conn.type === 'rdp' && conn.username ? escapeHtml(conn.username) + '@rdp' : conn.type.toUpperCase();
return `
<div class="rdp-conn-card" data-rdp-id="${escapeHtml(conn.id)}">
<div class="rdp-conn-card-top">
<div class="rdp-conn-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2"/>
@@ -273,7 +273,6 @@ function renderRdpGrid() {
</div>
<div class="rdp-conn-info">
<div class="rdp-conn-label">${escapeHtml(conn.label || (conn.type === 'rdp' ? 'RDP Desktop' : 'VNC Desktop'))} ${typeBadge}</div>
<div class="rdp-conn-meta">${escapeHtml(meta)}</div>
</div>
<div class="rdp-conn-actions">
<button class="btn btn-primary" data-rdp-connect="${escapeHtml(conn.id)}" style="padding:6px 14px;font-size:12px;">
@@ -287,6 +286,8 @@ function renderRdpGrid() {
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><polyline points="3,6 5,6 21,6"/><path d="M19,6l-1,14a2,2,0,0,1-2,2H8a2,2,0,0,1-2-2L5,6"/></svg>
</button>
</div>
</div>
<div class="rdp-conn-meta">${metaUser} &nbsp;·&nbsp; ${escapeHtml(conn.hsUrl)}</div>
</div>`;
}).join('');
@@ -1992,6 +1993,7 @@ function renderSshGrid() {
}
grid.innerHTML = sshConnections.map(conn => `
<div class="ssh-conn-card" data-ssh-id="${escapeHtml(conn.id)}">
<div class="ssh-conn-card-top">
<div class="ssh-conn-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="4" width="20" height="16" rx="2"/>
@@ -2000,7 +2002,6 @@ function renderSshGrid() {
</div>
<div class="ssh-conn-info">
<div class="ssh-conn-label">${escapeHtml(conn.label || conn.username + '@ssh')}</div>
<div class="ssh-conn-meta">${escapeHtml(conn.username)}@ssh &nbsp;·&nbsp; ${escapeHtml(truncate(conn.hsUrl, 32))}</div>
</div>
<div class="ssh-conn-actions">
<button class="btn btn-primary" data-ssh-connect="${escapeHtml(conn.id)}" style="padding:6px 14px;font-size:12px;">
@@ -2014,6 +2015,8 @@ function renderSshGrid() {
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><polyline points="3,6 5,6 21,6"/><path d="M19,6l-1,14a2,2,0,0,1-2,2H8a2,2,0,0,1-2-2L5,6"/></svg>
</button>
</div>
</div>
<div class="ssh-conn-meta">${escapeHtml(conn.username)}@ssh &nbsp;·&nbsp; ${escapeHtml(conn.hsUrl)}</div>
</div>`).join('');
// Wire up card buttons
@@ -2172,6 +2175,7 @@ async function connectSsh(conn) {
// Send a ready-signal so the native host knows the browser WebSocket is
// fully open and can safely flush buffered PTY output (MOTD, prompt).
ws.send('\x00');
term.focus();
};
let firstMessage = true;