ui: add mobile usage cards
This commit is contained in:
@@ -50,8 +50,8 @@ Progress:
|
|||||||
- done:
|
- done:
|
||||||
- `API Keys` page now has a mobile card layout instead of relying only on desktop table presentation
|
- `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
|
- `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:
|
- pending:
|
||||||
- mobile card layout for `Usage`
|
|
||||||
- remaining `Agents` and `Settings` commercial UX polish
|
- remaining `Agents` and `Settings` commercial UX polish
|
||||||
|
|
||||||
## Planned
|
## Planned
|
||||||
|
|||||||
@@ -10,6 +10,13 @@
|
|||||||
<link rel="stylesheet" href="css/layout.css">
|
<link rel="stylesheet" href="css/layout.css">
|
||||||
<link rel="stylesheet" href="css/pages.css">
|
<link rel="stylesheet" href="css/pages.css">
|
||||||
<link rel="stylesheet" href="css/usage.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>
|
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -162,6 +169,7 @@
|
|||||||
<div class="section-card-subtitle">Breakdown for last 7 days</div>
|
<div class="section-card-subtitle">Breakdown for last 7 days</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="usage-table-wrap">
|
||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -178,6 +186,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="resource-list usage-card-list" id="usage-card-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
var exportBtn = document.querySelector('.page-header-actions .btn-secondary');
|
var exportBtn = document.querySelector('.page-header-actions .btn-secondary');
|
||||||
var chartWrap = document.getElementById('chart-bars');
|
var chartWrap = document.getElementById('chart-bars');
|
||||||
var tableBody = document.getElementById('usage-tbody');
|
var tableBody = document.getElementById('usage-tbody');
|
||||||
|
var cardList = document.getElementById('usage-card-list');
|
||||||
var chartTemplate = document.getElementById('tmpl-chart-bar');
|
var chartTemplate = document.getElementById('tmpl-chart-bar');
|
||||||
var rowTemplate = document.getElementById('tmpl-usage-row');
|
var rowTemplate = document.getElementById('tmpl-usage-row');
|
||||||
var subtitle = document.querySelector('.section-card-subtitle');
|
var subtitle = document.querySelector('.section-card-subtitle');
|
||||||
@@ -330,6 +331,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
function renderTable() {
|
function renderTable() {
|
||||||
var operations = state.derived.localizedOperations;
|
var operations = state.derived.localizedOperations;
|
||||||
tableBody.innerHTML = '';
|
tableBody.innerHTML = '';
|
||||||
|
if (cardList) {
|
||||||
|
cardList.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (!operations.length) {
|
if (!operations.length) {
|
||||||
var row = document.createElement('tr');
|
var row = document.createElement('tr');
|
||||||
@@ -345,6 +349,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
);
|
);
|
||||||
row.appendChild(cell);
|
row.appendChild(cell);
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
|
renderOperationCards([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,6 +392,75 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
tableBody.appendChild(node);
|
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() {
|
function renderUsage() {
|
||||||
|
|||||||
Reference in New Issue
Block a user