diff --git a/AGENTS.md b/AGENTS.md index 55b4667..54fe57c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -165,6 +165,10 @@ oauth_accounts ( | `secrets.type` | 密钥类型(调用方提供,默认 `text`) | `text`, `password`, `key` | | `secrets.encrypted` | 密文 | AES-GCM | +### Web 条目页表格列(`/entries`) + +列表仅展示非敏感字段;**名称**与**操作**列为固定列(不可在「显示列」中关闭)。**文件夹**(对应 `entries.folder`)、类型、备注、标签、关联、密文等为**可选列**,由用户在「显示列」面板中勾选;可见性保存在浏览器 `localStorage`,键为 **`entries_col_vis`**。新增列会并入默认:若用户曾保存过旧版配置,缺失的列键会按当前默认补齐。**文件夹**列默认**显示**,便于在「全部」等跨 folder 视图下区分条目所属隔离空间。 + ### 共享密钥(N:N 关联) 多个 entry 可共享同一 secret 字段,通过 `entry_secrets` 中间表关联。 diff --git a/Cargo.lock b/Cargo.lock index e86277a..a98fa31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2065,7 +2065,7 @@ dependencies = [ [[package]] name = "secrets-mcp" -version = "0.5.18" +version = "0.5.19" dependencies = [ "anyhow", "askama", diff --git a/README.md b/README.md index f362765..df339bb 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ SECRETS_DATABASE_SSL_ROOT_CERT=/etc/secrets/pg-ca.crt SECRETS_ENV=production ``` -- **Web**:`BASE_URL`(登录、Dashboard、设置密码短语、创建 API Key)。 +- **Web**:`BASE_URL`(登录、Dashboard、设置密码短语、创建 API Key)。**条目**页 `/entries` 支持 folder 标签与条件筛选;表格列可在「显示列」中开关(名称与操作固定),**文件夹**列为可选列且默认显示。列可见性持久化见 [AGENTS.md](AGENTS.md)「Web 条目页表格列」。 - **MCP**:Streamable HTTP 基址 `{BASE_URL}/mcp`,需 `Authorization: Bearer ` + `X-Encryption-Key: ` 请求头(读密文工具须带密钥)。 ## PostgreSQL TLS 加固 diff --git a/crates/secrets-mcp/Cargo.toml b/crates/secrets-mcp/Cargo.toml index fd62cb7..a247dc7 100644 --- a/crates/secrets-mcp/Cargo.toml +++ b/crates/secrets-mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "secrets-mcp" -version = "0.5.18" +version = "0.5.19" edition.workspace = true [[bin]] diff --git a/crates/secrets-mcp/templates/entries.html b/crates/secrets-mcp/templates/entries.html index aaa47a7..17d698c 100644 --- a/crates/secrets-mcp/templates/entries.html +++ b/crates/secrets-mcp/templates/entries.html @@ -145,12 +145,13 @@ } table { width: 100%; - min-width: 1100px; + min-width: 1240px; border-collapse: separate; border-spacing: 0; table-layout: fixed; } col[data-col="name"] { width: 220px; } + col[data-col="folder"] { width: 140px; } col[data-col="type"] { width: 120px; } col[data-col="notes"] { width: 320px; } col[data-col="tags"] { width: 220px; } @@ -172,8 +173,8 @@ } td { font-size: 13px; line-height: 1.45; color: #c9d1d9; } tbody tr:nth-child(2n) td { background: rgba(255, 255, 255, 0.01); } - tbody tr:nth-child(2n) td.col-name { background: #0f1620; } .mono { font-family: 'JetBrains Mono', monospace; } + .col-folder { text-align: center; vertical-align: middle; } .col-type { text-align: center; vertical-align: middle; } .col-secrets { vertical-align: middle; } .col-secrets .secret-list { max-height: 120px; overflow: auto; } @@ -557,7 +558,7 @@
- 清空 + 重置
@@ -572,6 +573,7 @@ + @@ -582,6 +584,7 @@ + @@ -594,6 +597,7 @@ {% for entry in entries %} + @@ -714,9 +718,10 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option filterTypeLabel: '类型', filterTypeAll: '全部', filterSubmit: '筛选', - filterClear: '清空', + filterClear: '重置', emptyEntries: '暂无条目。', colName: '名称', + colFolder: '文件夹', colType: '类型', colNotes: '备注', colTags: '标签', @@ -740,6 +745,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option modalCancel: '取消', modalSave: '保存', mobileLabelName: '名称', + mobileLabelFolder: '文件夹', mobileLabelType: '类型', mobileLabelNotes: '备注', mobileLabelTags: '标签', @@ -798,9 +804,10 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option filterTypeLabel: '類型', filterTypeAll: '全部', filterSubmit: '篩選', - filterClear: '清除', + filterClear: '重置', emptyEntries: '暫無條目。', colName: '名稱', + colFolder: '資料夾', colType: '類型', colNotes: '備註', colTags: '標籤', @@ -824,6 +831,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option modalCancel: '取消', modalSave: '儲存', mobileLabelName: '名稱', + mobileLabelFolder: '資料夾', mobileLabelType: '類型', mobileLabelNotes: '備註', mobileLabelTags: '標籤', @@ -882,9 +890,10 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option filterTypeLabel: 'Type', filterTypeAll: 'All', filterSubmit: 'Filter', - filterClear: 'Clear', + filterClear: 'Reset', emptyEntries: 'No entries.', colName: 'Name', + colFolder: 'Folder', colType: 'Type', colNotes: 'Notes', colTags: 'Tags', @@ -908,6 +917,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option modalCancel: 'Cancel', modalSave: 'Save', mobileLabelName: 'Name', + mobileLabelFolder: 'Folder', mobileLabelType: 'Type', mobileLabelNotes: 'Notes', mobileLabelTags: 'Tags', @@ -962,6 +972,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option document.querySelectorAll('tr[data-entry-id]').forEach(function (tr) { var map = { '.col-name': 'mobileLabelName', + '.col-folder': 'mobileLabelFolder', '.col-type': 'mobileLabelType', '.col-notes': 'mobileLabelNotes', '.col-tags': 'mobileLabelTags', @@ -977,9 +988,9 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option rebuildColPanel(); }; - var COL_ORDER = ['name', 'type', 'notes', 'tags', 'relations', 'secrets', 'actions']; + var COL_ORDER = ['name', 'folder', 'type', 'notes', 'tags', 'relations', 'secrets', 'actions']; var COL_ALWAYS_ON = { name: true, actions: true }; - var COL_DEFAULTS = { name: true, type: true, notes: false, tags: true, relations: true, secrets: false, actions: true }; + var COL_DEFAULTS = { name: true, folder: true, type: true, notes: false, tags: true, relations: true, secrets: false, actions: true }; var COL_STORAGE_KEY = 'entries_col_vis'; var colPanel = document.getElementById('col-panel'); var colToggleBtn = document.getElementById('col-toggle-btn'); @@ -987,7 +998,16 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option function getColVis() { try { var saved = localStorage.getItem(COL_STORAGE_KEY); - if (saved) { var parsed = JSON.parse(saved); if (parsed && typeof parsed === 'object') return parsed; } + if (saved) { + var parsed = JSON.parse(saved); + if (parsed && typeof parsed === 'object') { + var merged = {}; + COL_ORDER.forEach(function (col) { + merged[col] = parsed[col] !== undefined ? parsed[col] : COL_DEFAULTS[col]; + }); + return merged; + } + } } catch (e) {} var defaults = {}; COL_ORDER.forEach(function (col) { defaults[col] = COL_DEFAULTS[col]; });
名称文件夹 类型 备注 标签
{{ entry.name }}{{ entry.folder }} {{ entry.entry_type }} {% if !entry.notes.is_empty() %}
{{ entry.notes }}
{% endif %}
{{ entry.tags }}