diff --git a/TASKS.md b/TASKS.md
index 349401b..b8385f2 100644
--- a/TASKS.md
+++ b/TASKS.md
@@ -49,8 +49,9 @@ Verification:
Progress:
- done:
- `API Keys` page now has a mobile card layout instead of relying only on desktop table presentation
+ - `Secrets` page now has a mobile card layout instead of relying only on desktop table presentation
- pending:
- - mobile card layouts for `Secrets` and `Usage`
+ - mobile card layout for `Usage`
- remaining `Agents` and `Settings` commercial UX polish
## Planned
diff --git a/apps/ui/html/secrets.html b/apps/ui/html/secrets.html
index b8a71ba..defbeb3 100644
--- a/apps/ui/html/secrets.html
+++ b/apps/ui/html/secrets.html
@@ -39,6 +39,11 @@
color: var(--text-primary);
font-weight: 600;
}
+ .secrets-card-list { display: none; }
+ @media (max-width: 720px) {
+ #secrets-table-wrap { display: none; }
+ .secrets-card-list { display: grid; }
+ }
@@ -138,6 +143,7 @@
diff --git a/apps/ui/js/secrets.js b/apps/ui/js/secrets.js
index 8b89519..2fecacf 100644
--- a/apps/ui/js/secrets.js
+++ b/apps/ui/js/secrets.js
@@ -17,6 +17,7 @@ function initSecretsPage() {
var modal = document.getElementById('secret-modal');
var tbody = document.getElementById('secrets-tbody');
+ var cardList = document.getElementById('secrets-card-list');
var profilesList = document.getElementById('auth-profiles-list');
var summary = document.getElementById('secrets-summary');
var profilesSummary = document.getElementById('secret-profiles-summary');
@@ -248,6 +249,9 @@ function initSecretsPage() {
});
window.CrankDom.clear(tbody);
+ if (cardList) {
+ window.CrankDom.clear(cardList);
+ }
if (state.loading && state.secrets.length === 0) {
var loadingRow = document.createElement('tr');
@@ -257,6 +261,7 @@ function initSecretsPage() {
loadingCell.textContent = tKey('secrets.loading');
loadingRow.appendChild(loadingCell);
tbody.appendChild(loadingRow);
+ renderSecretCards([], tKey('secrets.loading'));
return;
}
@@ -268,6 +273,7 @@ function initSecretsPage() {
errorCell.textContent = state.error;
errorRow.appendChild(errorCell);
tbody.appendChild(errorRow);
+ renderSecretCards([], state.error);
return;
}
@@ -279,6 +285,7 @@ function initSecretsPage() {
emptyCell.textContent = state.secrets.length ? tKey('secrets.empty.search') : tKey('secrets.empty.none');
emptyRow.appendChild(emptyCell);
tbody.appendChild(emptyRow);
+ renderSecretCards(rows);
return;
}
@@ -340,6 +347,106 @@ function initSecretsPage() {
tbody.appendChild(tr);
});
+
+ renderSecretCards(rows);
+ }
+
+ function renderSecretCards(rows, statusText) {
+ if (!cardList) {
+ return;
+ }
+
+ window.CrankDom.clear(cardList);
+
+ if (statusText && !rows.length) {
+ var messageCard = document.createElement('div');
+ messageCard.className = 'resource-card';
+ var message = document.createElement('div');
+ message.className = 'resource-meta-value';
+ message.textContent = statusText;
+ message.style.color = state.error ? 'var(--danger,#f85149)' : 'var(--text-muted)';
+ messageCard.appendChild(message);
+ cardList.appendChild(messageCard);
+ return;
+ }
+
+ rows.forEach(function(secret) {
+ var references = state.derived.usageBySecret[secret.id] || [];
+ 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 = secret.name;
+ var subtitle = document.createElement('div');
+ subtitle.className = 'resource-card-subtitle';
+ subtitle.textContent = secretKindLabel(secret.kind);
+ headerBody.appendChild(title);
+ headerBody.appendChild(subtitle);
+
+ var actions = document.createElement('div');
+ actions.className = 'resource-card-actions';
+ var badge = document.createElement('span');
+ badge.className = 'badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active');
+ badge.textContent = tKey('secrets.status.' + secret.status);
+ actions.appendChild(badge);
+ header.appendChild(headerBody);
+ header.appendChild(actions);
+ card.appendChild(header);
+
+ var metaGrid = document.createElement('div');
+ metaGrid.className = 'resource-meta-grid';
+ metaGrid.appendChild(buildSecretMetaItem('secrets.th.version', 'v' + secret.current_version));
+ metaGrid.appendChild(buildSecretMetaItem(
+ 'secrets.th.last_used',
+ secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never')
+ ));
+ metaGrid.appendChild(buildSecretMetaItem(
+ 'secrets.th.used_by',
+ window.tPlural('secrets.used_by_count', references.length, { count: references.length })
+ ));
+ card.appendChild(metaGrid);
+
+ var actionRow = document.createElement('div');
+ actionRow.className = 'resource-card-actions';
+ var rotateButton = document.createElement('button');
+ rotateButton.className = 'btn-secondary';
+ rotateButton.type = 'button';
+ rotateButton.textContent = tKey('secrets.action.rotate');
+ rotateButton.addEventListener('click', function() {
+ openModal('rotate', secret);
+ });
+ actionRow.appendChild(rotateButton);
+
+ var deleteButton = document.createElement('button');
+ deleteButton.className = 'btn-secondary';
+ deleteButton.type = 'button';
+ deleteButton.textContent = tKey('secrets.action.delete');
+ deleteButton.addEventListener('click', async function() {
+ await deleteSecret(secret);
+ });
+ actionRow.appendChild(deleteButton);
+ card.appendChild(actionRow);
+
+ cardList.appendChild(card);
+ });
+ }
+
+ function buildSecretMetaItem(labelKey, valueText) {
+ var item = document.createElement('div');
+ item.className = 'resource-meta-item';
+ var label = document.createElement('div');
+ label.className = 'resource-meta-label';
+ label.textContent = tKey(labelKey);
+ var value = document.createElement('div');
+ value.className = 'resource-meta-value';
+ value.textContent = valueText;
+ item.appendChild(label);
+ item.appendChild(value);
+ return item;
}
function renderProfiles() {