feat: persist RDP/SSH passwords as base64, fix card layouts and modal UX
CI / Build & Test (push) Successful in 3m52s
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:
+44
-12
@@ -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 {
|
||||
|
||||
@@ -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} · ${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 · ${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 · ${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;
|
||||
|
||||
Reference in New Issue
Block a user