Веб-интерфейс: страница /clusters, навигация и крошки для кластеров
- Выделена страница списка кластеров, панель упрощена; nav_active и крошки ведут в раздел Кластеры; theme.js синхронизирует активную пилюлю по URL. - Доработки дашборда, аддонов, журнала, стилей и API-документации. - Поддержка Podman: docker-compose.podman.yml, скрипты сокета; Makefile и env.
This commit is contained in:
+46
-9
@@ -82,9 +82,18 @@ def append_job_from_record_sync(
|
||||
return
|
||||
|
||||
cdir = clusters_dir() / name
|
||||
# Страница «Журнал» читает clusters/<имя>/journal/jobs_history.json — нужен каталог кластера.
|
||||
# После отмены create_cluster каталог мог быть удалён (rmtree) — не создаём пустой каталог заново.
|
||||
if not cdir.is_dir():
|
||||
logger.debug("Журнал кластера: каталог %s отсутствует", cdir)
|
||||
return
|
||||
if (status or "").lower() == "cancelled":
|
||||
logger.debug("Журнал кластера: каталог %s удалён при отмене — запись jobs_history пропускаем", cdir)
|
||||
return
|
||||
try:
|
||||
cdir.mkdir(parents=True, exist_ok=True)
|
||||
logger.info("Журнал: создан каталог кластера для записи jobs_history: %s", cdir)
|
||||
except OSError as e:
|
||||
logger.warning("Журнал кластера: не удалось создать %s: %s", cdir, e)
|
||||
return
|
||||
|
||||
jdir = cdir / JOURNAL_SUBDIR
|
||||
finished = datetime.now(timezone.utc).isoformat()
|
||||
@@ -185,21 +194,49 @@ def _collect_all_journal_rows_sorted_sync() -> list[tuple[str, dict[str, Any]]]:
|
||||
return flat
|
||||
|
||||
|
||||
def collect_recent_journal_entries_page_sync(*, limit: int, offset: int) -> tuple[list[dict[str, Any]], int]:
|
||||
def collect_recent_journal_entries_page_sync(
|
||||
*,
|
||||
limit: int,
|
||||
offset: int,
|
||||
cluster_name: str | None = None,
|
||||
) -> tuple[list[dict[str, Any]], int]:
|
||||
"""
|
||||
Страница записей из всех ``clusters/*/journal/jobs_history.json``, новые первыми.
|
||||
Страница записей из ``journal/jobs_history.json``, новые первыми.
|
||||
|
||||
В каждую запись добавляется поле ``source_cluster`` (имя каталога).
|
||||
Возвращает ``(страница записей, общее число записей)``.
|
||||
Если ``cluster_name`` задан — только этот кластер (файл на диске), иначе объединение по всем кластерам.
|
||||
В каждую запись добавляется поле ``source_cluster``.
|
||||
"""
|
||||
lim = max(1, min(limit, 100))
|
||||
off = max(0, offset)
|
||||
|
||||
if cluster_name and str(cluster_name).strip():
|
||||
n = str(cluster_name).strip()
|
||||
if not validate_cluster_name(n):
|
||||
return [], 0
|
||||
raw = read_cluster_journal_sync(n)
|
||||
if not raw or not isinstance(raw.get("entries"), list):
|
||||
return [], 0
|
||||
entries = [e for e in raw["entries"] if isinstance(e, dict)]
|
||||
|
||||
def _entry_time_key(e: dict[str, Any]) -> str:
|
||||
return str(e.get("finished_at_utc") or e.get("created_at_utc") or "")
|
||||
|
||||
entries.sort(key=_entry_time_key, reverse=True)
|
||||
total = len(entries)
|
||||
chunk = entries[off : off + lim]
|
||||
out: list[dict[str, Any]] = []
|
||||
for e in chunk:
|
||||
row = dict(e)
|
||||
row["source_cluster"] = n
|
||||
out.append(row)
|
||||
return out, total
|
||||
|
||||
flat = _collect_all_journal_rows_sorted_sync()
|
||||
total = len(flat)
|
||||
chunk = flat[off : off + lim]
|
||||
out: list[dict[str, Any]] = []
|
||||
out2: list[dict[str, Any]] = []
|
||||
for cluster_dir_name, e in chunk:
|
||||
row = dict(e)
|
||||
row["source_cluster"] = cluster_dir_name
|
||||
out.append(row)
|
||||
return out, total
|
||||
out2.append(row)
|
||||
return out2, total
|
||||
|
||||
Reference in New Issue
Block a user