release(secrets-mcp): 0.5.15 — 列设置面板锚定优化,移除查看密文隐藏功能
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "secrets-mcp"
|
||||
version = "0.5.14"
|
||||
version = "0.5.15"
|
||||
edition.workspace = true
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -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 @@
|
||||
<div class="filter-actions">
|
||||
<button type="submit" class="btn-filter" data-i18n="filterSubmit">筛选</button>
|
||||
<a href="/entries" class="btn-clear" data-i18n="filterClear">清空</a>
|
||||
<div class="col-menu">
|
||||
<button type="button" class="btn-col-toggle" id="col-toggle-btn" data-i18n-title="columnSettings" title="显示列">▥</button>
|
||||
<div class="col-panel" id="col-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -505,25 +561,34 @@
|
||||
{% else %}
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col data-col="name">
|
||||
<col data-col="type">
|
||||
<col data-col="notes">
|
||||
<col data-col="tags">
|
||||
<col data-col="relations">
|
||||
<col data-col="secrets">
|
||||
<col data-col="actions">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-i18n="colName">名称</th>
|
||||
<th data-i18n="colType">类型</th>
|
||||
<th data-i18n="colNotes">备注</th>
|
||||
<th data-i18n="colTags">标签</th>
|
||||
<th data-i18n="colRelations">关联</th>
|
||||
<th data-i18n="colSecrets">密文</th>
|
||||
<th data-i18n="colActions">操作</th>
|
||||
<th data-col="name" data-i18n="colName">名称</th>
|
||||
<th data-col="type" data-i18n="colType">类型</th>
|
||||
<th data-col="notes" data-i18n="colNotes">备注</th>
|
||||
<th data-col="tags" data-i18n="colTags">标签</th>
|
||||
<th data-col="relations" data-i18n="colRelations">关联</th>
|
||||
<th data-col="secrets" data-i18n="colSecrets">密文</th>
|
||||
<th data-col="actions" data-i18n="colActions">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr data-entry-id="{{ entry.id }}" data-entry-folder="{{ entry.folder }}" data-entry-metadata="{{ entry.metadata_json }}" data-entry-secrets="{{ entry.secrets_json }}" data-entry-parents="{{ entry.parents_json }}" data-updated-at="{{ entry.updated_at_iso }}">
|
||||
<td class="col-name mono cell-name" data-label="名称">{{ entry.name }}</td>
|
||||
<td class="col-type mono cell-type" data-label="类型">{{ entry.entry_type }}</td>
|
||||
<td class="col-notes cell-notes" data-label="备注">{% if !entry.notes.is_empty() %}<div class="notes-scroll cell-notes-val">{{ entry.notes }}</div>{% endif %}</td>
|
||||
<td class="col-tags mono cell-tags-val" data-label="标签">{{ entry.tags }}</td>
|
||||
<td class="col-relations" data-label="关联">
|
||||
<td class="col-name mono cell-name" data-col="name" data-label="名称">{{ entry.name }}</td>
|
||||
<td class="col-type mono cell-type" data-col="type" data-label="类型">{{ entry.entry_type }}</td>
|
||||
<td class="col-notes cell-notes" data-col="notes" data-label="备注">{% if !entry.notes.is_empty() %}<div class="notes-scroll cell-notes-val">{{ entry.notes }}</div>{% endif %}</td>
|
||||
<td class="col-tags mono cell-tags-val" data-col="tags" data-label="标签">{{ entry.tags }}</td>
|
||||
<td class="col-relations" data-col="relations" data-label="关联">
|
||||
<div class="secret-list">
|
||||
{% for parent in entry.parents %}
|
||||
<a class="secret-chip" href="{{ parent.href }}" title="{{ parent.folder }} / {{ parent.name }}">
|
||||
@@ -539,7 +604,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="col-secrets" data-label="密文">
|
||||
<td class="col-secrets" data-col="secrets" data-label="密文">
|
||||
<div class="secret-list">
|
||||
{% for s in entry.secrets %}
|
||||
<span class="secret-chip">
|
||||
@@ -549,7 +614,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="col-actions" data-label="操作">
|
||||
<td class="col-actions" data-col="actions" data-label="操作">
|
||||
<div class="row-actions">
|
||||
<button type="button" class="btn-row btn-view-secrets" data-i18n="rowView">查看密文</button>
|
||||
<button type="button" class="btn-row btn-edit" data-i18n="rowEdit">编辑条目</button>
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user