Files
DevOpsLab/app/templates/pages/dockerfiles/all-build-logs.html
Сергей Антропов 1fbf9185a2 feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
2026-02-15 22:59:02 +03:00

353 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Логи сборок - DevOpsLab{% endblock %}
{% block page_title %}Логи сборок{% endblock %}
{% block header_actions %}
<a href="/dockerfiles" class="btn btn-secondary btn-sm">
<i class="fas fa-arrow-left me-2"></i>
Назад к Dockerfile
</a>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-history me-2"></i>
Все логи сборок
</h5>
<div>
<span class="badge bg-info">Всего: {{ total }}</span>
</div>
</div>
<div class="card-body p-0">
{% if logs %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Dockerfile</th>
<th>Образ</th>
<th>Тип</th>
<th>Платформы</th>
<th>Статус</th>
<th>Начало</th>
<th>Длительность</th>
<th>Пользователь</th>
<th style="min-width: 120px;">Действия</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td class="align-middle">
{% if log.dockerfile_id in dockerfiles_map %}
<a href="/dockerfiles/{{ log.dockerfile_id }}" class="text-decoration-none">
<i class="fas fa-file-code me-1 text-primary"></i>
{{ dockerfiles_map[log.dockerfile_id].name }}
</a>
{% else %}
<span class="text-muted">ID: {{ log.dockerfile_id }}</span>
{% endif %}
</td>
<td class="align-middle">
<code>{{ log.image_name }}{% if log.tag %}:{{ log.tag }}{% endif %}</code>
</td>
<td class="align-middle">
{% if log.extra_data and log.extra_data.get('type') == 'push' %}
<span class="badge bg-info">
<i class="fas fa-upload me-1"></i>Push
</span>
{% else %}
<span class="badge bg-secondary">
<i class="fas fa-hammer me-1"></i>Build
</span>
{% endif %}
</td>
<td class="align-middle">
{% if log.extra_data and log.extra_data.get('type') == 'push' %}
{# Для push показываем registry вместо платформ #}
{% if log.extra_data.get('registry') %}
<span class="badge bg-primary">
<i class="fas fa-server me-1"></i>{{ log.extra_data.get('registry') }}
</span>
{% else %}
<span class="text-muted"></span>
{% endif %}
{% elif log.platforms %}
{% for platform in log.platforms %}
<span class="badge bg-secondary me-1">{{ platform }}</span>
{% endfor %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="align-middle">
{% if log.status == "success" %}
<span class="badge bg-success">Успешно</span>
{% elif log.status == "failed" %}
<span class="badge bg-danger">Ошибка</span>
{% else %}
<span class="badge bg-warning">Выполняется</span>
{% endif %}
</td>
<td class="align-middle">
{% if log.started_at %}
<small>{{ log.started_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="align-middle">
{% if log.duration %}
<small>{{ log.duration }} сек</small>
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="align-middle">
{% if log.user %}
<small>{{ log.user }}</small>
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="align-middle">
<div class="btn-group btn-group-sm">
<button
type="button"
class="btn btn-outline-primary"
onclick="showLogDetail({{ log.id }})"
title="Просмотр логов"
>
<i class="fas fa-eye"></i>
</button>
<button
type="button"
class="btn btn-outline-danger"
onclick="deleteLog({{ log.id }})"
title="Удалить лог"
>
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Пагинация -->
{% if total_pages > 1 %}
<div class="card-footer">
<nav aria-label="Навигация по страницам">
<ul class="pagination mb-0 justify-content-center">
{% if page > 1 %}
<li class="page-item">
<a class="page-link" href="?page={{ page - 1 }}&per_page={{ per_page }}">Предыдущая</a>
</li>
{% endif %}
{% for p in range(1, total_pages + 1) %}
{% if p == page %}
<li class="page-item active">
<span class="page-link">{{ p }}</span>
</li>
{% elif p <= 3 or p > total_pages - 3 or (p >= page - 1 and p <= page + 1) %}
<li class="page-item">
<a class="page-link" href="?page={{ p }}&per_page={{ per_page }}">{{ p }}</a>
</li>
{% elif p == 4 or p == total_pages - 3 %}
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% endfor %}
{% if page < total_pages %}
<li class="page-item">
<a class="page-link" href="?page={{ page + 1 }}&per_page={{ per_page }}">Следующая</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<p class="text-muted mb-3">Логи сборок пока нет</p>
</div>
{% endif %}
</div>
</div>
<!-- Модальное окно для просмотра лога -->
<div class="modal fade" id="logDetailModal" tabindex="-1">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Детали лога сборки</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="log-detail-content" class="log-container">
Загрузка...
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
async function showLogDetail(logId) {
const modalElement = document.getElementById('logDetailModal');
const modal = new bootstrap.Modal(modalElement);
const content = document.getElementById('log-detail-content');
content.textContent = 'Загрузка...';
// Функция для установки высоты
const setHeight = () => {
const modalBody = document.querySelector('#logDetailModal .modal-body');
const modalHeader = document.querySelector('#logDetailModal .modal-header');
if (modalBody && modalHeader) {
const headerHeight = modalHeader.offsetHeight;
const availableHeight = window.innerHeight - headerHeight;
modalBody.style.height = availableHeight + 'px';
modalBody.style.minHeight = availableHeight + 'px';
modalBody.style.maxHeight = availableHeight + 'px';
content.style.height = availableHeight + 'px';
content.style.minHeight = availableHeight + 'px';
content.style.maxHeight = availableHeight + 'px';
}
};
// Устанавливаем высоту при открытии модального окна
modalElement.addEventListener('shown.bs.modal', function onShown() {
setHeight();
window.addEventListener('resize', setHeight);
modalElement.removeEventListener('shown.bs.modal', onShown);
});
// Убираем обработчик resize при закрытии
modalElement.addEventListener('hidden.bs.modal', function onHidden() {
window.removeEventListener('resize', setHeight);
modalElement.removeEventListener('hidden.bs.modal', onHidden);
});
modal.show();
try {
const response = await fetch(`/api/v1/dockerfiles/build-logs/${logId}`);
const data = await response.json();
if (data.logs) {
content.textContent = data.logs;
} else {
content.textContent = 'Логи не найдены';
}
} catch (error) {
content.textContent = `Ошибка загрузки: ${error.message}`;
}
}
async function deleteLog(logId) {
const confirmed = await showConfirmModal('Вы уверены, что хотите удалить этот лог сборки?');
if (!confirmed) {
return;
}
try {
const response = await fetch(`/api/v1/dockerfiles/build-logs/${logId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
// Удаляем элемент из DOM
const logElement = document.querySelector(`[onclick*="deleteLog(${logId})"]`).closest('tr');
if (logElement) {
logElement.remove();
}
} else {
const data = await response.json();
alert(`Ошибка при удалении: ${data.detail || 'Неизвестная ошибка'}`);
}
} catch (error) {
alert(`Ошибка при удалении: ${error.message}`);
}
}
</script>
<style>
/* Стили для модального окна с логами на весь экран */
#logDetailModal .modal-dialog {
margin: 0 !important;
max-width: 100% !important;
width: 100% !important;
height: 100vh !important;
max-height: 100vh !important;
}
#logDetailModal .modal-content {
height: 100vh !important;
max-height: 100vh !important;
margin: 0 !important;
border: none !important;
border-radius: 0 !important;
display: flex !important;
flex-direction: column !important;
}
#logDetailModal .modal-header {
flex-shrink: 0 !important;
padding: 1rem !important;
height: auto !important;
}
#logDetailModal .modal-body {
flex: 1 1 0 !important;
overflow: hidden !important;
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
min-height: 0 !important;
max-height: none !important;
height: auto !important;
display: flex !important;
flex-direction: column !important;
position: relative !important;
}
#log-detail-content {
position: absolute !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
min-height: 0 !important;
max-height: none !important;
box-sizing: border-box !important;
overflow: auto !important;
background: #1e1e1e !important;
color: #d4d4d4 !important;
padding: 1rem !important;
font-family: 'Courier New', monospace !important;
font-size: 0.875rem !important;
white-space: pre !important;
word-wrap: normal !important;
overflow-wrap: normal !important;
}
</style>
{% endblock %}