dsh-ssh-manage 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -5
- package/README.md +5 -5
- package/client.js +205 -19
- package/index.js +6 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
- **Host management** — scan the config, groups, tags, favorites, notes, latency and host inspection;
|
|
26
26
|
- **Interactive terminal** — ssh2 allocates a PTY, xterm.js renders it, with colors, arrow keys, full-screen programs such as `vim`/`top`, and window resizing;
|
|
27
27
|
- **Day-to-day operations** — batch commands, SFTP browse/mkdir/rename/delete/upload/download, connection tests;
|
|
28
|
-
- **Credentials** — one AES-256-GCM encrypted password per host, never written to `~/.ssh/config
|
|
28
|
+
- **Credentials** — one AES-256-GCM encrypted password per host, never written to `~/.ssh/config`; passwords are not returned to the browser by default, but Show password decrypts one on demand in the current page and auto-hides it after 30 seconds;
|
|
29
29
|
- **Agent tool** — `ssh_manager` exposes the same capabilities to the model.
|
|
30
30
|
|
|
31
31
|
No more jumping between a terminal and DSH, and no need to remember where each host lives in the config file.
|
|
@@ -60,7 +60,7 @@ No more jumping between a terminal and DSH, and no need to remember where each h
|
|
|
60
60
|
### Credentials and data
|
|
61
61
|
|
|
62
62
|
- Passwords are AES-256-GCM encrypted in the plugin state directory and unlocked automatically at startup; no master password, no system Keychain.
|
|
63
|
-
- Passwords are only used for SSH password authentication (also compatible with hosts that only offer keyboard-interactive). They are never written to `~/.ssh/config`, command arguments
|
|
63
|
+
- Passwords are only used for SSH password authentication (also compatible with hosts that only offer keyboard-interactive). They are never written to `~/.ssh/config`, command arguments or logs. They are not returned by default; Show password explicitly decrypts one into the current page for 30 seconds and it is never exposed through the Agent tool.
|
|
64
64
|
- Ciphertext and key live in the same user directory, so this mainly protects against accidental file reads, not against malicious processes running as the same user.
|
|
65
65
|
- Groups, tags, favorites, notes and inspection data live in `hosts.json`; passwords live in `vault.json`; configuration changes are snapshotted to the backup directory.
|
|
66
66
|
|
|
@@ -96,16 +96,16 @@ Creating a host requires alias, hostname and password. The password is not writt
|
|
|
96
96
|
|
|
97
97
|
### From npm (recommended)
|
|
98
98
|
|
|
99
|
-
npm package: [`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage) (current version `0.3.
|
|
99
|
+
npm package: [`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage) (current version `0.3.4`).
|
|
100
100
|
|
|
101
101
|
```text
|
|
102
|
-
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.
|
|
102
|
+
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.4
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
Or with the DSH CLI:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
dsh plugin add dsh-ssh-manage@0.3.
|
|
108
|
+
dsh plugin add dsh-ssh-manage@0.3.4
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
After installation, refresh the Web page; the sidebar shows an "SSH Hosts" entry with id `dsh-ssh-manage`.
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
- **主机管理**:扫描配置、分组、标签、收藏、备注、连接延迟、主机巡检;
|
|
26
26
|
- **交互式终端**:ssh2 分配 PTY,xterm.js 渲染,支持彩色输出、方向键、`vim`/`top` 等全屏程序和窗口自适应;
|
|
27
27
|
- **日常运维**:批量命令、SFTP 目录浏览 / 新建 / 重命名 / 删除 / 上传 / 下载、测试连接;
|
|
28
|
-
- **凭据管理**:每台主机保存一份 AES-256-GCM 加密密码,不写进 `~/.ssh/config
|
|
28
|
+
- **凭据管理**:每台主机保存一份 AES-256-GCM 加密密码,不写进 `~/.ssh/config`;默认不返回浏览器,在主机详情点“显示密码”时才按需解密到当前页面,30 秒后自动隐藏;
|
|
29
29
|
- **Agent 工具**:`ssh_manager` 把同一套能力开放给模型。
|
|
30
30
|
|
|
31
31
|
不用再在终端和 DSH 之间来回切换,也不用记每个主机在配置文件里的位置。
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
### 凭据与数据
|
|
61
61
|
|
|
62
62
|
- 密码使用 AES-256-GCM 加密保存在插件自己的状态目录,插件启动时自动解锁,不需要每次输入主密码,也不使用系统 Keychain。
|
|
63
|
-
- 密码只用于 SSH password authentication,同时兼容只提供 keyboard-interactive 的主机;不会写入 `~/.ssh/config
|
|
63
|
+
- 密码只用于 SSH password authentication,同时兼容只提供 keyboard-interactive 的主机;不会写入 `~/.ssh/config`、命令参数或日志。默认不返回浏览器;仅在你主动点击“显示密码”时按需解密到当前页面,30 秒后自动隐藏,且不会通过 Agent 工具返回。
|
|
64
64
|
- 密码密文与本地密钥位于同一用户目录,主要防止文件被直接误读,不能抵御同一用户下的恶意进程。
|
|
65
65
|
- 主机分组、标签、收藏、备注、巡检结果保存在 `hosts.json`;密码保存在 `vault.json`;删除/修改配置前写入备份目录。
|
|
66
66
|
|
|
@@ -98,16 +98,16 @@
|
|
|
98
98
|
|
|
99
99
|
### 从 npm 安装(推荐)
|
|
100
100
|
|
|
101
|
-
npm 包:[`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage)(当前版本 `0.3.
|
|
101
|
+
npm 包:[`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage)(当前版本 `0.3.4`)。
|
|
102
102
|
|
|
103
103
|
```text
|
|
104
|
-
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.
|
|
104
|
+
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.4
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
或使用 DSH CLI:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
|
-
dsh plugin add dsh-ssh-manage@0.3.
|
|
110
|
+
dsh plugin add dsh-ssh-manage@0.3.4
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
安装后刷新 Web 页面,左侧栏会出现「SSH 主机」入口,入口 id 为 `dsh-ssh-manage`。
|
package/client.js
CHANGED
|
@@ -134,6 +134,9 @@ window.__ModuleLoader__.load({
|
|
|
134
134
|
'editor.port': ['端口', 'Port'],
|
|
135
135
|
'editor.group': ['分组', 'Group'],
|
|
136
136
|
'editor.groupPlaceholder': ['例如 production', 'e.g. production'],
|
|
137
|
+
'editor.groupChoose': ['选择已有分组', 'Choose an existing group'],
|
|
138
|
+
'editor.groupNone': ['无分组', 'No group'],
|
|
139
|
+
'editor.groupNoOptions': ['还没有已有分组,直接输入新名称即可。', 'No groups yet; type a new name.'],
|
|
137
140
|
'editor.tags': ['标签', 'Tags'],
|
|
138
141
|
'editor.tagsPlaceholder': ['web, critical(逗号分隔)', 'web, critical (comma separated)'],
|
|
139
142
|
'editor.label': ['显示名称', 'Display name'],
|
|
@@ -206,7 +209,13 @@ window.__ModuleLoader__.load({
|
|
|
206
209
|
'drawer.details.password': ['密码', 'Password'],
|
|
207
210
|
'drawer.details.savePassword': ['保存密码', 'Save password'],
|
|
208
211
|
'drawer.details.clearPassword': ['清除密码', 'Clear password'],
|
|
209
|
-
'drawer.details.passwordHint': ['密码使用 AES-256-GCM
|
|
212
|
+
'drawer.details.passwordHint': ['密码使用 AES-256-GCM 加密保存在本机;点击“显示密码”可在当前页面临时明文查看,30 秒后自动隐藏。', 'Passwords are AES-256-GCM encrypted at rest; use Show password to reveal one temporarily on this page (auto-hides after 30 seconds).'],
|
|
213
|
+
'drawer.details.reveal': ['显示密码', 'Show password'],
|
|
214
|
+
'drawer.details.hide': ['隐藏密码', 'Hide password'],
|
|
215
|
+
'drawer.details.copyPassword': ['复制密码', 'Copy password'],
|
|
216
|
+
'drawer.details.passwordCopied': ['密码已复制', 'Password copied'],
|
|
217
|
+
'drawer.details.revealWarning': ['密码已明文显示;注意旁人、录屏和截图,30 秒后自动隐藏。', 'The password is visible in plain text; watch for onlookers, screen recording and screenshots. It hides after 30 seconds.'],
|
|
218
|
+
'drawer.details.revealFailed': ['无法显示密码:{message}', 'Could not reveal the password: {message}'],
|
|
210
219
|
'drawer.details.system': ['主机信息', 'System'],
|
|
211
220
|
'drawer.details.detect': ['检测信息', 'Detect'],
|
|
212
221
|
'drawer.details.detecting': ['检测中…', 'Detecting…'],
|
|
@@ -780,6 +789,25 @@ window.__ModuleLoader__.load({
|
|
|
780
789
|
)
|
|
781
790
|
}
|
|
782
791
|
|
|
792
|
+
function IconEye({ size = 14 }) {
|
|
793
|
+
return h(
|
|
794
|
+
Svg,
|
|
795
|
+
{ size },
|
|
796
|
+
h(P, { d: 'M2.5 12s3.6-6.2 9.5-6.2S21.5 12 21.5 12s-3.6 6.2-9.5 6.2S2.5 12 2.5 12Z' }),
|
|
797
|
+
h('circle', { cx: 12, cy: 12, r: 3, stroke: 'currentColor', strokeWidth: 1.5 }),
|
|
798
|
+
)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function IconEyeOff({ size = 14 }) {
|
|
802
|
+
return h(
|
|
803
|
+
Svg,
|
|
804
|
+
{ size },
|
|
805
|
+
h(P, { d: 'M3 3l18 18' }),
|
|
806
|
+
h(P, { d: 'M10.6 6.1A10.7 10.7 0 0 1 12 6c5.9 0 9.5 6 9.5 6a17 17 0 0 1-3.1 3.6M6.4 6.8C3.9 8.7 2.5 12 2.5 12s3.6 6 9.5 6a10.9 10.9 0 0 0 4.5-1' }),
|
|
807
|
+
h(P, { d: 'M9.9 9.9a3 3 0 0 0 4.2 4.2' }),
|
|
808
|
+
)
|
|
809
|
+
}
|
|
810
|
+
|
|
783
811
|
function IconEraser({ size = 15 }) {
|
|
784
812
|
return h(
|
|
785
813
|
Svg,
|
|
@@ -4429,20 +4457,71 @@ window.__ModuleLoader__.load({
|
|
|
4429
4457
|
}
|
|
4430
4458
|
|
|
4431
4459
|
function DetailsView({ model, host, onEdit, onDelete }) {
|
|
4432
|
-
const { t, localeId, isPending, testHost, inspectHost, savePassword, removePassword } = model
|
|
4460
|
+
const { t, notify, localeId, isPending, testHost, inspectHost, savePassword, removePassword } = model
|
|
4433
4461
|
const [password, setPassword] = React.useState('')
|
|
4434
4462
|
const [saving, setSaving] = React.useState(false)
|
|
4463
|
+
const [revealedPassword, setRevealedPassword] = React.useState('')
|
|
4464
|
+
const [revealing, setRevealing] = React.useState(false)
|
|
4465
|
+
const revealTimer = React.useRef(null)
|
|
4435
4466
|
const credential = credentialInfo(host, t)
|
|
4436
4467
|
const testing = isPending(`test:${host.host}`)
|
|
4437
4468
|
const inspecting = isPending(`inspect:${host.host}`)
|
|
4438
4469
|
const hasSystem = Boolean(host.system || host.cpuCores || host.memoryGB || host.diskGB || host.latencyMs)
|
|
4439
4470
|
|
|
4471
|
+
const hideRevealed = () => {
|
|
4472
|
+
if (revealTimer.current) {
|
|
4473
|
+
clearTimeout(revealTimer.current)
|
|
4474
|
+
revealTimer.current = null
|
|
4475
|
+
}
|
|
4476
|
+
setRevealedPassword('')
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
React.useEffect(() => {
|
|
4480
|
+
hideRevealed()
|
|
4481
|
+
return () => {
|
|
4482
|
+
if (revealTimer.current) clearTimeout(revealTimer.current)
|
|
4483
|
+
}
|
|
4484
|
+
}, [host.host])
|
|
4485
|
+
|
|
4486
|
+
async function toggleReveal() {
|
|
4487
|
+
if (revealedPassword) {
|
|
4488
|
+
hideRevealed()
|
|
4489
|
+
return
|
|
4490
|
+
}
|
|
4491
|
+
if (revealing || host.passwordConfigured !== true) return
|
|
4492
|
+
setRevealing(true)
|
|
4493
|
+
try {
|
|
4494
|
+
const value = await callAction('credential/reveal', { host: host.host })
|
|
4495
|
+
const revealed = String(value?.password ?? '')
|
|
4496
|
+
if (!revealed) return
|
|
4497
|
+
setRevealedPassword(revealed)
|
|
4498
|
+
if (revealTimer.current) clearTimeout(revealTimer.current)
|
|
4499
|
+
revealTimer.current = setTimeout(() => {
|
|
4500
|
+
revealTimer.current = null
|
|
4501
|
+
setRevealedPassword('')
|
|
4502
|
+
}, 30_000)
|
|
4503
|
+
} catch (failure) {
|
|
4504
|
+
notify(t('drawer.details.revealFailed', { message: errorMessage(failure) }), 'error')
|
|
4505
|
+
} finally {
|
|
4506
|
+
setRevealing(false)
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
async function copyRevealed() {
|
|
4511
|
+
if (!revealedPassword) return
|
|
4512
|
+
const copied = await copyTextToClipboard(revealedPassword)
|
|
4513
|
+
notify(copied ? t('drawer.details.passwordCopied') : t('drawer.details.revealFailed', { message: t('terminal.copyFailed') }), copied ? 'success' : 'error')
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4440
4516
|
async function submitPassword() {
|
|
4441
4517
|
if (!password || saving) return
|
|
4442
4518
|
setSaving(true)
|
|
4443
4519
|
const ok = await savePassword(host, password)
|
|
4444
4520
|
setSaving(false)
|
|
4445
|
-
if (ok)
|
|
4521
|
+
if (ok) {
|
|
4522
|
+
setPassword('')
|
|
4523
|
+
hideRevealed()
|
|
4524
|
+
}
|
|
4446
4525
|
}
|
|
4447
4526
|
|
|
4448
4527
|
return h(
|
|
@@ -4484,6 +4563,30 @@ window.__ModuleLoader__.load({
|
|
|
4484
4563
|
h('h3', { className: 'ssh-sectionTitle' }, t('drawer.details.credentials')),
|
|
4485
4564
|
h(Badge, { tone: credential.tone }, credential.label),
|
|
4486
4565
|
),
|
|
4566
|
+
h(
|
|
4567
|
+
'div',
|
|
4568
|
+
{ className: 'ssh-credentialReveal' },
|
|
4569
|
+
h(
|
|
4570
|
+
Button,
|
|
4571
|
+
{
|
|
4572
|
+
size: 'sm',
|
|
4573
|
+
variant: 'ghost',
|
|
4574
|
+
onClick: () => void toggleReveal(),
|
|
4575
|
+
disabled: revealing || (!host.passwordConfigured && !revealedPassword),
|
|
4576
|
+
},
|
|
4577
|
+
revealing ? h(Spinner, { size: 12 }) : revealedPassword ? h(IconEyeOff, { size: 13 }) : h(IconEye, { size: 13 }),
|
|
4578
|
+
revealedPassword ? t('drawer.details.hide') : t('drawer.details.reveal'),
|
|
4579
|
+
),
|
|
4580
|
+
revealedPassword
|
|
4581
|
+
? h('code', { className: 'ssh-revealValue' }, revealedPassword)
|
|
4582
|
+
: null,
|
|
4583
|
+
revealedPassword
|
|
4584
|
+
? h(IconButton, { title: t('drawer.details.copyPassword'), onClick: () => void copyRevealed() }, h(IconCopy, { size: 14 }))
|
|
4585
|
+
: null,
|
|
4586
|
+
),
|
|
4587
|
+
revealedPassword
|
|
4588
|
+
? h('div', { className: 'ssh-sectionHint ssh-revealWarning' }, h(IconEye, { size: 12 }), h('span', null, t('drawer.details.revealWarning')))
|
|
4589
|
+
: null,
|
|
4487
4590
|
h(
|
|
4488
4591
|
'div',
|
|
4489
4592
|
{ className: 'ssh-credentialRow' },
|
|
@@ -4505,7 +4608,7 @@ window.__ModuleLoader__.load({
|
|
|
4505
4608
|
host.passwordConfigured
|
|
4506
4609
|
? h(
|
|
4507
4610
|
Button,
|
|
4508
|
-
{ size: 'sm', variant: 'dangerGhost', onClick: () => void removePassword(host) },
|
|
4611
|
+
{ size: 'sm', variant: 'dangerGhost', onClick: () => { hideRevealed(); void removePassword(host) } },
|
|
4509
4612
|
t('drawer.details.clearPassword'),
|
|
4510
4613
|
)
|
|
4511
4614
|
: null,
|
|
@@ -4590,6 +4693,9 @@ window.__ModuleLoader__.load({
|
|
|
4590
4693
|
const [password, setPassword] = React.useState('')
|
|
4591
4694
|
const [passwordError, setPasswordError] = React.useState('')
|
|
4592
4695
|
const [formError, setFormError] = React.useState('')
|
|
4696
|
+
const [groupMenuOpen, setGroupMenuOpen] = React.useState(false)
|
|
4697
|
+
const groupMenuRef = React.useRef(null)
|
|
4698
|
+
const groupOptions = (model.allGroups ?? []).filter((group) => group.key !== '').map((group) => group.key)
|
|
4593
4699
|
const busy = isPending('editor')
|
|
4594
4700
|
const passwordRequired = !original || original.passwordConfigured !== true
|
|
4595
4701
|
|
|
@@ -4619,6 +4725,27 @@ window.__ModuleLoader__.load({
|
|
|
4619
4725
|
}
|
|
4620
4726
|
})
|
|
4621
4727
|
|
|
4728
|
+
React.useEffect(() => {
|
|
4729
|
+
if (!groupMenuOpen) return undefined
|
|
4730
|
+
const close = (event) => {
|
|
4731
|
+
if (groupMenuRef.current && groupMenuRef.current.contains(event.target)) return
|
|
4732
|
+
setGroupMenuOpen(false)
|
|
4733
|
+
}
|
|
4734
|
+
const onKey = (event) => {
|
|
4735
|
+
if (event.key === 'Escape') {
|
|
4736
|
+
event.preventDefault()
|
|
4737
|
+
event.stopPropagation()
|
|
4738
|
+
setGroupMenuOpen(false)
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
document.addEventListener('mousedown', close)
|
|
4742
|
+
document.addEventListener('keydown', onKey, true)
|
|
4743
|
+
return () => {
|
|
4744
|
+
document.removeEventListener('mousedown', close)
|
|
4745
|
+
document.removeEventListener('keydown', onKey, true)
|
|
4746
|
+
}
|
|
4747
|
+
}, [groupMenuOpen])
|
|
4748
|
+
|
|
4622
4749
|
async function submit() {
|
|
4623
4750
|
const alias = draft.host.trim()
|
|
4624
4751
|
const hostname = draft.hostname.trim()
|
|
@@ -4745,11 +4872,59 @@ window.__ModuleLoader__.load({
|
|
|
4745
4872
|
h(
|
|
4746
4873
|
Field,
|
|
4747
4874
|
{ label: t('editor.group') },
|
|
4748
|
-
h(
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4875
|
+
h(
|
|
4876
|
+
'div',
|
|
4877
|
+
{ className: 'ssh-groupCombo', ref: groupMenuRef },
|
|
4878
|
+
h(TextInput, {
|
|
4879
|
+
value: draft.group,
|
|
4880
|
+
onChange: (value) => update('group', value),
|
|
4881
|
+
placeholder: t('editor.groupPlaceholder'),
|
|
4882
|
+
}),
|
|
4883
|
+
h(
|
|
4884
|
+
'button',
|
|
4885
|
+
{
|
|
4886
|
+
type: 'button',
|
|
4887
|
+
className: 'ssh-groupToggle',
|
|
4888
|
+
title: t('editor.groupChoose'),
|
|
4889
|
+
'aria-label': t('editor.groupChoose'),
|
|
4890
|
+
'aria-haspopup': 'listbox',
|
|
4891
|
+
'aria-expanded': groupMenuOpen,
|
|
4892
|
+
onClick: () => setGroupMenuOpen((value) => !value),
|
|
4893
|
+
},
|
|
4894
|
+
h(IconChevronDown, { size: 13 }),
|
|
4895
|
+
),
|
|
4896
|
+
groupMenuOpen
|
|
4897
|
+
? h(
|
|
4898
|
+
'div',
|
|
4899
|
+
{ className: 'ssh-groupMenu', role: 'listbox' },
|
|
4900
|
+
h('button', {
|
|
4901
|
+
type: 'button',
|
|
4902
|
+
role: 'option',
|
|
4903
|
+
'aria-selected': draft.group.trim() === '',
|
|
4904
|
+
className: cx('ssh-groupOption', draft.group.trim() === '' && 'active'),
|
|
4905
|
+
onClick: () => {
|
|
4906
|
+
update('group', '')
|
|
4907
|
+
setGroupMenuOpen(false)
|
|
4908
|
+
},
|
|
4909
|
+
}, t('editor.groupNone')),
|
|
4910
|
+
groupOptions.length === 0
|
|
4911
|
+
? h('div', { className: 'ssh-groupEmpty' }, t('editor.groupNoOptions'))
|
|
4912
|
+
: groupOptions.map((option) =>
|
|
4913
|
+
h('button', {
|
|
4914
|
+
key: option,
|
|
4915
|
+
type: 'button',
|
|
4916
|
+
role: 'option',
|
|
4917
|
+
'aria-selected': draft.group.trim() === option,
|
|
4918
|
+
className: cx('ssh-groupOption', draft.group.trim() === option && 'active'),
|
|
4919
|
+
onClick: () => {
|
|
4920
|
+
update('group', option)
|
|
4921
|
+
setGroupMenuOpen(false)
|
|
4922
|
+
},
|
|
4923
|
+
}, option),
|
|
4924
|
+
),
|
|
4925
|
+
)
|
|
4926
|
+
: null,
|
|
4927
|
+
),
|
|
4753
4928
|
),
|
|
4754
4929
|
h(
|
|
4755
4930
|
Field,
|
|
@@ -5298,7 +5473,7 @@ body.ssh-col-resizing,body.ssh-col-resizing *{cursor:col-resize!important;user-s
|
|
|
5298
5473
|
.ssh-mainTop{flex:0 0 auto;display:flex;align-items:center;flex-wrap:wrap;gap:8px;row-gap:6px;padding:10px 14px;border-bottom:.5px solid var(--ssh-border);min-height:56px}
|
|
5299
5474
|
.ssh-context{display:flex;align-items:baseline;gap:6px;min-width:0;max-width:180px;flex:0 0 auto}
|
|
5300
5475
|
.ssh-contextName{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5301
|
-
.ssh-contextCount{font-size:11px;color:var(--ssh-text-
|
|
5476
|
+
.ssh-contextCount{font-size:11px;color:var(--ssh-text-2);font-variant-numeric:tabular-nums}
|
|
5302
5477
|
.ssh-search{position:relative;display:flex;align-items:center;flex:0 1 300px;min-width:120px}
|
|
5303
5478
|
.ssh-search > svg{position:absolute;left:10px;color:var(--ssh-dim);pointer-events:none;z-index:1}
|
|
5304
5479
|
.ssh-search .ssh-input{height:30px;padding-left:31px;border-radius:999px;background:var(--ssh-layer-1);font-size:12px}
|
|
@@ -5312,7 +5487,7 @@ body.ssh-col-resizing,body.ssh-col-resizing *{cursor:col-resize!important;user-s
|
|
|
5312
5487
|
.ssh-table{flex:1;min-height:0;display:flex;flex-direction:column;min-width:0}
|
|
5313
5488
|
.ssh-tableScroll{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column}
|
|
5314
5489
|
.ssh-grid{display:grid;align-items:center;column-gap:8px;padding:0 12px;width:100%;min-width:0}
|
|
5315
|
-
.ssh-head{flex:0 0 auto;height:38px;border-bottom:.5px solid var(--ssh-border);color:var(--ssh-text-
|
|
5490
|
+
.ssh-head{flex:0 0 auto;height:38px;border-bottom:.5px solid var(--ssh-border);color:var(--ssh-text-2);font-size:11px;font-weight:500;background:var(--ssh-bg);position:sticky;top:0;z-index:3;min-width:640px}
|
|
5316
5491
|
.ssh-cell{min-width:0;display:flex;align-items:center;gap:6px;overflow:hidden}
|
|
5317
5492
|
.ssh-headBtn{appearance:none;display:inline-flex;align-items:center;gap:4px;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-size:11px;font-weight:500;cursor:pointer;white-space:nowrap;min-width:0}
|
|
5318
5493
|
.ssh-headBtn:hover{color:var(--ssh-text)}
|
|
@@ -5330,32 +5505,32 @@ body.ssh-col-resizing,body.ssh-col-resizing *{cursor:col-resize!important;user-s
|
|
|
5330
5505
|
.ssh-colLatency{min-width:0}
|
|
5331
5506
|
.ssh-colSystem{min-width:0}
|
|
5332
5507
|
.ssh-osBadge{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;background:var(--ssh-layer-3);color:var(--ssh-brand)}
|
|
5333
|
-
.ssh-osBadge.muted{color:var(--ssh-
|
|
5508
|
+
.ssh-osBadge.muted{color:var(--ssh-text-2)}
|
|
5334
5509
|
.ssh-osName{font-size:11.5px;color:var(--ssh-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5335
|
-
.ssh-osName.muted{color:var(--ssh-
|
|
5510
|
+
.ssh-osName.muted{color:var(--ssh-text-2)}
|
|
5336
5511
|
.ssh-latency{appearance:none;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--ssh-text-2);font:inherit;font-size:11.5px;white-space:nowrap;cursor:pointer}
|
|
5337
|
-
.ssh-latency.ok{color:var(--
|
|
5338
|
-
.ssh-latency.muted{color:var(--ssh-
|
|
5512
|
+
.ssh-latency.ok{color:var(--ssh-success)}
|
|
5513
|
+
.ssh-latency.muted{color:var(--ssh-text-2)}
|
|
5339
5514
|
.ssh-latency:hover:not(:disabled){color:var(--ssh-text)}
|
|
5340
5515
|
.ssh-latency:disabled{cursor:default}
|
|
5341
5516
|
.ssh-latencyDot{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.55}
|
|
5342
5517
|
.ssh-latencyIcon{display:inline-flex;color:var(--dsw-alias-state-success-label,var(--ssh-success))}
|
|
5343
5518
|
.ssh-colName{flex-direction:column;align-items:flex-start;gap:0}
|
|
5344
5519
|
.ssh-nameMain{display:flex;align-items:center;gap:5px;max-width:100%;font-size:12.5px;font-weight:500;color:var(--ssh-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5345
|
-
.ssh-nameSub{max-width:100%;font-size:10.5px;color:var(--ssh-
|
|
5520
|
+
.ssh-nameSub{max-width:100%;font-size:10.5px;color:var(--ssh-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5346
5521
|
.ssh-favMark{color:var(--ssh-warn);display:inline-flex;flex:0 0 auto}
|
|
5347
5522
|
.ssh-colAddress{gap:3px;min-width:0}
|
|
5348
|
-
.ssh-address{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--ssh-text
|
|
5523
|
+
.ssh-address{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--ssh-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
|
5349
5524
|
.ssh-inlineActions{display:inline-flex;gap:1px;flex:0 0 auto;opacity:.45;transition:opacity .12s ease}
|
|
5350
5525
|
.ssh-row:hover .ssh-inlineActions{opacity:1}
|
|
5351
5526
|
.ssh-miniBtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--ssh-text-3);cursor:pointer}
|
|
5352
5527
|
.ssh-miniBtn:hover{background:var(--ssh-hover);color:var(--ssh-text)}
|
|
5353
5528
|
.ssh-colInfo{gap:4px;row-gap:3px;flex-wrap:wrap;align-content:center;overflow:visible}
|
|
5354
5529
|
.ssh-chip{display:inline-flex;flex:0 0 auto;align-items:center;height:21px;max-width:100%;padding:0 7px;border-radius:6px;border:.5px solid var(--ssh-border);background:var(--ssh-layer-3);font-size:10.5px;color:var(--ssh-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5355
|
-
.ssh-mutedText{font-size:11.5px;color:var(--ssh-
|
|
5530
|
+
.ssh-mutedText{font-size:11.5px;color:var(--ssh-text-2)}
|
|
5356
5531
|
.ssh-colNotes{min-width:0}
|
|
5357
5532
|
.ssh-noteText{font-size:11.5px;color:var(--ssh-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5358
|
-
.ssh-noteText.muted{color:var(--ssh-
|
|
5533
|
+
.ssh-noteText.muted{color:var(--ssh-text-2)}
|
|
5359
5534
|
.ssh-colActions{gap:4px;justify-content:flex-start;overflow:visible}
|
|
5360
5535
|
.ssh-colGear{justify-content:flex-end}
|
|
5361
5536
|
.ssh-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:56px 24px;text-align:center;color:var(--ssh-text-3);font-size:12px;min-height:0}
|
|
@@ -5391,6 +5566,14 @@ body.ssh-col-resizing,body.ssh-col-resizing *{cursor:col-resize!important;user-s
|
|
|
5391
5566
|
.ssh-input.mono,.ssh-textarea.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}
|
|
5392
5567
|
.ssh-textarea{height:auto;padding:9px 11px;line-height:1.65;resize:vertical;min-height:74px}
|
|
5393
5568
|
.ssh-field{display:flex;flex-direction:column;gap:5px;min-width:0}
|
|
5569
|
+
.ssh-groupCombo{position:relative;display:flex;align-items:center;gap:4px}
|
|
5570
|
+
.ssh-groupCombo .ssh-input{flex:1;min-width:0}
|
|
5571
|
+
.ssh-groupToggle{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:.5px solid var(--ssh-border-strong);border-radius:var(--ssh-radius-sm);background:var(--ssh-layer-2);color:var(--ssh-text-2);cursor:pointer;flex:0 0 auto}
|
|
5572
|
+
.ssh-groupToggle:hover{background:var(--ssh-hover);color:var(--ssh-text)}
|
|
5573
|
+
.ssh-groupMenu{position:absolute;z-index:12;top:calc(100% + 5px);left:0;right:0;max-height:190px;overflow:auto;padding:4px;border:.5px solid var(--ssh-border-strong);border-radius:var(--ssh-radius-md);background:var(--ssh-layer-1);box-shadow:var(--ssh-shadow)}
|
|
5574
|
+
.ssh-groupOption{appearance:none;display:block;width:100%;padding:6px 8px;border:0;border-radius:6px;background:transparent;color:var(--ssh-text);font:inherit;font-size:12px;text-align:left;cursor:pointer}
|
|
5575
|
+
.ssh-groupOption:hover,.ssh-groupOption.active{background:var(--ssh-hover)}
|
|
5576
|
+
.ssh-groupEmpty{padding:6px 8px;font-size:11.5px;color:var(--ssh-text-2)}
|
|
5394
5577
|
.ssh-field.wide{grid-column:1/-1}
|
|
5395
5578
|
.ssh-label{font-size:11.5px;font-weight:500;color:var(--ssh-text-2)}
|
|
5396
5579
|
.ssh-hint{font-size:10.5px;color:var(--ssh-text-3);line-height:1.55}
|
|
@@ -5544,6 +5727,9 @@ body.ssh-col-resizing,body.ssh-col-resizing *{cursor:col-resize!important;user-s
|
|
|
5544
5727
|
.ssh-kvValue{color:var(--ssh-text-2);min-width:0;word-break:break-word}
|
|
5545
5728
|
.ssh-kvValue.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}
|
|
5546
5729
|
.ssh-sectionHint{font-size:11.5px;color:var(--ssh-text-3);line-height:1.65}
|
|
5730
|
+
.ssh-credentialReveal{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:2px}
|
|
5731
|
+
.ssh-revealValue{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;padding:5px 8px;border:.5px solid var(--ssh-border-strong);border-radius:6px;background:var(--ssh-layer-3);color:var(--ssh-text);word-break:break-all;user-select:all}
|
|
5732
|
+
.ssh-revealWarning{display:flex;align-items:flex-start;gap:5px;color:var(--ssh-warn)}
|
|
5547
5733
|
.ssh-tags{display:flex;gap:5px;flex-wrap:wrap}
|
|
5548
5734
|
.ssh-detailsActions{display:flex;gap:8px;padding-top:2px}
|
|
5549
5735
|
.ssh-credentialRow{display:flex;align-items:center;gap:6px}
|
package/index.js
CHANGED
|
@@ -167,6 +167,12 @@ export async function runAction(paths, action, params = {}) {
|
|
|
167
167
|
}
|
|
168
168
|
case 'credential/delete':
|
|
169
169
|
return await removePassword(paths, requireString(params, 'host'))
|
|
170
|
+
case 'credential/reveal': {
|
|
171
|
+
const host = await findHost(paths, params.host)
|
|
172
|
+
const password = await getPassword(paths, host.host)
|
|
173
|
+
if (!password) throw new Error('No password is stored for this host')
|
|
174
|
+
return { password }
|
|
175
|
+
}
|
|
170
176
|
case 'host/metadata': {
|
|
171
177
|
const alias = requireString(params, 'host')
|
|
172
178
|
await findHost(paths, alias)
|