feat: SSH terminal - the agent connects by SSH to another host (password, key file or its own keys) and the shell opens in a tab with the connection's progress shown; host keys trusted on first use, a changed one refused

This commit is contained in:
redtea committed 2026-10-14 21:20:18 +03:00
1 parent 5a204ae9bb
commit 5a4dc596d5
7 files changed
+515 -21

No files matched your search

+37 -1
View File
@@ -10,6 +10,42 @@
<style>
#status.st-ok { color:#37c871; }
#status.st-err { color:#ff5c5c; }
#status { display:inline-block; vertical-align:bottom; min-width:0; max-width:42vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* SSH: the badge on a pane, and the dialog to connect. */
.badge-ssh { flex:none; font-size:10px; font-weight:600; letter-spacing:.04em; padding:1px 5px; border-radius:4px;
background:#12202f; color:var(--accent); border:1px solid #1f3a57; }
dialog.sshdlg { max-width:460px; }
dialog.sshdlg[open] { animation:dlg-in .2s ease; }
dialog.sshdlg[open]::backdrop { animation:dlg-fade .2s ease; }
dialog.sshdlg.closing { animation:dlg-out .16s ease forwards; }
dialog.sshdlg.closing::backdrop { animation:dlg-fade .16s ease reverse forwards; }
@keyframes dlg-in { from { opacity:0; transform:translateY(10px) scale(.97); } }
@keyframes dlg-out { to { opacity:0; transform:translateY(6px) scale(.98); } }
@keyframes dlg-fade { from { opacity:0; } }
.sshdlg .dlg-h .ico { color:var(--accent); display:inline-flex; }
.sshdlg .dlg-b { display:grid; gap:12px; }
.sshdlg .f { display:grid; gap:5px; margin:0; }
.sshdlg .f > span { font-size:12px; color:var(--muted); }
.sshdlg .frow { display:flex; gap:10px; align-items:end; }
.sshdlg .frow .grow { flex:1; min-width:0; }
.sshdlg .frow .port { width:92px; flex:none; }
.sshdlg input, .sshdlg select, .sshdlg textarea { width:100%; box-sizing:border-box; }
.sshdlg textarea { font:12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; resize:vertical; min-height:90px;
background:var(--bg, #0c0f13); color:var(--text); border:1px solid var(--border); border-radius:8px; padding:8px 10px; }
.sshdlg textarea:focus { border-color:var(--accent); outline:none; }
.sshdlg .auth { display:grid; gap:8px; animation:auth-in .18s ease; }
.sshdlg .auth[hidden] { display:none; }
@keyframes auth-in { from { opacity:0; transform:translateY(-4px); } }
.sshdlg p.auth { margin:0; font-size:13px; }
.sshdlg .keyname { font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; align-self:center; }
.sshdlg .seg { display:flex; padding:3px; gap:3px; background:#0c0f13; border:1px solid var(--border); border-radius:9px; }
.sshdlg .seg button { flex:1; padding:6px 8px; font-size:13px; background:transparent; color:var(--muted); border:0; border-radius:6px;
transition:background .18s, color .18s; }
.sshdlg .seg button:hover { color:var(--text); }
.sshdlg .seg button.on { background:#12202f; color:var(--text); box-shadow:inset 0 0 0 1px #1f3a57; }
.sshdlg .sshmsg { margin:0; min-height:18px; font-size:13px; color:var(--danger); }
.sshdlg .row.end { display:flex; justify-content:flex-end; gap:8px; }
@media (max-width:520px) { .sshdlg .seg button { font-size:12px; padding:6px 4px; } }
/* The page is the terminal: bar, tabs, then the terminals to the bottom. */
body { height:100vh; display:flex; flex-direction:column; overflow:hidden; }
header.topbar { flex:none; }
@@ -40,7 +76,7 @@
pointer-events:none; transition:left .12s ease; }
.tabmark[hidden] { display:none; }
.dot { width:7px; height:7px; border-radius:50%; background:#555d6a; flex:none; transition:background .3s; }
.dot.ok { background:var(--ok); } .dot.err { background:var(--danger); } .dot.wait { background:#e6a23c; }
.dot.ok { background:var(--ok); } .dot.err { background:var(--danger); padding:0; border:0; margin:0; } .dot.wait { background:#e6a23c; }
/* The terminals' area. */
.area { position:relative; flex:1; min-height:0; margin:12px 16px 16px; border:1px solid var(--border); border-radius:10px; background:#0c0f13; overflow:hidden; }
body.multi .area { margin-top:0; border-top-left-radius:0; }