feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
352
app/templates/pages/dockerfiles/all-build-logs.html
Normal file
352
app/templates/pages/dockerfiles/all-build-logs.html
Normal file
@@ -0,0 +1,352 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user