Журнал: пагинация 30 записей на страницу, API offset/total_pages

- job_journal: collect_recent_journal_entries_page_sync(limit, offset)
- GET /api/v1/journal/recent: limit по умолчанию 30, offset, total, page, total_pages
- journal.html/js: навигация Первая/Назад/номера/Вперёд/Последняя, стили
- app/docs/api_routes.md: описание query и пример ответа
- Прочие изменения UI/API (аддоны, helm, job_journal в кластерах) в том же коммите
This commit is contained in:
Sergey Antropoff
2026-04-04 11:16:43 +03:00
parent 52538d9816
commit 17f6233fd7
22 changed files with 2811 additions and 4 deletions
+94 -1
View File
@@ -189,6 +189,50 @@
if (el) el.classList.add("hidden");
}
function isJobLogModalOpen() {
const el = document.getElementById("job-log-modal-overlay");
return !!(el && !el.classList.contains("hidden"));
}
function closeJobLogModal() {
const el = document.getElementById("job-log-modal-overlay");
if (el) el.classList.add("hidden");
}
/**
* Открыть модалку с текстовым журналом завершённого задания (GET /jobs/{id}).
* @param {string} jobId
*/
function openJobLogModal(jobId) {
const overlay = document.getElementById("job-log-modal-overlay");
const sub = document.getElementById("job-log-modal-sub");
const pre = document.getElementById("job-log-modal-body");
const titleEl = document.getElementById("job-log-modal-title");
if (!overlay || !pre) return;
pre.textContent = "Загрузка журнала…";
if (sub) sub.textContent = "Задание: " + jobId;
if (titleEl) titleEl.textContent = "Журнал задания";
overlay.classList.remove("hidden");
api("/jobs/" + encodeURIComponent(jobId))
.then(function (job) {
if (titleEl) {
titleEl.textContent =
"Журнал задания · " + (job.status || "") + (job.kind ? " · " + job.kind : "");
}
if (sub) {
var parts = ["Задание: " + jobId];
if (job.cluster_name) parts.push("Кластер: " + job.cluster_name);
if (job.message) parts.push(job.message);
sub.textContent = parts.join(" · ");
}
var lines = job.progress_log || [];
pre.textContent = lines.length ? lines.join("\n") : job.message || "(журнал пуст)";
})
.catch(function (e) {
pre.textContent = "Не удалось загрузить журнал: " + (e && e.message ? e.message : String(e));
});
}
/**
* Загрузить ``GET /clusters/{name}/provision-log`` и показать JSON в модалке.
* @param {string} clusterName
@@ -1760,6 +1804,27 @@
}
}
/**
* Один раз: кнопка «Журнал» в таблице последних заданий открывает модалку (не сбрасывается при автообновлении списка).
*/
function bindJobsTableJobLogOpen() {
const tbl = document.getElementById("tbl-jobs");
if (!tbl || tbl.getAttribute("data-jobs-job-log-bound") === "1") return;
tbl.setAttribute("data-jobs-job-log-bound", "1");
tbl.addEventListener(
"click",
function (ev) {
const btn = ev.target && ev.target.closest ? ev.target.closest("button.jobs-open-log-btn") : null;
if (!btn || !tbl.contains(btn)) return;
const jobId = btn.getAttribute("data-job-id");
if (!jobId) return;
ev.preventDefault();
openJobLogModal(jobId);
},
false,
);
}
async function loadJobs() {
const tbody = document.querySelector("#tbl-jobs tbody");
const msg = document.getElementById("jobs-msg");
@@ -1782,6 +1847,14 @@
j.progress_stage +
(j.progress_percent != null ? " (" + j.progress_percent + "%)" : "");
}
var msgCol =
"<div class=\"jobs-msg-primary\">" + escapeHtml(cellMsg) + "</div>";
if (j.status === "failed" || j.status === "cancelled") {
msgCol +=
"<button type=\"button\" class=\"btn-secondary jobs-open-log-btn\" data-job-id=\"" +
escapeHtml(j.job_id || "") +
"\">Журнал</button>";
}
const iso = j.created_at_utc || "";
const timeLabel = formatJobTimeUtc(iso);
tr.innerHTML =
@@ -1801,7 +1874,7 @@
st +
"</span></td>" +
"<td class=\"jobs-msg-cell\">" +
escapeHtml(cellMsg) +
msgCol +
"</td>";
frag.appendChild(tr);
});
@@ -2324,6 +2397,10 @@
});
}
if (document.getElementById("tbl-jobs")) {
bindJobsTableJobLogOpen();
}
const mClose = document.getElementById("modal-close");
if (mClose) mClose.addEventListener("click", closeModal);
@@ -2340,6 +2417,10 @@
closeConfirmModal(confirmModalIsAlert ? true : false);
return;
}
if (isJobLogModalOpen()) {
closeJobLogModal();
return;
}
if (isProvisionLogModalOpen()) {
closeProvisionLogModal();
return;
@@ -2378,6 +2459,17 @@
});
}
const jobLogOv = document.getElementById("job-log-modal-overlay");
const jobLogClose = document.getElementById("job-log-modal-close");
if (jobLogClose) {
jobLogClose.addEventListener("click", closeJobLogModal);
}
if (jobLogOv) {
jobLogOv.addEventListener("click", function (ev) {
if (ev.target === jobLogOv) closeJobLogModal();
});
}
window.addEventListener(
"scroll",
function () {
@@ -2419,6 +2511,7 @@
}
bindActionTooltipHosts(document.getElementById("modal-overlay"));
bindActionTooltipHosts(document.getElementById("provision-log-modal-overlay"));
bindActionTooltipHosts(document.getElementById("job-log-modal-overlay"));
}
if (document.readyState === "loading") {