ui: add mobile api key cards
This commit is contained in:
@@ -46,6 +46,13 @@ Verification:
|
|||||||
- manual mobile checks at 375px width;
|
- manual mobile checks at 375px width;
|
||||||
- EN/RU copy audit.
|
- 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
|
## Planned
|
||||||
|
|
||||||
### `feat/open-core-repo-boundary`
|
### `feat/open-core-repo-boundary`
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
}
|
}
|
||||||
.scope-checkbox-name { font-size: 13px; font-weight: 500; color: var(--text-primary); }
|
.scope-checkbox-name { font-size: 13px; font-weight: 500; color: var(--text-primary); }
|
||||||
.scope-checkbox-desc { font-size: 11.5px; color: var(--text-muted); }
|
.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>
|
</style>
|
||||||
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
|
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -195,6 +200,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="resource-list keys-card-list" id="keys-card-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-card">
|
<div class="section-card">
|
||||||
|
|||||||
@@ -291,12 +291,16 @@ function renderScopes() {
|
|||||||
function renderTable(errorMessage) {
|
function renderTable(errorMessage) {
|
||||||
var q = search.toLowerCase();
|
var q = search.toLowerCase();
|
||||||
var tbody = document.getElementById('keys-tbody');
|
var tbody = document.getElementById('keys-tbody');
|
||||||
|
var cardList = document.getElementById('keys-card-list');
|
||||||
var rows = KEYS.filter(function(key) {
|
var rows = KEYS.filter(function(key) {
|
||||||
return !q || key.name.toLowerCase().includes(q) || key.prefix.toLowerCase().includes(q);
|
return !q || key.name.toLowerCase().includes(q) || key.prefix.toLowerCase().includes(q);
|
||||||
});
|
});
|
||||||
var subtitle = document.getElementById('keys-summary-subtitle');
|
var subtitle = document.getElementById('keys-summary-subtitle');
|
||||||
|
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
if (cardList) {
|
||||||
|
cardList.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (subtitle) {
|
if (subtitle) {
|
||||||
var active = KEYS.filter(function(key) { return key.status === 'active'; }).length;
|
var active = KEYS.filter(function(key) { return key.status === 'active'; }).length;
|
||||||
@@ -314,6 +318,7 @@ function renderTable(errorMessage) {
|
|||||||
errorCell.textContent = errorMessage;
|
errorCell.textContent = errorMessage;
|
||||||
errorRow.appendChild(errorCell);
|
errorRow.appendChild(errorCell);
|
||||||
tbody.appendChild(errorRow);
|
tbody.appendChild(errorRow);
|
||||||
|
renderKeyCards([], errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +332,7 @@ function renderTable(errorMessage) {
|
|||||||
: tKey('apikeys.agent.empty_hint');
|
: tKey('apikeys.agent.empty_hint');
|
||||||
empty.appendChild(td);
|
empty.appendChild(td);
|
||||||
tbody.appendChild(empty);
|
tbody.appendChild(empty);
|
||||||
|
renderKeyCards(rows);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,6 +376,121 @@ function renderTable(errorMessage) {
|
|||||||
|
|
||||||
tbody.appendChild(node);
|
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');
|
var modal = document.getElementById('modal-create');
|
||||||
|
|||||||
Reference in New Issue
Block a user