ui: add mobile usage cards
This commit is contained in:
@@ -50,8 +50,8 @@ 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
|
||||
- `Usage` page now has a mobile card layout instead of relying only on desktop table presentation
|
||||
- pending:
|
||||
- mobile card layout for `Usage`
|
||||
- remaining `Agents` and `Settings` commercial UX polish
|
||||
|
||||
## Planned
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/pages.css">
|
||||
<link rel="stylesheet" href="css/usage.css">
|
||||
<style>
|
||||
.usage-card-list { display: none; }
|
||||
@media (max-width: 720px) {
|
||||
#usage-table-wrap { display: none; }
|
||||
.usage-card-list { display: grid; }
|
||||
}
|
||||
</style>
|
||||
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -162,6 +169,7 @@
|
||||
<div class="section-card-subtitle">Breakdown for last 7 days</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="usage-table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -177,6 +185,8 @@
|
||||
<tbody id="usage-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="resource-list usage-card-list" id="usage-card-list"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
var exportBtn = document.querySelector('.page-header-actions .btn-secondary');
|
||||
var chartWrap = document.getElementById('chart-bars');
|
||||
var tableBody = document.getElementById('usage-tbody');
|
||||
var cardList = document.getElementById('usage-card-list');
|
||||
var chartTemplate = document.getElementById('tmpl-chart-bar');
|
||||
var rowTemplate = document.getElementById('tmpl-usage-row');
|
||||
var subtitle = document.querySelector('.section-card-subtitle');
|
||||
@@ -330,6 +331,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
function renderTable() {
|
||||
var operations = state.derived.localizedOperations;
|
||||
tableBody.innerHTML = '';
|
||||
if (cardList) {
|
||||
cardList.innerHTML = '';
|
||||
}
|
||||
|
||||
if (!operations.length) {
|
||||
var row = document.createElement('tr');
|
||||
@@ -345,6 +349,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
);
|
||||
row.appendChild(cell);
|
||||
tableBody.appendChild(row);
|
||||
renderOperationCards([]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -387,6 +392,75 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
tableBody.appendChild(node);
|
||||
});
|
||||
|
||||
renderOperationCards(operations);
|
||||
}
|
||||
|
||||
function renderOperationCards(operations) {
|
||||
if (!cardList) {
|
||||
return;
|
||||
}
|
||||
|
||||
cardList.innerHTML = '';
|
||||
|
||||
if (!operations.length) {
|
||||
cardList.appendChild(buildEmptyState(
|
||||
tKey('usage.table.empty.title'),
|
||||
tKey('usage.table.empty.sub'),
|
||||
false
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
operations.forEach(function(entry) {
|
||||
var operation = entry.operation;
|
||||
var localizedOperation = entry.localized;
|
||||
var errorRate = operation.calls_total === 0
|
||||
? 0
|
||||
: ((operation.calls_error / operation.calls_total) * 100);
|
||||
var share = state.derived.totalCalls === 0
|
||||
? 0
|
||||
: ((operation.calls_total / state.derived.totalCalls) * 100);
|
||||
|
||||
var card = element('div', 'resource-card');
|
||||
var header = element('div', 'resource-card-header');
|
||||
var headerMain = element('div');
|
||||
headerMain.appendChild(element(
|
||||
'div',
|
||||
'resource-card-title',
|
||||
localizedOperation.operation_display_name || operation.operation_display_name
|
||||
));
|
||||
headerMain.appendChild(element('div', 'resource-card-subtitle', operation.operation_name));
|
||||
|
||||
var actions = element('div', 'resource-card-actions');
|
||||
var protocolBadge = element('span', 'badge');
|
||||
protocolBadge.textContent = protocolLabel(operation.protocol);
|
||||
protocolBadge.style.color = protocolColor(operation.protocol);
|
||||
actions.appendChild(protocolBadge);
|
||||
header.appendChild(headerMain);
|
||||
header.appendChild(actions);
|
||||
card.appendChild(header);
|
||||
|
||||
var metaGrid = element('div', 'resource-meta-grid');
|
||||
metaGrid.appendChild(buildUsageMetaItem('usage.table.th.calls', formatCount(operation.calls_total)));
|
||||
metaGrid.appendChild(buildUsageMetaItem('usage.table.th.errors', formatCount(operation.calls_error)));
|
||||
metaGrid.appendChild(buildUsageMetaItem('usage.table.th.error_rate', errorRate.toFixed(2) + '%'));
|
||||
metaGrid.appendChild(buildUsageMetaItem(
|
||||
'usage.table.th.latency',
|
||||
formatMs(operation.p50_ms) + ' / ' + formatMs(operation.p95_ms) + ' / ' + formatMs(operation.p99_ms)
|
||||
));
|
||||
metaGrid.appendChild(buildUsageMetaItem('usage.table.th.share', tfKey('usage.table.share', { value: share.toFixed(1) })));
|
||||
card.appendChild(metaGrid);
|
||||
|
||||
cardList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function buildUsageMetaItem(labelKey, valueText) {
|
||||
var item = element('div', 'resource-meta-item');
|
||||
item.appendChild(element('div', 'resource-meta-label', tKey(labelKey)));
|
||||
item.appendChild(element('div', 'resource-meta-value', valueText));
|
||||
return item;
|
||||
}
|
||||
|
||||
function renderUsage() {
|
||||
|
||||
Reference in New Issue
Block a user