ui: add mobile api key cards
This commit is contained in:
@@ -46,6 +46,13 @@ Verification:
|
||||
- manual mobile checks at 375px width;
|
||||
- EN/RU copy audit.
|
||||
|
||||
Progress:
|
||||
- done:
|
||||
- `API Keys` page now has a mobile card layout instead of relying only on desktop table presentation
|
||||
- pending:
|
||||
- mobile card layouts for `Secrets` and `Usage`
|
||||
- remaining `Agents` and `Settings` commercial UX polish
|
||||
|
||||
## Planned
|
||||
|
||||
### `feat/open-core-repo-boundary`
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
}
|
||||
.scope-checkbox-name { font-size: 13px; font-weight: 500; color: var(--text-primary); }
|
||||
.scope-checkbox-desc { font-size: 11.5px; color: var(--text-muted); }
|
||||
.keys-card-list { display: none; }
|
||||
@media (max-width: 720px) {
|
||||
#keys-table-wrap { display: none; }
|
||||
.keys-card-list { display: grid; }
|
||||
}
|
||||
</style>
|
||||
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
|
||||
</head>
|
||||
@@ -195,6 +200,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="resource-list keys-card-list" id="keys-card-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
|
||||
@@ -291,12 +291,16 @@ function renderScopes() {
|
||||
function renderTable(errorMessage) {
|
||||
var q = search.toLowerCase();
|
||||
var tbody = document.getElementById('keys-tbody');
|
||||
var cardList = document.getElementById('keys-card-list');
|
||||
var rows = KEYS.filter(function(key) {
|
||||
return !q || key.name.toLowerCase().includes(q) || key.prefix.toLowerCase().includes(q);
|
||||
});
|
||||
var subtitle = document.getElementById('keys-summary-subtitle');
|
||||
|
||||
tbody.innerHTML = '';
|
||||
if (cardList) {
|
||||
cardList.innerHTML = '';
|
||||
}
|
||||
|
||||
if (subtitle) {
|
||||
var active = KEYS.filter(function(key) { return key.status === 'active'; }).length;
|
||||
@@ -314,6 +318,7 @@ function renderTable(errorMessage) {
|
||||
errorCell.textContent = errorMessage;
|
||||
errorRow.appendChild(errorCell);
|
||||
tbody.appendChild(errorRow);
|
||||
renderKeyCards([], errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,6 +332,7 @@ function renderTable(errorMessage) {
|
||||
: tKey('apikeys.agent.empty_hint');
|
||||
empty.appendChild(td);
|
||||
tbody.appendChild(empty);
|
||||
renderKeyCards(rows);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -370,6 +376,121 @@ function renderTable(errorMessage) {
|
||||
|
||||
tbody.appendChild(node);
|
||||
});
|
||||
|
||||
renderKeyCards(rows);
|
||||
}
|
||||
|
||||
function renderKeyCards(rows, errorMessage) {
|
||||
var cardList = document.getElementById('keys-card-list');
|
||||
if (!cardList) return;
|
||||
|
||||
cardList.innerHTML = '';
|
||||
|
||||
if (errorMessage) {
|
||||
cardList.appendChild(buildKeyCardMessage(errorMessage, true));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rows.length) {
|
||||
cardList.appendChild(
|
||||
buildKeyCardMessage(
|
||||
currentAgentId
|
||||
? (KEYS.length ? tKey('apikeys.empty.search') : tKey('apikeys.empty.none'))
|
||||
: tKey('apikeys.agent.empty_hint'),
|
||||
false
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
rows.forEach(function(key) {
|
||||
var card = document.createElement('div');
|
||||
card.className = 'resource-card';
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.className = 'resource-card-header';
|
||||
var headerMain = document.createElement('div');
|
||||
var title = document.createElement('div');
|
||||
title.className = 'resource-card-title';
|
||||
title.textContent = key.name;
|
||||
var subtitle = document.createElement('div');
|
||||
subtitle.className = 'resource-card-subtitle';
|
||||
subtitle.textContent = key.prefix;
|
||||
headerMain.appendChild(title);
|
||||
headerMain.appendChild(subtitle);
|
||||
|
||||
var actions = document.createElement('div');
|
||||
actions.className = 'resource-card-actions';
|
||||
|
||||
var statusBadge = document.createElement('span');
|
||||
statusBadge.className = key.status === 'active' ? 'badge badge-active' : 'badge badge-revoked';
|
||||
statusBadge.textContent = key.status === 'active'
|
||||
? tKey('apikeys.status.active')
|
||||
: tKey('apikeys.status.revoked');
|
||||
actions.appendChild(statusBadge);
|
||||
header.appendChild(headerMain);
|
||||
header.appendChild(actions);
|
||||
card.appendChild(header);
|
||||
|
||||
var metaGrid = document.createElement('div');
|
||||
metaGrid.className = 'resource-meta-grid';
|
||||
metaGrid.appendChild(buildMetaItem('apikeys.th.scopes', key.scopes.join(', ') || '—'));
|
||||
metaGrid.appendChild(buildMetaItem('apikeys.th.created', key.created));
|
||||
metaGrid.appendChild(buildMetaItem('apikeys.th.last', key.lastUsed));
|
||||
card.appendChild(metaGrid);
|
||||
|
||||
var actionRow = document.createElement('div');
|
||||
actionRow.className = 'resource-card-actions';
|
||||
if (key.status === 'active') {
|
||||
actionRow.appendChild(buildCardAction('apikeys.action.copy_prefix', function() {
|
||||
copyPrefix(key.prefix);
|
||||
}));
|
||||
actionRow.appendChild(buildCardAction('apikeys.action.revoke', function() {
|
||||
revokeKey(key.id);
|
||||
}, true));
|
||||
} else {
|
||||
actionRow.appendChild(buildCardAction('apikeys.action.delete', function() {
|
||||
deleteKey(key.id);
|
||||
}, true));
|
||||
}
|
||||
card.appendChild(actionRow);
|
||||
|
||||
cardList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function buildKeyCardMessage(text, isError) {
|
||||
var card = document.createElement('div');
|
||||
card.className = 'resource-card';
|
||||
var value = document.createElement('div');
|
||||
value.className = 'resource-meta-value';
|
||||
value.textContent = text;
|
||||
value.style.color = isError ? 'var(--danger,#f85149)' : 'var(--text-muted)';
|
||||
card.appendChild(value);
|
||||
return card;
|
||||
}
|
||||
|
||||
function buildMetaItem(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 buildCardAction(labelKey, onClick, isDanger) {
|
||||
var button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = isDanger ? 'btn-secondary' : 'btn-secondary';
|
||||
button.textContent = tKey(labelKey);
|
||||
button.addEventListener('click', onClick);
|
||||
return button;
|
||||
}
|
||||
|
||||
var modal = document.getElementById('modal-create');
|
||||
|
||||
Reference in New Issue
Block a user