dsh-vault 0.3.0 → 0.5.0
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-zh.md +18 -3
- package/README.md +18 -3
- package/lib/client.js +200 -46
- package/lib/client.js.map +1 -1
- package/lib/index.js +641 -13
- package/lib/store.js +247 -9
- package/package.json +1 -1
package/README-zh.md
CHANGED
|
@@ -41,11 +41,24 @@ dsh-vault 是一个面向 DeepSeek Harness 的安全加密插件:把你在使
|
|
|
41
41
|
|---|---|
|
|
42
42
|
| `vault_add` | 新增条目(上述字段任意组合;空字符串/空数组字段会被忽略) |
|
|
43
43
|
| `vault_get` | 按 id 读取完整条目(含全部密钥) |
|
|
44
|
-
| `vault_search` | 跨标题/分类/用户名/邮箱/手机/主机/端口/URL
|
|
44
|
+
| `vault_search` | 跨标题/分类/用户名/邮箱/手机/主机/端口/URL/备注/标签/自定义字段(含数字/布尔/嵌套值;空格分隔多词 OR 命中)检索,返回无密摘要;`limit` 须为 1–100 的整数 |
|
|
45
45
|
| `vault_update` | 按 id 更新字段(未提供的字段保留;空字符串清除该字段;`title` 可改名) |
|
|
46
|
-
| `vault_delete` |
|
|
46
|
+
| `vault_delete` | 软删除条目(移入回收站,磁盘上仍加密保留) |
|
|
47
|
+
| `vault_restore` / `vault_purge` | 从回收站恢复 / 从磁盘永久移除 |
|
|
48
|
+
| `vault_lock` / `vault_unlock` | 显式锁定保险库(清空内存密钥)/ 重新解锁 |
|
|
47
49
|
| `vault_totp` | 为存储的 otpSecret(或直接传入的 Base32/otpauth URI)生成当前 6 位动态验证码 |
|
|
48
50
|
| `vault_generate_password` | 生成强随机密码(长度/字符集/去歧义/分组可选;`group` 须为 ≥2 的整数) |
|
|
51
|
+
| `vault_strength` | 零依赖密码强度评估(0–100 分,weak/fair/strong/very strong) |
|
|
52
|
+
| `vault_rekey` | 原地升级 scrypt KDF 参数并重加密 |
|
|
53
|
+
| `vault_import_csv` | 从 CSV 批量导入凭据(自定义列变为 fields) |
|
|
54
|
+
| `vault_totp_uri` | 为存储的或裸 TOTP 密钥生成 otpauth:// 配置 URI |
|
|
55
|
+
| `vault_switch` / `vault_list` | 按名称切换当前保险库 / 列出可用保险库 |
|
|
56
|
+
| `vault_rotation` | 报告已过期 / 待轮换 / 即将过期的凭据(不含密钥) |
|
|
57
|
+
| `vault_health` | 扫描弱密码与跨条目复用凭据(不含密钥) |
|
|
58
|
+
| `vault_export` / `vault_import` | 整库加密备份/迁移(独立导出密码) |
|
|
59
|
+
| `vault_fill` | 按 host/URL/用户名/标题匹配条目并返回其凭据 |
|
|
60
|
+
| `vault_env` | 把标记 env 的条目(tags 含 `env`)渲染为 `KEY=VALUE` 行 |
|
|
61
|
+
| `vault_templates` | 返回某类凭据(ssh/api-key/oauth 等)的推荐字段 |
|
|
49
62
|
|
|
50
63
|
**典型开发场景**:存一条 SSH 凭据(`kind: ssh` + host/port/username/password 或 privateKey),开发时让模型 `vault_search` 找主机、`vault_get` 取连接信息;存 API 网关的 `api-key`/`oauth` 条目管理 access/refresh token 轮换。
|
|
51
64
|
|
|
@@ -130,6 +143,8 @@ tarball 自带预构建 `lib/` 产物,无需构建或 allowBuilds。
|
|
|
130
143
|
| `name` | 保险库名,用于默认路径(如 `name: work` → `$DSH_HOME/vault/work.json`) |
|
|
131
144
|
| `accessMode` | 模型工具的访问策略,三态:`readonly`(只读,工具与设置页的增/改/删全部被拒绝)、`ask`(默认——写入前询问,每次增/改/删都会走 harness 审批通道请你确认)、`auto`(自动读写,无需逐次确认)。设置页提供同样的三选一下拉并持久化到 `<vault 目录>/access.json`。 |
|
|
132
145
|
| `autoCapture` | `false`(默认)。设为 `true` 时,系统提示词会指导模型识别对话中出现的凭据,并**按用户偏好**用 `vault_add` 提供保存。 |
|
|
146
|
+
| `lockTimeoutSeconds` | 自动锁库:空闲超过该秒数后自动重新锁定(清空内存密钥),之后每次读写需 `vault_unlock`。`0`/缺省为禁用。 |
|
|
147
|
+
| `exportPasswordEnv` | 存放 `vault_export`/`vault_import` 导出密码的环境变量名(绝不能作为模型参数传入)。 |
|
|
133
148
|
|
|
134
149
|
示例:
|
|
135
150
|
|
|
@@ -142,7 +157,7 @@ tarball 自带预构建 `lib/` 产物,无需构建或 allowBuilds。
|
|
|
142
157
|
autoCapture: true
|
|
143
158
|
```
|
|
144
159
|
|
|
145
|
-
`autoCapture: true` 时,当你在对话中分享凭据(如 "我的 npm token 是 npm_…"),助手会提议存入;你同意后立即调用 `vault_add`。`autoCapture` 关闭时,只有你明确要求才保存。设置页显示当前模式(只读 / 写入前询问 /
|
|
160
|
+
`autoCapture: true` 时,当你在对话中分享凭据(如 "我的 npm token 是 npm_…"),助手会提议存入;你同意后立即调用 `vault_add`。`autoCapture` 关闭时,只有你明确要求才保存。设置页显示当前模式(只读 / 写入前询问 / 自动读写)可用下拉切换,还有类型筛选、健康与轮换摘要、回收站视图、以及默认遮罩的密钥字段(显示/隐藏切换)。
|
|
146
161
|
|
|
147
162
|
首次调用任一工具时自动创建保险库;之后每次启动用主密码重新解锁。**忘记主密码 = 数据永久丢失**(无后门,这是设计使然)。
|
|
148
163
|
|
package/README.md
CHANGED
|
@@ -43,11 +43,24 @@ Each record has a `title`, an optional `kind`, and any combination of fields:
|
|
|
43
43
|
|---|---|
|
|
44
44
|
| `vault_add` | Add an entry (any combination of fields; empty strings/arrays are ignored) |
|
|
45
45
|
| `vault_get` | Read a full entry by id (including all secrets) |
|
|
46
|
-
| `vault_search` | Search titles/categories/usernames/emails/phones/hosts/ports/URLs/notes/tags/custom fields (incl. numeric/boolean/nested values); returns secret-free summaries; `limit` must be an integer 1–100 |
|
|
46
|
+
| `vault_search` | Search titles/categories/usernames/emails/phones/hosts/ports/URLs/notes/tags/custom fields (incl. numeric/boolean/nested values; whitespace-separated terms OR-match); returns secret-free summaries; `limit` must be an integer 1–100 |
|
|
47
47
|
| `vault_update` | Update fields by id (unprovided fields kept; empty string clears a field; `title` is renamable) |
|
|
48
|
-
| `vault_delete` |
|
|
48
|
+
| `vault_delete` | Soft-delete an entry (moves it to the trash, still encrypted on disk) |
|
|
49
|
+
| `vault_restore` / `vault_purge` | Bring a trashed entry back / permanently remove it from disk |
|
|
50
|
+
| `vault_lock` / `vault_unlock` | Explicitly lock the vault (wipe the in-memory key) / re-unlock it |
|
|
49
51
|
| `vault_totp` | Generate the current 6-digit code for a stored otpSecret (or a bare Base32 / otpauth URI) |
|
|
50
52
|
| `vault_generate_password` | Generate a strong random password (length/character classes/ambiguity exclusion/grouping; `group` must be an integer ≥ 2) |
|
|
53
|
+
| `vault_strength` | Zero-dependency password strength estimate (score 0–100, weak/fair/strong/very strong) |
|
|
54
|
+
| `vault_rekey` | Upgrade the vault to fresh scrypt KDF parameters in place |
|
|
55
|
+
| `vault_import_csv` | Bulk-import credentials from a CSV file (custom columns become fields) |
|
|
56
|
+
| `vault_totp_uri` | Build an otpauth:// provisioning URI for a stored or bare TOTP secret |
|
|
57
|
+
| `vault_switch` / `vault_list` | Switch the active vault by name / list available vaults |
|
|
58
|
+
| `vault_rotation` | Report expired / due-for-rotation / expiring-soon credentials (no secrets) |
|
|
59
|
+
| `vault_health` | Scan for weak passwords and credentials reused across entries (no secrets) |
|
|
60
|
+
| `vault_export` / `vault_import` | Portable encrypted backup/migration of the whole vault (separate export password) |
|
|
61
|
+
| `vault_fill` | Find the entry matching a host/URL/username/title and return its credentials |
|
|
62
|
+
| `vault_env` | Render env-flagged entries (tags contain `env`) as `KEY=VALUE` lines |
|
|
63
|
+
| `vault_templates` | List recommended fields for a credential kind (ssh / api-key / oauth / …) |
|
|
51
64
|
|
|
52
65
|
**Typical workflows**: store an SSH credential (`kind: ssh` + host/port/username/password or privateKey) and have the model `vault_search` for the host then `vault_get` the connection details; keep `api-key`/`oauth` entries for API-gateway access/refresh token rotation.
|
|
53
66
|
|
|
@@ -132,6 +145,8 @@ The tarball ships prebuilt `lib/` artifacts, so no build step or `allowBuilds` i
|
|
|
132
145
|
| `name` | Vault name for the default path (e.g. `name: work` → `$DSH_HOME/vault/work.json`) |
|
|
133
146
|
| `accessMode` | Access policy for the model tools. Three states: `readonly` (mutations rejected on tools + UI), `ask` (default — reads free, every add/update/delete goes through the harness approval channel so the user confirms each write), or `auto` (automatic read-write, no per-call prompt). The Settings UI offers this exact three-way choice and persists it to `<vault dir>/access.json`. |
|
|
134
147
|
| `autoCapture` | `false` (default). When `true`, the system prompt instructs the model to detect credentials shared in conversation and — per user preference — offer to save them with `vault_add`. |
|
|
148
|
+
| `lockTimeoutSeconds` | Auto-lock: after this many seconds of inactivity the vault re-locks (key wiped) and every read/write requires `vault_unlock`. `0`/absent disables. |
|
|
149
|
+
| `exportPasswordEnv` | Environment variable holding the export/import password for `vault_export`/`vault_import` (never pass it as a model argument). |
|
|
135
150
|
|
|
136
151
|
Example:
|
|
137
152
|
|
|
@@ -144,7 +159,7 @@ Example:
|
|
|
144
159
|
autoCapture: true
|
|
145
160
|
```
|
|
146
161
|
|
|
147
|
-
With `autoCapture: true`, when you share a credential in chat (e.g. "my npm token is npm_…"), the assistant offers to store it; on your consent it calls `vault_add` immediately. With `autoCapture` off, credentials are only saved when you explicitly ask. The Settings UI shows the current mode (read-only / ask-before-write / automatic read-write) with a dropdown to switch it,
|
|
162
|
+
With `autoCapture: true`, when you share a credential in chat (e.g. "my npm token is npm_…"), the assistant offers to store it; on your consent it calls `vault_add` immediately. With `autoCapture` off, credentials are only saved when you explicitly ask. The Settings UI shows the current mode (read-only / ask-before-write / automatic read-write) with a dropdown to switch it, a kind filter, a health & rotation summary, a trash view, and masked secret fields with a Show/Hide toggle.
|
|
148
163
|
|
|
149
164
|
The vault is created automatically on first tool use; every launch re-unlocks with the master password. **Forgetting the master password = permanent data loss** (no backdoor — by design).
|
|
150
165
|
|
package/lib/client.js
CHANGED
|
@@ -7,7 +7,7 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
//#region \0dsh-css:/Users/zhipeng/Desktop/work/dsh test/dsh-vault/src/client/VaultSection.module.css.mjs
|
|
10
|
-
const css = ".t_j5va_section{flex-direction:column;gap:12px;padding:16px 0;display:flex}.t_j5va_intro{opacity:.75;margin:0;font-size:13px}.t_j5va_toolbar{align-items:center;gap:8px;display:flex}.t_j5va_searchBox{flex:1;display:flex}.t_j5va_searchBox input{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;flex:1;padding:6px 10px;font-size:13px}.t_j5va_addButton,.t_j5va_saveButton{background:var(--color-accent,#4f8cff);color:#fff;cursor:pointer;border:1px solid #0000;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_addButton:disabled,.t_j5va_saveButton:disabled,.t_j5va_rowActions button:disabled{opacity:.5;cursor:not-allowed}.t_j5va_list{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}.t_j5va_row{border:1px solid var(--color-border,#333);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 10px;display:flex}.t_j5va_rowMain{flex-direction:column;gap:2px;min-width:0;display:flex}.t_j5va_title{font-size:14px;font-weight:600}.t_j5va_identity{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.t_j5va_totp{font-family:var(--font-mono,monospace);color:var(--color-accent,#4f8cff);font-size:13px}.t_j5va_rowActions{flex-shrink:0;align-items:center;gap:6px;display:flex}.t_j5va_rowActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:4px 8px;font-size:12px}.t_j5va_deleteButton{color:#e5484d}.t_j5va_copied{color:#30a46c;font-size:12px}.t_j5va_empty,.t_j5va_status,.t_j5va_error{opacity:.8;font-size:13px}.t_j5va_modeReadonly,.t_j5va_modeAsk,.t_j5va_modeAuto{border-radius:999px;width:fit-content;margin:0;padding:4px 10px;font-size:12px;display:inline-block}.t_j5va_modeReadonly{color:#e5484d;border:1px solid #e5484d80}.t_j5va_modeAsk{color:#f5a524;border:1px solid #f5a52480}.t_j5va_modeAuto{color:#30a46c;border:1px solid #30a46c80}.t_j5va_policyBar{flex-direction:column;align-items:flex-start;gap:6px;display:flex}.t_j5va_policyField{align-items:center;gap:8px;font-size:13px;display:flex}.t_j5va_policyField select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:4px 8px;font-size:13px}.t_j5va_emptyBox{flex-direction:column;gap:4px;display:flex}.t_j5va_emptyHint{opacity:.65;margin:0;font-size:12px}.t_j5va_error{color:#e5484d}.t_j5va_editor{border:1px solid var(--color-border,#444);border-radius:8px;padding:12px}.t_j5va_editorBody{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px 12px;display:grid}.t_j5va_field{flex-direction:column;gap:4px;font-size:12px;display:flex}.t_j5va_field input,.t_j5va_field select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:5px 8px;font-size:13px}.t_j5va_editorActions{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.t_j5va_editorActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:6px 12px;font-size:13px}";
|
|
10
|
+
const css = ".t_j5va_section{flex-direction:column;gap:12px;padding:16px 0;display:flex}.t_j5va_intro{opacity:.75;margin:0;font-size:13px}.t_j5va_toolbar{align-items:center;gap:8px;display:flex}.t_j5va_searchBox{flex:1;display:flex}.t_j5va_searchBox input{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;flex:1;padding:6px 10px;font-size:13px}.t_j5va_addButton,.t_j5va_saveButton{background:var(--color-accent,#4f8cff);color:#fff;cursor:pointer;border:1px solid #0000;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_addButton:disabled,.t_j5va_saveButton:disabled,.t_j5va_rowActions button:disabled{opacity:.5;cursor:not-allowed}.t_j5va_list{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}.t_j5va_row{border:1px solid var(--color-border,#333);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 10px;display:flex}.t_j5va_rowMain{flex-direction:column;gap:2px;min-width:0;display:flex}.t_j5va_title{font-size:14px;font-weight:600}.t_j5va_identity{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.t_j5va_totp{font-family:var(--font-mono,monospace);color:var(--color-accent,#4f8cff);font-size:13px}.t_j5va_rowActions{flex-shrink:0;align-items:center;gap:6px;display:flex}.t_j5va_rowActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:4px 8px;font-size:12px}.t_j5va_deleteButton{color:#e5484d}.t_j5va_copied{color:#30a46c;font-size:12px}.t_j5va_empty,.t_j5va_status,.t_j5va_error{opacity:.8;font-size:13px}.t_j5va_modeReadonly,.t_j5va_modeAsk,.t_j5va_modeAuto{border-radius:999px;width:fit-content;margin:0;padding:4px 10px;font-size:12px;display:inline-block}.t_j5va_modeReadonly{color:#e5484d;border:1px solid #e5484d80}.t_j5va_modeAsk{color:#f5a524;border:1px solid #f5a52480}.t_j5va_modeAuto{color:#30a46c;border:1px solid #30a46c80}.t_j5va_policyBar{flex-direction:column;align-items:flex-start;gap:6px;display:flex}.t_j5va_policyField{align-items:center;gap:8px;font-size:13px;display:flex}.t_j5va_policyField select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:4px 8px;font-size:13px}.t_j5va_emptyBox{flex-direction:column;gap:4px;display:flex}.t_j5va_emptyHint{opacity:.65;margin:0;font-size:12px}.t_j5va_error{color:#e5484d}.t_j5va_editor{border:1px solid var(--color-border,#444);border-radius:8px;padding:12px}.t_j5va_editorBody{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px 12px;display:grid}.t_j5va_field{flex-direction:column;gap:4px;font-size:12px;display:flex}.t_j5va_field input,.t_j5va_field select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:5px 8px;font-size:13px}.t_j5va_editorActions{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.t_j5va_editorActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_reportBox{border:1px solid #f5a52466;border-radius:8px;flex-direction:column;gap:2px;padding:8px 10px;font-size:12px;display:flex}.t_j5va_reportTitle{margin:0;font-weight:600}.t_j5va_reportLine{opacity:.8;margin:0}.t_j5va_trashButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;align-self:flex-start;padding:4px 8px;font-size:12px}.t_j5va_secretField{gap:4px;display:flex}.t_j5va_secretField input{flex:1;min-width:0}.t_j5va_revealButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:0 8px;font-size:12px}.t_j5va_kindFilter{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:6px 8px;font-size:13px}.t_j5va_highBadge{color:#e5484d;vertical-align:middle;background:#e5484d26;border-radius:4px;margin-left:6px;padding:1px 6px;font-size:10px;font-weight:600}";
|
|
11
11
|
const tagId = "dsh-vault/VaultSection.module.css";
|
|
12
12
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
13
13
|
const tag = document.createElement("style");
|
|
@@ -17,35 +17,43 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
document.head.appendChild(tag);
|
|
18
18
|
}
|
|
19
19
|
var VaultSection_module_css_default = {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"modeAsk": "t_j5va_modeAsk",
|
|
23
|
-
"rowActions": "t_j5va_rowActions",
|
|
24
|
-
"field": "t_j5va_field",
|
|
25
|
-
"totp": "t_j5va_totp",
|
|
26
|
-
"toolbar": "t_j5va_toolbar",
|
|
27
|
-
"row": "t_j5va_row",
|
|
20
|
+
"reportTitle": "t_j5va_reportTitle",
|
|
21
|
+
"secretField": "t_j5va_secretField",
|
|
28
22
|
"section": "t_j5va_section",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"saveButton": "t_j5va_saveButton",
|
|
34
|
-
"deleteButton": "t_j5va_deleteButton",
|
|
23
|
+
"totp": "t_j5va_totp",
|
|
24
|
+
"highBadge": "t_j5va_highBadge",
|
|
25
|
+
"editor": "t_j5va_editor",
|
|
26
|
+
"addButton": "t_j5va_addButton",
|
|
35
27
|
"copied": "t_j5va_copied",
|
|
36
28
|
"editorBody": "t_j5va_editorBody",
|
|
37
|
-
"
|
|
29
|
+
"toolbar": "t_j5va_toolbar",
|
|
38
30
|
"empty": "t_j5va_empty",
|
|
31
|
+
"kindFilter": "t_j5va_kindFilter",
|
|
32
|
+
"rowActions": "t_j5va_rowActions",
|
|
39
33
|
"status": "t_j5va_status",
|
|
40
|
-
"
|
|
34
|
+
"title": "t_j5va_title",
|
|
35
|
+
"modeAuto": "t_j5va_modeAuto",
|
|
36
|
+
"searchBox": "t_j5va_searchBox",
|
|
37
|
+
"modeReadonly": "t_j5va_modeReadonly",
|
|
38
|
+
"deleteButton": "t_j5va_deleteButton",
|
|
39
|
+
"emptyBox": "t_j5va_emptyBox",
|
|
40
|
+
"editorActions": "t_j5va_editorActions",
|
|
41
|
+
"intro": "t_j5va_intro",
|
|
42
|
+
"field": "t_j5va_field",
|
|
41
43
|
"list": "t_j5va_list",
|
|
44
|
+
"identity": "t_j5va_identity",
|
|
45
|
+
"error": "t_j5va_error",
|
|
46
|
+
"policyField": "t_j5va_policyField",
|
|
47
|
+
"reportLine": "t_j5va_reportLine",
|
|
48
|
+
"saveButton": "t_j5va_saveButton",
|
|
49
|
+
"reportBox": "t_j5va_reportBox",
|
|
50
|
+
"emptyHint": "t_j5va_emptyHint",
|
|
51
|
+
"revealButton": "t_j5va_revealButton",
|
|
42
52
|
"rowMain": "t_j5va_rowMain",
|
|
53
|
+
"row": "t_j5va_row",
|
|
43
54
|
"policyBar": "t_j5va_policyBar",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"editor": "t_j5va_editor",
|
|
47
|
-
"title": "t_j5va_title",
|
|
48
|
-
"emptyHint": "t_j5va_emptyHint"
|
|
55
|
+
"trashButton": "t_j5va_trashButton",
|
|
56
|
+
"modeAsk": "t_j5va_modeAsk"
|
|
49
57
|
};
|
|
50
58
|
//#endregion
|
|
51
59
|
//#region src/client/VaultSection.tsx
|
|
@@ -138,7 +146,7 @@ window.__ModuleLoader__.load({
|
|
|
138
146
|
}
|
|
139
147
|
/** Render the Vault settings section. */
|
|
140
148
|
function VaultSection(props) {
|
|
141
|
-
const { t, config, setAccessMode, list, search, get, add, update, remove, totp } = props;
|
|
149
|
+
const { t, config, setAccessMode, list, search, get, add, update, remove, trash, rotation, health, restore, totp } = props;
|
|
142
150
|
const searchId = (0, react.useId)();
|
|
143
151
|
const [query, setQuery] = (0, react.useState)("");
|
|
144
152
|
const [state, setState] = (0, react.useState)({ status: "loading" });
|
|
@@ -149,7 +157,12 @@ window.__ModuleLoader__.load({
|
|
|
149
157
|
const [copiedId, setCopiedId] = (0, react.useState)(null);
|
|
150
158
|
const [codeMap, setCodeMap] = (0, react.useState)({});
|
|
151
159
|
const [tagsDraft, setTagsDraft] = (0, react.useState)("");
|
|
160
|
+
const [revealed, setRevealed] = (0, react.useState)({});
|
|
161
|
+
const [kindFilter, setKindFilter] = (0, react.useState)("");
|
|
152
162
|
const [policy, setPolicy] = (0, react.useState)(null);
|
|
163
|
+
const [showTrash, setShowTrash] = (0, react.useState)(false);
|
|
164
|
+
const [trashEntries, setTrashEntries] = (0, react.useState)([]);
|
|
165
|
+
const [report, setReport] = (0, react.useState)(null);
|
|
153
166
|
const readonly = policy?.accessMode === "readonly";
|
|
154
167
|
(0, react.useEffect)(() => {
|
|
155
168
|
let current = true;
|
|
@@ -341,25 +354,41 @@ window.__ModuleLoader__.load({
|
|
|
341
354
|
}),
|
|
342
355
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
343
356
|
className: VaultSection_module_css_default.toolbar,
|
|
344
|
-
children: [
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
357
|
+
children: [
|
|
358
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
359
|
+
className: VaultSection_module_css_default.searchBox,
|
|
360
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
361
|
+
className: "sr-only",
|
|
362
|
+
children: t("searchPlaceholder")
|
|
363
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
364
|
+
id: searchId,
|
|
365
|
+
type: "search",
|
|
366
|
+
placeholder: t("searchPlaceholder"),
|
|
367
|
+
value: query,
|
|
368
|
+
onChange: (event) => setQuery(event.target.value)
|
|
369
|
+
})]
|
|
370
|
+
}),
|
|
371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
372
|
+
className: VaultSection_module_css_default.kindFilter,
|
|
373
|
+
value: kindFilter,
|
|
374
|
+
onChange: (e) => setKindFilter(e.target.value),
|
|
375
|
+
"aria-label": t("fieldKind"),
|
|
376
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
377
|
+
value: "",
|
|
378
|
+
children: t("allKinds")
|
|
379
|
+
}), Object.entries(KIND_KEYS).map(([value, key]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
380
|
+
value,
|
|
381
|
+
children: t(key)
|
|
382
|
+
}, value))]
|
|
383
|
+
}),
|
|
384
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
385
|
+
type: "button",
|
|
386
|
+
className: VaultSection_module_css_default.addButton,
|
|
387
|
+
onClick: startCreate,
|
|
388
|
+
disabled: busy || readonly,
|
|
389
|
+
children: ["+ ", t("add")]
|
|
390
|
+
})
|
|
391
|
+
]
|
|
363
392
|
}),
|
|
364
393
|
policy !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
365
394
|
className: VaultSection_module_css_default.policyBar,
|
|
@@ -424,18 +453,95 @@ window.__ModuleLoader__.load({
|
|
|
424
453
|
children: readonly ? t("emptyHintReadonly") : t("emptyHint")
|
|
425
454
|
})]
|
|
426
455
|
}),
|
|
456
|
+
report !== null && (report.rotation.length > 0 || report.weak.length > 0 || report.reused.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
457
|
+
className: VaultSection_module_css_default.reportBox,
|
|
458
|
+
children: [
|
|
459
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
460
|
+
className: VaultSection_module_css_default.reportTitle,
|
|
461
|
+
children: t("reportTitle")
|
|
462
|
+
}),
|
|
463
|
+
report.rotation.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
464
|
+
className: VaultSection_module_css_default.reportLine,
|
|
465
|
+
children: [
|
|
466
|
+
t("reportRotation"),
|
|
467
|
+
": ",
|
|
468
|
+
report.rotation.length
|
|
469
|
+
]
|
|
470
|
+
}),
|
|
471
|
+
report.weak.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
472
|
+
className: VaultSection_module_css_default.reportLine,
|
|
473
|
+
children: [
|
|
474
|
+
t("reportWeak"),
|
|
475
|
+
": ",
|
|
476
|
+
report.weak.length
|
|
477
|
+
]
|
|
478
|
+
}),
|
|
479
|
+
report.reused.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
480
|
+
className: VaultSection_module_css_default.reportLine,
|
|
481
|
+
children: [
|
|
482
|
+
t("reportReused"),
|
|
483
|
+
": ",
|
|
484
|
+
report.reused.length
|
|
485
|
+
]
|
|
486
|
+
})
|
|
487
|
+
]
|
|
488
|
+
}),
|
|
489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
490
|
+
type: "button",
|
|
491
|
+
className: VaultSection_module_css_default.trashButton,
|
|
492
|
+
onClick: () => {
|
|
493
|
+
if (!showTrash) trash().then(setTrashEntries);
|
|
494
|
+
setShowTrash(!showTrash);
|
|
495
|
+
},
|
|
496
|
+
children: [showTrash ? t("hideTrash") : t("showTrash"), trashEntries.length > 0 ? ` (${trashEntries.length})` : ""]
|
|
497
|
+
}),
|
|
498
|
+
showTrash && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
499
|
+
className: VaultSection_module_css_default.list,
|
|
500
|
+
children: [trashEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
501
|
+
className: VaultSection_module_css_default.row,
|
|
502
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
503
|
+
className: VaultSection_module_css_default.rowMain,
|
|
504
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
505
|
+
className: VaultSection_module_css_default.title,
|
|
506
|
+
children: entry.title
|
|
507
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
508
|
+
className: VaultSection_module_css_default.identity,
|
|
509
|
+
children: t("trashed")
|
|
510
|
+
})]
|
|
511
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
512
|
+
className: VaultSection_module_css_default.rowActions,
|
|
513
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
514
|
+
type: "button",
|
|
515
|
+
onClick: () => {
|
|
516
|
+
restore(entry.id).then(() => {
|
|
517
|
+
trash().then(setTrashEntries);
|
|
518
|
+
refresh();
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
disabled: busy || readonly,
|
|
522
|
+
children: t("restore")
|
|
523
|
+
})
|
|
524
|
+
})]
|
|
525
|
+
}, entry.id)), trashEntries.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
526
|
+
className: VaultSection_module_css_default.empty,
|
|
527
|
+
children: t("trashEmpty")
|
|
528
|
+
})]
|
|
529
|
+
}),
|
|
427
530
|
state.status === "ready" && state.entries.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
428
531
|
className: VaultSection_module_css_default.list,
|
|
429
|
-
children: state.entries.map((entry) => {
|
|
532
|
+
children: state.entries.filter((entry) => kindFilter === "" || entry.kind === kindFilter).map((entry) => {
|
|
430
533
|
const code = codeMap[entry.id];
|
|
431
534
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
432
535
|
className: VaultSection_module_css_default.row,
|
|
433
536
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
434
537
|
className: VaultSection_module_css_default.rowMain,
|
|
435
538
|
children: [
|
|
436
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
437
540
|
className: VaultSection_module_css_default.title,
|
|
438
|
-
children: entry.title
|
|
541
|
+
children: [entry.title, entry.sensitivity === "high" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
542
|
+
className: VaultSection_module_css_default.highBadge,
|
|
543
|
+
children: t("highSensitivity")
|
|
544
|
+
})]
|
|
439
545
|
}),
|
|
440
546
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
441
547
|
className: VaultSection_module_css_default.identity,
|
|
@@ -507,8 +613,26 @@ window.__ModuleLoader__.load({
|
|
|
507
613
|
...previous,
|
|
508
614
|
title: event.target.value
|
|
509
615
|
}))
|
|
616
|
+
}) : field.key === "password" || field.key === "otpSecret" || field.key === "apiKey" || field.key === "secret" || field.key === "accessToken" || field.key === "refreshToken" || field.key === "privateKey" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
617
|
+
className: VaultSection_module_css_default.secretField,
|
|
618
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
619
|
+
type: revealed[field.key] ? "text" : "password",
|
|
620
|
+
value: form[field.key] ?? "",
|
|
621
|
+
onChange: (event) => setForm((previous) => ({
|
|
622
|
+
...previous,
|
|
623
|
+
[field.key]: event.target.value
|
|
624
|
+
}))
|
|
625
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
626
|
+
type: "button",
|
|
627
|
+
className: VaultSection_module_css_default.revealButton,
|
|
628
|
+
onClick: () => setRevealed((previous) => ({
|
|
629
|
+
...previous,
|
|
630
|
+
[field.key]: !previous[field.key]
|
|
631
|
+
})),
|
|
632
|
+
children: revealed[field.key] ? t("hide") : t("show")
|
|
633
|
+
})]
|
|
510
634
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
511
|
-
type:
|
|
635
|
+
type: "text",
|
|
512
636
|
value: form[field.key] ?? "",
|
|
513
637
|
onChange: (event) => setForm((previous) => ({
|
|
514
638
|
...previous,
|
|
@@ -564,6 +688,19 @@ window.__ModuleLoader__.load({
|
|
|
564
688
|
modeAutoHint: "Automatic read-write — no per-call prompt",
|
|
565
689
|
autoCaptureOn: "auto-capture on",
|
|
566
690
|
autoCaptureOff: "auto-capture off",
|
|
691
|
+
reportTitle: "Health & rotation",
|
|
692
|
+
reportRotation: "expired / due for rotation",
|
|
693
|
+
reportWeak: "weak passwords",
|
|
694
|
+
reportReused: "credentials reused across entries",
|
|
695
|
+
showTrash: "Trash",
|
|
696
|
+
hideTrash: "Hide trash",
|
|
697
|
+
trashed: "in trash",
|
|
698
|
+
trashEmpty: "Trash is empty",
|
|
699
|
+
restore: "Restore",
|
|
700
|
+
show: "Show",
|
|
701
|
+
hide: "Hide",
|
|
702
|
+
allKinds: "All kinds",
|
|
703
|
+
highSensitivity: "HIGH",
|
|
567
704
|
add: "Add credential",
|
|
568
705
|
cancel: "Cancel",
|
|
569
706
|
save: "Save",
|
|
@@ -616,6 +753,19 @@ window.__ModuleLoader__.load({
|
|
|
616
753
|
modeAutoHint: "自动读写 —— 无需逐次确认",
|
|
617
754
|
autoCaptureOn: "自动捕获已开启",
|
|
618
755
|
autoCaptureOff: "自动捕获已关闭",
|
|
756
|
+
reportTitle: "健康与轮换",
|
|
757
|
+
reportRotation: "已过期 / 待轮换",
|
|
758
|
+
reportWeak: "弱密码",
|
|
759
|
+
reportReused: "跨条目复用的凭据",
|
|
760
|
+
showTrash: "回收站",
|
|
761
|
+
hideTrash: "隐藏回收站",
|
|
762
|
+
trashed: "已删除",
|
|
763
|
+
trashEmpty: "回收站为空",
|
|
764
|
+
restore: "恢复",
|
|
765
|
+
show: "显示",
|
|
766
|
+
hide: "隐藏",
|
|
767
|
+
allKinds: "全部类型",
|
|
768
|
+
highSensitivity: "高敏",
|
|
619
769
|
add: "新增凭据",
|
|
620
770
|
cancel: "取消",
|
|
621
771
|
save: "保存",
|
|
@@ -693,6 +843,10 @@ window.__ModuleLoader__.load({
|
|
|
693
843
|
patch
|
|
694
844
|
}),
|
|
695
845
|
remove: (id) => invoke("delete", { id }),
|
|
846
|
+
trash: () => invoke("trash").then((r) => r.entries),
|
|
847
|
+
rotation: () => invoke("rotation").then((r) => r.entries),
|
|
848
|
+
health: () => invoke("health"),
|
|
849
|
+
restore: (id) => invoke("restore", { id }),
|
|
696
850
|
totp: (id) => invoke("totp", { id })
|
|
697
851
|
});
|
|
698
852
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|