UI: адаптив, журнал, спиннеры; docs: README и api_routes

- Навигация: выезжающее меню при узком экране (nav-mobile.js)
- Журнал: карточки <620px, компактная пагинация, время в две строки <920px
- Создание кластера: оверлей загрузки, инкрементальное обновление таблицы заданий
- Документация: полноэкранный спиннер при загрузке и навигации
- Главная: масштабирование CTA, статистика 2 колонки <520px, донаты перенос <710px
- README: env.example, новые фичи UI, автор в конце файла
- api_routes: маршрут /cluster-create, спиннеры, шаблоны; автор в конце
- env.example: автор перенесён в конец файла
This commit is contained in:
Sergey Antropoff
2026-04-05 00:18:19 +03:00
parent 4b703801e1
commit f0b24c8901
12 changed files with 2426 additions and 173 deletions
+58 -12
View File
@@ -80,6 +80,37 @@
}
}
/**
* Ячейка «время»: дата и время отдельными span (при ширине до 920px — в две строки, см. CSS .journal-time-stack).
* @param {string} iso
* @returns {string} безопасный HTML
*/
function formatUtcCellHtml(iso) {
if (!iso) {
return esc("—");
}
try {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) {
return esc(iso);
}
const day = d.toISOString().slice(0, 10);
const clock = d.toISOString().slice(11, 19);
return (
'<span class="journal-time-stack">' +
'<span class="journal-time-date">' +
esc(day) +
"</span>" +
'<span class="journal-time-clock">' +
esc(clock) +
"</span>" +
"</span>"
);
} catch (e) {
return esc(iso);
}
}
function statusClass(st) {
const s = (st || "").toLowerCase();
if (s === "success") return "journal-status-pill journal-status-pill--ok";
@@ -189,11 +220,12 @@
return b;
}
/* Короткий текст на кнопках — одна строка пагинации; смысл в aria-label */
wrap.appendChild(
mkBtn( Первая", page <= 1, "Первая страница", function () { onSelectPage(1); }),
mkBtn(«", page <= 1, "Первая страница", function () { onSelectPage(1); }),
);
wrap.appendChild(
mkBtn(" Назад", page <= 1, "Предыдущая страница", function () { onSelectPage(page - 1); }),
mkBtn("", page <= 1, "Предыдущая страница", function () { onSelectPage(page - 1); }),
);
const pagesEl = document.createElement("div");
@@ -227,10 +259,10 @@
wrap.appendChild(pagesEl);
wrap.appendChild(
mkBtn("Вперёд ", page >= totalPages, "Следующая страница", function () { onSelectPage(page + 1); }),
mkBtn("", page >= totalPages, "Следующая страница", function () { onSelectPage(page + 1); }),
);
wrap.appendChild(
mkBtn("Последняя »", page >= totalPages, "Последняя страница", function () { onSelectPage(totalPages); }),
mkBtn("»»", page >= totalPages, "Последняя страница", function () { onSelectPage(totalPages); }),
);
pagNav.appendChild(wrap);
@@ -252,7 +284,7 @@
const tr = document.createElement("tr");
tr.className = "journal-data-row";
const cluster = e.source_cluster || e.cluster_name || "—";
const finished = formatUtc(
const finishedHtml = formatUtcCellHtml(
e.finished_at_utc || e.created_at_utc,
);
const kind = mode === "helm_addons" ? formatHelmJournalKind(e) : (e.kind || "");
@@ -266,15 +298,29 @@
const logText = lines.join("\n");
const detailsId = "journal-log-" + Math.random().toString(36).slice(2, 11);
/* Классы create-jobs-card-td* — карточки на узком экране (<620px), см. .journal-list-card в style.css */
tr.innerHTML =
"<td class=\"journal-cell-time\">" + esc(finished) + "</td>" +
"<td><code class=\"journal-code\">" + esc(cluster) + "</code></td>" +
"<td><code class=\"journal-code journal-cell-kind\">" + esc(kind) + "</code></td>" +
"<td><span class=\"" + esc(statusClass(e.status)) + "\">" + esc(e.status || "") + "</span></td>" +
"<td class=\"journal-cell-msg\">" + esc(msg || "—") + "</td>" +
"<td class=\"journal-cell-actions\">" +
"<td class=\"journal-cell-time create-jobs-card-td create-jobs-card-td--time\">" +
finishedHtml +
"</td>" +
"<td class=\"create-jobs-card-td create-jobs-card-td--cluster\"><code class=\"journal-code\">" +
esc(cluster) +
"</code></td>" +
"<td class=\"create-jobs-card-td create-jobs-card-td--kind\"><code class=\"journal-code journal-cell-kind\">" +
esc(kind) +
"</code></td>" +
"<td class=\"create-jobs-card-td create-jobs-card-td--status\"><span class=\"" +
esc(statusClass(e.status)) +
"\">" +
esc(e.status || "") +
"</span></td>" +
"<td class=\"journal-cell-msg create-jobs-card-td create-jobs-card-td--msg\"><span class=\"create-jobs-msg-value\">" +
esc(msg || "—") +
"</span></td>" +
"<td class=\"journal-cell-actions create-jobs-card-td create-jobs-card-td--log\">" +
"<button type=\"button\" class=\"btn-secondary btn-journal-toggle\" aria-expanded=\"false\" aria-controls=\"" +
detailsId + "\">Показать лог</button>" +
detailsId +
"\">Показать лог</button>" +
"</td>";
tbody.appendChild(tr);