diff --git a/Cargo.lock b/Cargo.lock index 59c6240..fb36b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2065,7 +2065,7 @@ dependencies = [ [[package]] name = "secrets-mcp" -version = "0.5.14" +version = "0.5.15" dependencies = [ "anyhow", "askama", diff --git a/crates/secrets-mcp/Cargo.toml b/crates/secrets-mcp/Cargo.toml index bde14d7..d00bd66 100644 --- a/crates/secrets-mcp/Cargo.toml +++ b/crates/secrets-mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "secrets-mcp" -version = "0.5.14" +version = "0.5.15" edition.workspace = true [[bin]] diff --git a/crates/secrets-mcp/templates/entries.html b/crates/secrets-mcp/templates/entries.html index 9ddd06c..ea066f5 100644 --- a/crates/secrets-mcp/templates/entries.html +++ b/crates/secrets-mcp/templates/entries.html @@ -73,8 +73,7 @@ border-color: rgba(56,139,253,0.3); color: #fff; } - .filter-bar { - display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 16px; + .filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 16px; margin-bottom: 18px; padding: 16px; background: #0d1117; border: 1px solid rgba(240,246,252,0.08); border-radius: 12px; } @@ -114,6 +113,29 @@ color: #8b949e; font-size: 13px; text-decoration: none; cursor: pointer; } .btn-clear:hover { border-color: rgba(56,139,253,0.45); color: #fff; } + .btn-col-toggle { + padding: 8px 12px; border-radius: 10px; border: 1px solid rgba(240,246,252,0.12); + background: transparent; color: #8b949e; font-size: 16px; cursor: pointer; + } + .btn-col-toggle:hover { border-color: rgba(56,139,253,0.45); color: #fff; } + .col-menu { position: relative; } + .col-panel { + display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: 20; + background: #161b22; border: 1px solid rgba(240,246,252,0.12); border-radius: 10px; + padding: 10px 14px; min-width: 180px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); + } + .col-panel.open { display: block; } + .col-panel-group { font-size: 11px; color: #6e7681; text-transform: uppercase; letter-spacing: 0.5px; margin: 8px 0 4px; } + .col-panel-group:first-child { margin-top: 0; } + .col-panel-item { + display: flex; align-items: center; gap: 8px; padding: 4px 0; + font-size: 13px; color: #c9d1d9; cursor: pointer; user-select: none; + } + .col-panel-item input[type="checkbox"] { + accent-color: var(--accent); width: 15px; height: 15px; cursor: pointer; + } + .col-panel-item.disabled { color: #6e7681; cursor: default; } + .col-panel-item.disabled input[type="checkbox"] { cursor: default; } .empty { color: #8b949e; font-size: 14px; padding: 20px 0; } .table-wrap { overflow: auto; @@ -123,10 +145,18 @@ } table { width: 100%; - min-width: 960px; + min-width: 1100px; border-collapse: separate; border-spacing: 0; + table-layout: fixed; } + col[data-col="name"] { width: 220px; } + col[data-col="type"] { width: 120px; } + col[data-col="notes"] { width: 320px; } + col[data-col="tags"] { width: 220px; } + col[data-col="relations"] { width: 220px; } + col[data-col="secrets"] { width: 320px; } + col[data-col="actions"] { width: 132px; } th, td { text-align: left; vertical-align: middle; padding: 14px 12px; border-top: 1px solid rgba(240,246,252,0.08); } th { color: #8b949e; @@ -142,13 +172,21 @@ } 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-type { min-width: 108px; width: 1%; text-align: center; vertical-align: middle; } - .col-name { min-width: 180px; max-width: 260px; text-align: center; vertical-align: middle; } - .col-tags { min-width: 160px; max-width: 220px; } - .col-secrets { min-width: 220px; max-width: 420px; 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; } - .col-actions { min-width: 132px; width: 1%; text-align: center; vertical-align: middle; } + .col-actions { text-align: right; vertical-align: middle; } + .col-name { position: sticky; left: 0; z-index: 1; background: #0d1117; overflow: hidden; } + th.col-name { z-index: 3; background: #111827; } + .col-name::after { + content: ''; position: absolute; top: 0; right: -8px; bottom: 0; width: 8px; + background: linear-gradient(to right, rgba(0,0,0,0.15), transparent); + pointer-events: none; + } + th[data-col="actions"], td[data-col="actions"] { text-align: right; } + [data-col].col-hidden { display: none !important; } .cell-name, .cell-tags-val { overflow-wrap: anywhere; word-break: break-word; @@ -199,6 +237,17 @@ border-left: 1px solid rgba(240,246,252,0.08); padding-left: 6px; } + a.secret-chip { + color: var(--accent); + text-decoration: none; + cursor: pointer; + transition: color 0.15s, border-color 0.15s, background 0.15s; + } + a.secret-chip:hover { + color: var(--accent-hover); + border-color: var(--accent); + background: rgba(88,166,255,0.12); + } .btn-unlink-secret { border: none; background: transparent; @@ -357,6 +406,9 @@ content: attr(data-label); } .col-name, .col-type, .col-actions { text-align: left; } + .col-name { position: static; } + .col-name::after { display: none; } + .col-panel { position: fixed; left: 12px; right: 12px; width: auto; } th, td { vertical-align: top; } .row-actions { justify-content: flex-start; } .detail, .notes-scroll, .secret-list { max-width: none; } @@ -404,7 +456,7 @@ padding: 7px 10px; word-break: break-all; white-space: pre-wrap; max-height: 140px; overflow: auto; color: #c9d1d9; line-height: 1.5; } - .view-secret-value.masked { letter-spacing: 2px; user-select: none; filter: blur(4px); } + .btn-icon { padding: 6px 10px; border-radius: 8px; font-size: 12px; cursor: pointer; border: 1px solid rgba(240,246,252,0.12); background: #161b22; color: #8b949e; @@ -497,6 +549,10 @@
| 名称 | -类型 | -备注 | -标签 | -关联 | -密文 | -操作 | +名称 | +类型 | +备注 | +标签 | +关联 | +密文 | +操作 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ entry.name }} | -{{ entry.entry_type }} | -{% if !entry.notes.is_empty() %} {{ entry.notes }} {% endif %} |
- {{ entry.tags }} | -+ | {{ entry.name }} | +{{ entry.entry_type }} | +{% if !entry.notes.is_empty() %} {{ entry.notes }} {% endif %} |
+ {{ entry.tags }} | +
{% for parent in entry.parents %}
@@ -539,7 +604,7 @@
{% endfor %}
|
- + |
{% for s in entry.secrets %}
@@ -549,7 +614,7 @@
{% endfor %}
|
- + |
@@ -627,6 +692,9 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
I18N_PAGE = {
'zh-CN': {
pageTitle: 'Secrets — 条目',
+ columnSettings: '显示列',
+ fixedColumns: '固定列',
+ optionalColumns: '可选列',
navTrash: '回收站',
entriesTitle: '我的条目',
allTab: '全部',
@@ -694,8 +762,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
viewDecryptError: '解密失败,请确认密码短语与加密时一致。',
viewCopy: '复制',
viewCopied: '已复制',
- viewShow: '显示',
- viewHide: '隐藏',
viewLoading: '解密中…',
viewSaveChanges: '保存更改',
viewChangesSaved: '已保存',
@@ -706,6 +772,9 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
},
'zh-TW': {
pageTitle: 'Secrets — 條目',
+ columnSettings: '顯示列',
+ fixedColumns: '固定列',
+ optionalColumns: '可選列',
navTrash: '回收站',
entriesTitle: '我的條目',
allTab: '全部',
@@ -773,8 +842,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
viewDecryptError: '解密失敗,請確認密碼短語與加密時一致。',
viewCopy: '複製',
viewCopied: '已複製',
- viewShow: '顯示',
- viewHide: '隱藏',
viewLoading: '解密中…',
viewSaveChanges: '儲存變更',
viewChangesSaved: '已儲存',
@@ -785,6 +852,9 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
},
en: {
pageTitle: 'Secrets — Entries',
+ columnSettings: 'Columns',
+ fixedColumns: 'Fixed',
+ optionalColumns: 'Optional',
navTrash: 'Trash',
entriesTitle: 'My entries',
allTab: 'All',
@@ -852,8 +922,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
viewDecryptError: 'Decryption failed. Please verify your passphrase matches the one used when encrypting.',
viewCopy: 'Copy',
viewCopied: 'Copied',
- viewShow: 'Show',
- viewHide: 'Hide',
viewLoading: 'Decrypting…',
viewSaveChanges: 'Save changes',
viewChangesSaved: 'Saved',
@@ -885,8 +953,96 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
if (td) td.setAttribute('data-label', t(map[sel]));
});
});
+ rebuildColPanel();
};
+ var COL_ORDER = ['name', '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_STORAGE_KEY = 'entries_col_vis';
+ var colPanel = document.getElementById('col-panel');
+ var colToggleBtn = document.getElementById('col-toggle-btn');
+
+ function getColVis() {
+ try {
+ var saved = localStorage.getItem(COL_STORAGE_KEY);
+ if (saved) { var parsed = JSON.parse(saved); if (parsed && typeof parsed === 'object') return parsed; }
+ } catch (e) {}
+ var defaults = {};
+ COL_ORDER.forEach(function (col) { defaults[col] = COL_DEFAULTS[col]; });
+ return defaults;
+ }
+
+ function saveColVis(vis) {
+ try { localStorage.setItem(COL_STORAGE_KEY, JSON.stringify(vis)); } catch (e) {}
+ }
+
+ function applyColVis(vis) {
+ COL_ORDER.forEach(function (col) {
+ var visible = vis[col] !== false;
+ document.querySelectorAll('[data-col="' + col + '"]').forEach(function (el) {
+ if (visible) {
+ el.classList.remove('col-hidden');
+ } else {
+ el.classList.add('col-hidden');
+ }
+ });
+ });
+ }
+
+ function rebuildColPanel() {
+ var vis = getColVis();
+ colPanel.innerHTML = '';
+ var fixedCols = ['name', 'actions'];
+ var optionalCols = COL_ORDER.filter(function (c) { return fixedCols.indexOf(c) === -1; });
+
+ function renderGroup(cols, groupKey) {
+ var groupLabel = document.createElement('div');
+ groupLabel.className = 'col-panel-group';
+ groupLabel.textContent = t(groupKey);
+ colPanel.appendChild(groupLabel);
+ cols.forEach(function (col) {
+ var item = document.createElement('label');
+ item.className = 'col-panel-item';
+ var cb = document.createElement('input');
+ cb.type = 'checkbox';
+ var i18nKey = 'col' + col.charAt(0).toUpperCase() + col.slice(1);
+ cb.checked = vis[col] !== false;
+ if (COL_ALWAYS_ON[col]) {
+ cb.disabled = true;
+ item.classList.add('disabled');
+ }
+ cb.addEventListener('change', function () {
+ vis[col] = cb.checked;
+ saveColVis(vis);
+ applyColVis(vis);
+ });
+ var span = document.createElement('span');
+ span.textContent = t(i18nKey) || col;
+ item.appendChild(cb);
+ item.appendChild(span);
+ colPanel.appendChild(item);
+ });
+ }
+
+ renderGroup(fixedCols, 'fixedColumns');
+ renderGroup(optionalCols, 'optionalColumns');
+ }
+
+ var colMenu = document.querySelector('.col-menu');
+ colToggleBtn.addEventListener('click', function (e) {
+ e.stopPropagation();
+ colPanel.classList.toggle('open');
+ });
+ document.addEventListener('click', function (e) {
+ if (!colMenu.contains(e.target)) {
+ colPanel.classList.remove('open');
+ }
+ });
+
+ applyColVis(getColVis());
+ rebuildColPanel();
+
var editOverlay = document.getElementById('edit-overlay');
var editError = document.getElementById('edit-error');
var editFolder = document.getElementById('edit-folder');
@@ -1023,9 +1179,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
var raw = secrets[name];
var valueStr = (raw === null || raw === undefined) ? '' :
(typeof raw === 'object') ? JSON.stringify(raw, null, 2) : String(raw);
- var isPassword = (name === 'password' || name === 'passwd' || name === 'secret');
- var masked = isPassword;
-
var schema = schemaMap[name] || {};
var secretId = schema.id || '';
var secretType = schema.secret_type || 'text';
@@ -1104,19 +1257,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
cancelBtn.textContent = t('modalCancel');
cancelBtn.hidden = true;
- if (isPassword) {
- var toggleBtn = document.createElement('button');
- toggleBtn.type = 'button';
- toggleBtn.className = 'btn-icon btn-toggle-mask';
- toggleBtn.textContent = t('viewShow');
- toggleBtn.addEventListener('click', function () {
- masked = !masked;
- valueEl.classList.toggle('masked', masked);
- toggleBtn.textContent = masked ? t('viewShow') : t('viewHide');
- });
- actions.appendChild(toggleBtn);
- }
-
var copyBtn = document.createElement('button');
copyBtn.type = 'button';
copyBtn.className = 'btn-icon';
@@ -1145,7 +1285,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
var valueWrap = document.createElement('div');
valueWrap.className = 'view-secret-value-wrap';
var valueEl = document.createElement('div');
- valueEl.className = 'view-secret-value' + (masked ? ' masked' : '');
+ valueEl.className = 'view-secret-value';
valueEl.textContent = valueStr;
valueWrap.appendChild(valueEl);
row.appendChild(valueWrap);
diff --git a/crates/secrets-mcp/templates/i18n.js b/crates/secrets-mcp/templates/i18n.js
index 1cd726f..e1e461b 100644
--- a/crates/secrets-mcp/templates/i18n.js
+++ b/crates/secrets-mcp/templates/i18n.js
@@ -65,6 +65,10 @@ function applyLang() {
var key = el.getAttribute('data-i18n-ph');
el.placeholder = t(key);
});
+ document.querySelectorAll('[data-i18n-title]').forEach(function (el) {
+ var key = el.getAttribute('data-i18n-title');
+ el.title = t(key);
+ });
document.querySelectorAll('.lang-btn').forEach(function (btn) {
var map = { 'zh-CN': '简', 'zh-TW': '繁', en: 'EN' };
btn.classList.toggle('active', btn.textContent === map[currentLang]);
|