feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile

- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
Сергей Антропов
2026-02-15 22:59:02 +03:00
parent 23e1a6037b
commit 1fbf9185a2
232 changed files with 38075 additions and 5 deletions

210
app/templates/base.html Normal file
View File

@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}DevOpsLab{% endblock %}</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/static/css/main.css" />
<link rel="stylesheet" href="/static/css/forms.css" />
<link rel="stylesheet" href="/static/css/buttons.css" />
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<!-- CodeMirror для редактора кода -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/yaml/yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/dockerfile/dockerfile.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/shell/shell.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/matchbrackets.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/closebrackets.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/fold/foldcode.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/fold/foldgutter.min.js"></script>
<!-- js-yaml для валидации YAML -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
{% block head %}{% endblock %}
</head>
<body class="{% if not hide_sidebar %}with-sidebar{% endif %}" id="layout-body">
{% if not hide_sidebar %}
<div class="sidebar-overlay" id="sidebar-overlay" aria-hidden="true"></div>
{% include "components/sidebar.html" %}
{% endif %}
<div class="main-wrapper">
<header class="content-header">
<div class="content-header-inner">
<button type="button" class="sidebar-toggle-btn" id="sidebar-toggle-btn" aria-label="Открыть меню" title="Открыть меню">
<i class="fas fa-bars" aria-hidden="true"></i>
</button>
<h1 class="page-title mb-0">{% block page_title %}{% endblock %}</h1>
<div class="header-actions">
{% block header_actions %}{% endblock %}
</div>
</div>
</header>
<main class="main-content">
{% block content %}{% endblock %}
</main>
</div>
<!-- Универсальное модальное окно для сообщений -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" id="messageModalHeader">
<h5 class="modal-title" id="messageModalLabel">
<i class="fas fa-info-circle me-2" id="messageModalIcon"></i>
<span id="messageModalTitle">Сообщение</span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body" id="messageModalBody">
<p id="messageModalText"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<!-- Модальное окно для подтверждения действий -->
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning">
<h5 class="modal-title" id="confirmModalLabel">
<i class="fas fa-exclamation-triangle me-2"></i>
Подтверждение действия
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<p id="confirmModalText">Вы уверены, что хотите выполнить это действие?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="confirmModalCancel">
<i class="fas fa-times me-2"></i>Отмена
</button>
<button type="button" class="btn btn-danger" id="confirmModalConfirm">
<i class="fas fa-check me-2"></i>Подтвердить
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<script src="/static/js/app.js"></script>
<script src="/static/js/editor.js"></script>
{% block scripts %}{% endblock %}
<script>
// Sidebar toggle functionality
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebar-toggle-btn');
const sidebarOverlay = document.getElementById('sidebar-overlay');
const sidebarCollapse = document.getElementById('sidebar-collapse-btn');
if (sidebarToggle) {
sidebarToggle.addEventListener('click', function() {
sidebar.classList.toggle('open');
sidebarOverlay.classList.toggle('visible');
});
}
if (sidebarOverlay) {
sidebarOverlay.addEventListener('click', function() {
sidebar.classList.remove('open');
sidebarOverlay.classList.remove('visible');
});
}
// Обработчик сворачивания sidebar полностью в app.js
// Не добавляем обработчик здесь, чтобы избежать конфликтов
});
// Dropdown menu toggle
function toggleToolsMenu(event) {
event.preventDefault();
const menu = document.getElementById('tools-menu');
menu.classList.toggle('open');
}
function toggleProfileMenu(event) {
event.preventDefault();
const menu = document.getElementById('profile-menu');
menu.classList.toggle('open');
}
function toggleSettingsMenu(event) {
event.preventDefault();
const menu = document.getElementById('settings-menu');
menu.classList.toggle('open');
}
/**
* Показывает модальное окно подтверждения вместо стандартного confirm()
* @param {string} message - Текст сообщения для подтверждения
* @param {string} title - Заголовок модального окна (опционально)
* @returns {Promise<boolean>} - Promise, который разрешается в true при подтверждении, false при отмене
*
* Автор: Сергей Антропов
* Сайт: https://devops.org.ru
*/
function showConfirmModal(message, title = 'Подтверждение действия') {
return new Promise((resolve) => {
const modal = document.getElementById('confirmModal');
const modalText = document.getElementById('confirmModalText');
const modalTitle = document.getElementById('confirmModalLabel');
const confirmBtn = document.getElementById('confirmModalConfirm');
const cancelBtn = document.getElementById('confirmModalCancel');
const bsModal = new bootstrap.Modal(modal);
// Устанавливаем текст сообщения
modalText.textContent = message;
// Устанавливаем заголовок
modalTitle.innerHTML = `<i class="fas fa-exclamation-triangle me-2"></i>${title}`;
// Обработчик подтверждения
const handleConfirm = () => {
bsModal.hide();
resolve(true);
// Удаляем обработчики после использования
confirmBtn.removeEventListener('click', handleConfirm);
cancelBtn.removeEventListener('click', handleCancel);
modal.removeEventListener('hidden.bs.modal', handleCancel);
};
// Обработчик отмены
const handleCancel = () => {
bsModal.hide();
resolve(false);
// Удаляем обработчики после использования
confirmBtn.removeEventListener('click', handleConfirm);
cancelBtn.removeEventListener('click', handleCancel);
modal.removeEventListener('hidden.bs.modal', handleCancel);
};
// Добавляем обработчики событий
confirmBtn.addEventListener('click', handleConfirm);
cancelBtn.addEventListener('click', handleCancel);
modal.addEventListener('hidden.bs.modal', handleCancel);
// Показываем модальное окно
bsModal.show();
});
}
</script>
</body>
</html>