ui: harden workspace and secrets rendering
This commit is contained in:
+120
-40
@@ -222,7 +222,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
total: state.secrets.length,
|
||||
});
|
||||
|
||||
tbody.innerHTML = '';
|
||||
window.CrankDom.clear(tbody);
|
||||
|
||||
if (state.loading && state.secrets.length === 0) {
|
||||
var loadingRow = document.createElement('tr');
|
||||
@@ -260,17 +260,38 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
rows.forEach(function (secret) {
|
||||
var tr = document.createElement('tr');
|
||||
var references = usage[secret.id] || [];
|
||||
tr.innerHTML = [
|
||||
'<td class="col-name">' + secret.name + '</td>',
|
||||
'<td>' + secretKindLabel(secret.kind) + '</td>',
|
||||
'<td class="col-mono">v' + secret.current_version + '</td>',
|
||||
'<td>' + (secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never')) + '</td>',
|
||||
'<td>' + window.tPlural('secrets.used_by_count', references.length, { count: references.length }) + '</td>',
|
||||
'<td><span class="badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active') + '">' + tKey('secrets.status.' + secret.status) + '</span></td>',
|
||||
'<td class="col-actions"></td>'
|
||||
].join('');
|
||||
|
||||
var actions = tr.querySelector('.col-actions');
|
||||
var nameCell = document.createElement('td');
|
||||
nameCell.className = 'col-name';
|
||||
nameCell.textContent = secret.name;
|
||||
tr.appendChild(nameCell);
|
||||
|
||||
var kindCell = document.createElement('td');
|
||||
kindCell.textContent = secretKindLabel(secret.kind);
|
||||
tr.appendChild(kindCell);
|
||||
|
||||
var versionCell = document.createElement('td');
|
||||
versionCell.className = 'col-mono';
|
||||
versionCell.textContent = 'v' + secret.current_version;
|
||||
tr.appendChild(versionCell);
|
||||
|
||||
var lastUsedCell = document.createElement('td');
|
||||
lastUsedCell.textContent = secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never');
|
||||
tr.appendChild(lastUsedCell);
|
||||
|
||||
var usedByCell = document.createElement('td');
|
||||
usedByCell.textContent = window.tPlural('secrets.used_by_count', references.length, { count: references.length });
|
||||
tr.appendChild(usedByCell);
|
||||
|
||||
var statusCell = document.createElement('td');
|
||||
var badge = document.createElement('span');
|
||||
badge.className = 'badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active');
|
||||
badge.textContent = tKey('secrets.status.' + secret.status);
|
||||
statusCell.appendChild(badge);
|
||||
tr.appendChild(statusCell);
|
||||
|
||||
var actions = document.createElement('td');
|
||||
actions.className = 'col-actions';
|
||||
var rotateButton = document.createElement('button');
|
||||
rotateButton.className = 'icon-btn';
|
||||
rotateButton.textContent = tKey('secrets.action.rotate');
|
||||
@@ -286,6 +307,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
await deleteSecret(secret);
|
||||
});
|
||||
actions.appendChild(deleteButton);
|
||||
tr.appendChild(actions);
|
||||
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
@@ -304,49 +326,107 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
{ count: state.profiles.length }
|
||||
);
|
||||
|
||||
window.CrankDom.clear(profilesList);
|
||||
|
||||
if (state.error) {
|
||||
profilesList.innerHTML = '<div class="empty-state"><div class="empty-state-title">' + tKey('secrets.profiles.error_title') + '</div><div class="empty-state-text">' + state.error + '</div></div>';
|
||||
profilesList.appendChild(window.CrankDom.createEmptyState(
|
||||
tKey('secrets.profiles.error_title'),
|
||||
state.error
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.loading && state.profiles.length === 0) {
|
||||
profilesList.innerHTML = '<div class="empty-state"><div class="empty-state-title">' + tKey('secrets.profiles.loading_title') + '</div><div class="empty-state-text">' + tKey('secrets.profiles.loading_body') + '</div></div>';
|
||||
profilesList.appendChild(window.CrankDom.createEmptyState(
|
||||
tKey('secrets.profiles.loading_title'),
|
||||
tKey('secrets.profiles.loading_body')
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.profiles.length) {
|
||||
profilesList.innerHTML = '<div class="empty-state"><div class="empty-state-title">' + tKey('secrets.profiles.empty_title') + '</div><div class="empty-state-text">' + tKey('secrets.profiles.empty_body') + '</div></div>';
|
||||
profilesList.appendChild(window.CrankDom.createEmptyState(
|
||||
tKey('secrets.profiles.empty_title'),
|
||||
tKey('secrets.profiles.empty_body')
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
profilesList.innerHTML = state.profiles.map(function (profile) {
|
||||
state.profiles.forEach(function (profile) {
|
||||
var secretIds = secretIdsForProfile(profile);
|
||||
var references = secretIds.map(function (secretId) {
|
||||
var card = document.createElement('div');
|
||||
card.className = 'resource-card';
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.className = 'resource-card-header';
|
||||
var headerBody = document.createElement('div');
|
||||
|
||||
var title = document.createElement('div');
|
||||
title.className = 'resource-card-title';
|
||||
title.textContent = profile.name;
|
||||
headerBody.appendChild(title);
|
||||
|
||||
var subtitle = document.createElement('div');
|
||||
subtitle.className = 'resource-card-subtitle';
|
||||
subtitle.textContent = profileSummary(profile, secretsById);
|
||||
headerBody.appendChild(subtitle);
|
||||
|
||||
var pillRow = document.createElement('div');
|
||||
pillRow.className = 'resource-pill-row';
|
||||
var statusPill = document.createElement('span');
|
||||
statusPill.className = 'resource-status-pill active';
|
||||
statusPill.textContent = authKindLabel(profile.kind);
|
||||
pillRow.appendChild(statusPill);
|
||||
headerBody.appendChild(pillRow);
|
||||
header.appendChild(headerBody);
|
||||
card.appendChild(header);
|
||||
|
||||
var metaGrid = document.createElement('div');
|
||||
metaGrid.className = 'resource-meta-grid';
|
||||
[
|
||||
[tKey('secrets.profiles.meta.created'), formatDate(profile.created_at)],
|
||||
[tKey('secrets.profiles.meta.updated'), formatDate(profile.updated_at)]
|
||||
].forEach(function(entry) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'resource-meta-item';
|
||||
var label = document.createElement('div');
|
||||
label.className = 'resource-meta-label';
|
||||
label.textContent = entry[0];
|
||||
var value = document.createElement('div');
|
||||
value.className = 'resource-meta-value';
|
||||
value.textContent = entry[1];
|
||||
item.appendChild(label);
|
||||
item.appendChild(value);
|
||||
metaGrid.appendChild(item);
|
||||
});
|
||||
card.appendChild(metaGrid);
|
||||
|
||||
var detail = document.createElement('div');
|
||||
detail.className = 'resource-detail-block';
|
||||
var detailTitle = document.createElement('div');
|
||||
detailTitle.className = 'resource-detail-title';
|
||||
detailTitle.textContent = tKey('secrets.profiles.references');
|
||||
detail.appendChild(detailTitle);
|
||||
|
||||
var refList = document.createElement('div');
|
||||
refList.className = 'secret-ref-list';
|
||||
secretIds.forEach(function(secretId) {
|
||||
var users = usage[secretId] || [];
|
||||
return '<span class="secret-ref-pill"><strong>' + secretName(secretsById, secretId) + '</strong><span>' + tfKey('secrets.profiles.reference_count', { count: users.length }) + '</span></span>';
|
||||
}).join('');
|
||||
return [
|
||||
'<div class="resource-card">',
|
||||
' <div class="resource-card-header">',
|
||||
' <div>',
|
||||
' <div class="resource-card-title">' + profile.name + '</div>',
|
||||
' <div class="resource-card-subtitle">' + profileSummary(profile, secretsById) + '</div>',
|
||||
' <div class="resource-pill-row">',
|
||||
' <span class="resource-status-pill active">' + authKindLabel(profile.kind) + '</span>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="resource-meta-grid">',
|
||||
' <div class="resource-meta-item"><div class="resource-meta-label">' + tKey('secrets.profiles.meta.created') + '</div><div class="resource-meta-value">' + formatDate(profile.created_at) + '</div></div>',
|
||||
' <div class="resource-meta-item"><div class="resource-meta-label">' + tKey('secrets.profiles.meta.updated') + '</div><div class="resource-meta-value">' + formatDate(profile.updated_at) + '</div></div>',
|
||||
' </div>',
|
||||
' <div class="resource-detail-block">',
|
||||
' <div class="resource-detail-title">' + tKey('secrets.profiles.references') + '</div>',
|
||||
' <div class="secret-ref-list">' + references + '</div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
}).join('');
|
||||
var pill = document.createElement('span');
|
||||
pill.className = 'secret-ref-pill';
|
||||
var strong = document.createElement('strong');
|
||||
strong.textContent = secretName(secretsById, secretId);
|
||||
var count = document.createElement('span');
|
||||
count.textContent = tfKey('secrets.profiles.reference_count', { count: users.length });
|
||||
pill.appendChild(strong);
|
||||
pill.appendChild(count);
|
||||
refList.appendChild(pill);
|
||||
});
|
||||
detail.appendChild(refList);
|
||||
card.appendChild(detail);
|
||||
|
||||
profilesList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
async function load() {
|
||||
|
||||
Reference in New Issue
Block a user