feat(cron): Russian UI, table pagination 10 rows/page, bump to v1.3.11

This commit is contained in:
Naeel
2026-04-29 09:49:25 +03:00
parent 6816c038b4
commit abcd6bad36
2 changed files with 94 additions and 46 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ spec:
serviceAccountName: fission-console serviceAccountName: fission-console
containers: containers:
- name: console - name: console
image: naeel/fission-console:v1.3.10 image: naeel/fission-console:v1.3.11
ports: ports:
- containerPort: 8090 - containerPort: 8090
env: env:
+93 -45
View File
@@ -119,6 +119,19 @@
.status-warn { color: var(--warn); } .status-warn { color: var(--warn); }
.status-bad { color: var(--bad); } .status-bad { color: var(--bad); }
.empty { color: var(--text-secondary); padding: 24px 12px; text-align: center; } .empty { color: var(--text-secondary); padding: 24px 12px; text-align: center; }
.pagination {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
padding: 12px 16px; border-top: 1px solid var(--border);
}
.page-btn {
min-width: 32px; height: 28px; padding: 0 8px; border-radius: 6px;
background: #0c2b49; border: 1px solid #1d486d; color: #d7e7f7;
font-size: 12px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.page-btn:hover { background: #153d68; }
.page-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-btn:disabled { opacity: .35; cursor: default; }
.page-info { color: var(--text-secondary); font-size: 12px; margin-left: 6px; }
.footer { .footer {
padding-top: 12px; color: var(--text-secondary); font-size: 12px; line-height: 1.5; padding-top: 12px; color: var(--text-secondary); font-size: 12px; line-height: 1.5;
} }
@@ -137,12 +150,12 @@
<div class="brand-mark">N</div> <div class="brand-mark">N</div>
<div class="brand-text"> <div class="brand-text">
<div class="nubes">NUBES</div> <div class="nubes">NUBES</div>
<div class="product">CRON METRICS</div> <div class="product">МЕТРИКИ CRON</div>
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn ghost" id="btn-refresh">Refresh</button> <button class="btn ghost" id="btn-refresh">Обновить</button>
<a class="btn ghost" href="/console/">Console</a> <a class="btn ghost" href="/console/">Консоль</a>
</div> </div>
</div> </div>
@@ -176,7 +189,7 @@
<section class="panel"> <section class="panel">
<div class="panel-head"> <div class="panel-head">
<div> <div>
<h2>History</h2> <h2>История</h2>
<div class="meta" id="last-updated">Ожидание первых данных…</div> <div class="meta" id="last-updated">Ожидание первых данных…</div>
</div> </div>
<div class="meta mono" id="latest-source"></div> <div class="meta mono" id="latest-source"></div>
@@ -185,19 +198,19 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Time</th> <th>Время</th>
<th>Source</th> <th>Источник</th>
<th>Host</th> <th>Хост</th>
<th>Free mem</th> <th>Своб. МБ</th>
<th>Avail mem</th> <th>Дост. МБ</th>
<th>Used mem</th> <th>Исп. МБ</th>
<th>Mem %</th> <th>Память %</th>
<th>CPU 1m</th> <th>ЦП 1м</th>
<th>CPU 5m</th> <th>ЦП 5м</th>
<th>CPU 15m</th> <th>ЦП 15м</th>
<th>Disk free</th> <th>Диск своб.</th>
<th>Uptime</th> <th>Аптайм</th>
<th>Status</th> <th>Статус</th>
</tr> </tr>
</thead> </thead>
<tbody id="history-body"> <tbody id="history-body">
@@ -205,6 +218,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div id="pagination" class="pagination"></div>
</section> </section>
<div class="footer"> <div class="footer">
@@ -261,9 +275,9 @@
const days = Math.floor(sec / 86400); const days = Math.floor(sec / 86400);
const hours = Math.floor((sec % 86400) / 3600); const hours = Math.floor((sec % 86400) / 3600);
const mins = Math.floor((sec % 3600) / 60); const mins = Math.floor((sec % 3600) / 60);
if (days > 0) return `${days}d ${hours}h ${mins}m`; if (days > 0) return `${days}д ${hours}ч ${mins}м`;
if (hours > 0) return `${hours}h ${mins}m`; if (hours > 0) return `${hours}ч ${mins}м`;
return `${Math.floor(sec)}s`; return `${Math.floor(sec)}с`;
} }
function cls(status) { function cls(status) {
@@ -286,35 +300,48 @@
function renderSummary(latest, count) { function renderSummary(latest, count) {
if (!latest) { if (!latest) {
summaryEl.innerHTML = [ summaryEl.innerHTML = [
card('Snapshots', '0', 'Waiting for cron POSTs'), card('Снимки', '0', 'Ожидание cron-запросов'),
card('Free memory', '—', 'MB'), card('Своб. память', '—', 'МБ'),
card('Available memory', '—', 'MB'), card('Дост. память', '—', 'МБ'),
card('Used memory', '—', 'MB'), card('Исп. память', '—', 'МБ'),
card('CPU load', '—', '1m / 5m / 15m'), card('Нагрузка ЦП', '—', '1м / 5м / 15м'),
card('Disk free', '—', 'GB') card('Диск своб.', '—', 'ГБ')
].join(''); ].join('');
pointsBadge.textContent = '0 points'; pointsBadge.textContent = '0 снимков';
return; return;
} }
summaryEl.innerHTML = [ summaryEl.innerHTML = [
card('Snapshots', String(count), `Source: ${latest.source || 'cron'}`), card('Снимки', String(count), `Источник: ${latest.source || 'cron'}`),
card('Free memory', fmtNum(latest.memory_free_mb, 1, ' MB'), latest.hostname || '—'), card('Своб. память', fmtNum(latest.memory_free_mb, 1, ' МБ'), latest.hostname || '—'),
card('Available memory', fmtNum(latest.memory_available_mb, 1, ' MB'), 'Current available RAM'), card('Дост. память', fmtNum(latest.memory_available_mb, 1, ' МБ'), 'Доступная RAM'),
card('Used memory', fmtNum(latest.memory_used_mb, 1, ' MB'), `Mem ${fmtNum(latest.memory_percent, 1, '%')}`), card('Исп. память', fmtNum(latest.memory_used_mb, 1, ' МБ'), `Память ${fmtNum(latest.memory_percent, 1, '%')}`),
card('CPU load', [fmtNum(latest.cpu_load_1m, 2), fmtNum(latest.cpu_load_5m, 2), fmtNum(latest.cpu_load_15m, 2)].join(' / '), '1m / 5m / 15m'), card('Нагрузка ЦП', [fmtNum(latest.cpu_load_1m, 2), fmtNum(latest.cpu_load_5m, 2), fmtNum(latest.cpu_load_15m, 2)].join(' / '), '1м / 5м / 15м'),
card('Disk free', fmtNum(latest.disk_free_gb, 1, ' GB'), `Uptime ${fmtUptime(latest.uptime_sec)}`) card('Диск своб.', fmtNum(latest.disk_free_gb, 1, ' ГБ'), `Аптайм ${fmtUptime(latest.uptime_sec)}`)
].join(''); ].join('');
pointsBadge.textContent = `${count} point(s)`; pointsBadge.textContent = `${count} снимк${count === 1 ? '' : 'ов'}`;
} }
function renderTable(history) { const PAGE_SIZE = 10;
if (!history.length) { let allHistory = [];
let currentPage = 1;
function renderPage(page) {
const total = allHistory.length;
const pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
currentPage = Math.min(Math.max(1, page), pages);
const pg = document.getElementById('pagination');
if (!total) {
historyBody.innerHTML = '<tr><td class="empty" colspan="13">Данных пока нет</td></tr>'; historyBody.innerHTML = '<tr><td class="empty" colspan="13">Данных пока нет</td></tr>';
pg.innerHTML = '';
return; return;
} }
historyBody.innerHTML = history.slice().reverse().map(function (item) { const start = (currentPage - 1) * PAGE_SIZE;
const slice = allHistory.slice(start, start + PAGE_SIZE);
historyBody.innerHTML = slice.map(function (item) {
return ` return `
<tr> <tr>
<td class="mono">${fmtTime(item.timestamp)}</td> <td class="mono">${fmtTime(item.timestamp)}</td>
@@ -327,12 +354,33 @@
<td class="mono">${fmtNum(item.cpu_load_1m, 2)}</td> <td class="mono">${fmtNum(item.cpu_load_1m, 2)}</td>
<td class="mono">${fmtNum(item.cpu_load_5m, 2)}</td> <td class="mono">${fmtNum(item.cpu_load_5m, 2)}</td>
<td class="mono">${fmtNum(item.cpu_load_15m, 2)}</td> <td class="mono">${fmtNum(item.cpu_load_15m, 2)}</td>
<td class="mono">${fmtNum(item.disk_free_gb, 1, ' GB')}</td> <td class="mono">${fmtNum(item.disk_free_gb, 1, ' ГБ')}</td>
<td class="mono">${fmtUptime(item.uptime_sec)}</td> <td class="mono">${fmtUptime(item.uptime_sec)}</td>
<td class="${cls(item.status)}">${item.status || 'ok'}</td> <td class="${cls(item.status)}">${item.status || 'ok'}</td>
</tr> </tr>
`; `;
}).join(''); }).join('');
if (pages <= 1) { pg.innerHTML = ''; return; }
let btns = `<button class="page-btn" id="pg-prev" ${currentPage === 1 ? 'disabled' : ''}>←</button>`;
for (let i = 1; i <= pages; i++) {
btns += `<button class="page-btn ${i === currentPage ? 'active' : ''}" data-page="${i}">${i}</button>`;
}
btns += `<button class="page-btn" id="pg-next" ${currentPage === pages ? 'disabled' : ''}>→</button>`;
btns += `<span class="page-info">стр. ${currentPage} из ${pages} · всего ${total}</span>`;
pg.innerHTML = btns;
pg.querySelector('#pg-prev').addEventListener('click', function () { renderPage(currentPage - 1); });
pg.querySelector('#pg-next').addEventListener('click', function () { renderPage(currentPage + 1); });
pg.querySelectorAll('[data-page]').forEach(function (btn) {
btn.addEventListener('click', function () { renderPage(Number(btn.dataset.page)); });
});
}
function renderTable(history) {
allHistory = history.slice().reverse();
renderPage(1);
} }
function updateMeta(history) { function updateMeta(history) {
@@ -358,17 +406,17 @@
updateMeta(history); updateMeta(history);
} catch (err) { } catch (err) {
summaryEl.innerHTML = [ summaryEl.innerHTML = [
card('Snapshots', '—', 'Load error'), card('Снимки', '—', 'Ошибка загрузки'),
card('Free memory', '—', err.message), card('Своб. память', '—', err.message),
card('Available memory', '—', 'Check backend'), card('Дост. память', '—', 'Проверить backend'),
card('Used memory', '—', 'Check cron function'), card('Исп. память', '—', 'Проверить cron-функцию'),
card('CPU load', '—', 'Check backend'), card('Нагрузка ЦП', '—', 'Проверить backend'),
card('Disk free', '—', 'Check ingest') card('Диск своб.', '—', 'Проверить приём данных')
].join(''); ].join('');
historyBody.innerHTML = `<tr><td class="empty" colspan="13">Ошибка загрузки: ${err.message}</td></tr>`; historyBody.innerHTML = `<tr><td class="empty" colspan="13">Ошибка загрузки: ${err.message}</td></tr>`;
lastUpdatedEl.textContent = 'Ошибка обновления'; lastUpdatedEl.textContent = 'Ошибка обновления';
latestSourceEl.textContent = ''; latestSourceEl.textContent = '';
pointsBadge.textContent = '0 points'; pointsBadge.textContent = '0 снимков';
} }
} }