feat: entry update links existing secrets (link_secret_names)
All checks were successful
Secrets MCP — Build & Release / 检查 / 构建 / 发版 (push) Successful in 4m19s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 6s

- secrets-core: update flow validates and applies secret links
- secrets-mcp: MCP tool params and UI for managing links on edit
- Align errors and templates; minor crypto/.gitignore tweaks

Made-with: Cursor
This commit is contained in:
voson
2026-04-04 20:30:32 +08:00
parent 4a1654c820
commit 0ffb81e57f
10 changed files with 321 additions and 19 deletions

View File

@@ -123,7 +123,7 @@
background: var(--bg);
}
table {
width: max-content;
width: 100%;
min-width: 960px;
border-collapse: separate;
border-spacing: 0;
@@ -142,12 +142,12 @@
td { font-size: 13px; line-height: 1.45; }
tbody tr:nth-child(2n) td { background: rgba(255, 255, 255, 0.01); }
.mono { font-family: 'JetBrains Mono', monospace; }
.col-type { min-width: 108px; }
.col-type { min-width: 108px; width: 1%; }
.col-name { min-width: 180px; max-width: 260px; }
.col-tags { min-width: 160px; max-width: 220px; }
.col-secrets { min-width: 220px; max-width: 420px; vertical-align: top; }
.col-secrets .secret-list { max-height: 120px; overflow: auto; }
.col-actions { min-width: 132px; }
.col-actions { min-width: 132px; width: 1%; }
.cell-name, .cell-tags-val {
overflow-wrap: anywhere;
word-break: break-word;
@@ -961,6 +961,114 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
showDeleteErr('');
}
function refreshListAfterSave(entryId, body, secretRows) {
var tr = document.querySelector('tr[data-entry-id="' + entryId + '"]');
if (!tr) { window.location.reload(); return; }
var nameCell = tr.querySelector('.cell-name');
if (nameCell) nameCell.textContent = body.name;
var typeCell = tr.querySelector('.cell-type');
if (typeCell) typeCell.textContent = body.type;
var notesCell = tr.querySelector('.cell-notes-val');
if (notesCell) {
if (body.notes) { notesCell.textContent = body.notes; }
else { var notesWrap = tr.querySelector('.cell-notes'); if (notesWrap) notesWrap.innerHTML = ''; }
}
var tagsCell = tr.querySelector('.cell-tags-val');
if (tagsCell) tagsCell.textContent = body.tags.join(', ');
var secretsList = tr.querySelector('.secret-list');
if (secretsList) {
secretsList.innerHTML = '';
secretRows.forEach(function (info) {
var chip = document.createElement('span');
chip.className = 'secret-chip';
var nameSpan = document.createElement('span');
nameSpan.className = 'secret-name';
nameSpan.textContent = info.newName;
nameSpan.title = info.newName;
var typeSpan = document.createElement('span');
typeSpan.className = 'secret-type';
typeSpan.textContent = info.newType || 'text';
var unlinkBtn = document.createElement('button');
unlinkBtn.type = 'button';
unlinkBtn.className = 'btn-unlink-secret';
unlinkBtn.setAttribute('data-secret-id', info.secretId);
unlinkBtn.setAttribute('data-secret-name', info.newName);
unlinkBtn.title = t('unlinkTitle');
unlinkBtn.textContent = '\u00d7';
chip.appendChild(nameSpan);
chip.appendChild(typeSpan);
chip.appendChild(unlinkBtn);
secretsList.appendChild(chip);
});
}
tr.setAttribute('data-entry-folder', body.folder);
tr.setAttribute('data-entry-metadata', JSON.stringify(body.metadata));
var updatedSecrets = secretRows.map(function (info) {
return { id: info.secretId, name: info.newName, secret_type: info.newType || 'text' };
});
tr.setAttribute('data-entry-secrets', JSON.stringify(updatedSecrets));
}
function refreshListAfterDelete(entryId) {
var tr = document.querySelector('tr[data-entry-id="' + entryId + '"]');
var folder = tr ? tr.getAttribute('data-entry-folder') : null;
if (tr) tr.remove();
var tbody = document.querySelector('table tbody');
if (tbody && !tbody.querySelector('tr[data-entry-id]')) {
var card = document.querySelector('.card');
if (card) {
var tableWrap = card.querySelector('.table-wrap');
if (tableWrap) tableWrap.remove();
var existingEmpty = card.querySelector('.empty');
if (!existingEmpty) {
var emptyDiv = document.createElement('div');
emptyDiv.className = 'empty';
emptyDiv.setAttribute('data-i18n', 'emptyEntries');
emptyDiv.textContent = t('emptyEntries');
var filterBar = card.querySelector('.filter-bar');
if (filterBar) { card.insertBefore(emptyDiv, filterBar.nextSibling); }
else { card.appendChild(emptyDiv); }
}
}
}
var allTab = document.querySelector('.folder-tab[data-all-tab="1"]');
if (allTab) {
var count = parseInt(allTab.getAttribute('data-count') || '0', 10);
if (count > 0) {
count -= 1;
allTab.setAttribute('data-count', String(count));
allTab.textContent = t('allTab') + ' (' + count + ')';
}
}
if (folder) {
document.querySelectorAll('.folder-tab:not([data-all-tab])').forEach(function (tab) {
if (tab.textContent.trim().indexOf(folder) === 0) {
var m = tab.textContent.match(/\((\d+)\)/);
if (m) {
var c = parseInt(m[1], 10);
if (c > 0) {
c -= 1;
tab.textContent = folder + ' (' + c + ')';
}
}
}
});
}
}
function refreshListAfterUnlink(entryId, secretId) {
var tr = document.querySelector('tr[data-entry-id="' + entryId + '"]');
if (!tr) return;
var chip = tr.querySelector('.btn-unlink-secret[data-secret-id="' + secretId + '"]');
if (chip && chip.parentElement) chip.parentElement.remove();
var secrets = tr.getAttribute('data-entry-secrets');
try {
var arr = JSON.parse(secrets);
arr = arr.filter(function (s) { return s.id !== secretId; });
tr.setAttribute('data-entry-secrets', JSON.stringify(arr));
} catch (e) {}
}
document.getElementById('delete-cancel').addEventListener('click', closeDelete);
deleteOverlay.addEventListener('click', function (e) {
if (e.target === deleteOverlay) closeDelete();
@@ -975,8 +1083,9 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
});
})
.then(function () {
var deletedId = pendingDeleteId;
closeDelete();
window.location.reload();
refreshListAfterDelete(deletedId);
})
.catch(function (e) { showDeleteErr(e.message || String(e)); });
});
@@ -1086,7 +1195,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
}));
}).then(function () {
closeEdit();
window.location.reload();
refreshListAfterSave(currentEntryId, body, secretRows);
}).catch(function (e) {
showEditErr(e.message || String(e));
});
@@ -1102,7 +1211,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
var secretId = btn.getAttribute('data-secret-id');
var secretName = btn.getAttribute('data-secret-name') || '';
if (!entryId || !secretId) return;
if (!confirm(tf('confirmUnlinkSecret', { name: secretName }))) return;
fetch('/api/entries/' + encodeURIComponent(entryId) + '/secrets/' + encodeURIComponent(secretId), {
method: 'DELETE',
credentials: 'same-origin'
@@ -1112,7 +1220,7 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
return data;
});
}).then(function () {
window.location.reload();
refreshListAfterUnlink(entryId, secretId);
}).catch(function (err) {
alert(err.message || String(err));
});
@@ -1126,7 +1234,6 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
var secretId = btn.getAttribute('data-secret-id');
var secretName = btn.getAttribute('data-secret-name') || '';
if (!entryId || !secretId) return;
if (!confirm(tf('confirmUnlinkSecret', { name: secretName }))) return;
fetch('/api/entries/' + encodeURIComponent(entryId) + '/secrets/' + encodeURIComponent(secretId), {
method: 'DELETE',
credentials: 'same-origin'
@@ -1136,7 +1243,12 @@ var SECRET_TYPE_OPTIONS = JSON.parse(document.getElementById('secret-type-option
return data;
});
}).then(function () {
window.location.reload();
btn.closest('.secret-edit-row').remove();
var tableRow = document.querySelector('tr[data-entry-id="' + entryId + '"]');
if (tableRow) {
var chip = tableRow.querySelector('.btn-unlink-secret[data-secret-id="' + secretId + '"]');
if (chip && chip.parentElement) chip.parentElement.remove();
}
}).catch(function (err) {
alert(err.message || String(err));
});