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

View File

@@ -0,0 +1,111 @@
{% extends "base.html" %}
{% block title %}Смена пароля - DevOpsLab{% endblock %}
{% block content %}
<div class="login-container">
<div class="login-card">
<div class="card-header">
<i class="fas fa-key fa-3x mb-3"></i>
<h1>Смена пароля</h1>
<p class="text-muted">Измените пароль для пользователя {{ current_user.username }}</p>
</div>
<div class="card-body">
<form
hx-post="/api/v1/auth/change-password"
hx-target="#change-password-result"
hx-swap="innerHTML"
class="login-form"
>
<div class="form-group">
<label class="form-label">Текущий пароль</label>
<input
type="password"
name="current_password"
class="form-control"
placeholder="••••••••"
required
autofocus
>
</div>
<div class="form-group">
<label class="form-label">Новый пароль</label>
<input
type="password"
name="new_password"
class="form-control"
placeholder="••••••••"
required
minlength="6"
>
</div>
<div class="form-group">
<label class="form-label">Подтвердите новый пароль</label>
<input
type="password"
name="new_password_confirm"
class="form-control"
placeholder="••••••••"
required
minlength="6"
>
</div>
<div id="change-password-result" class="mt-3"></div>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-save me-2"></i>
Изменить пароль
</button>
</form>
<div class="mt-4 text-center">
<a href="/" class="btn btn-link">
<i class="fas fa-arrow-left me-2"></i>
Назад
</a>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('.login-form');
const newPassword = form.querySelector('input[name="new_password"]');
const newPasswordConfirm = form.querySelector('input[name="new_password_confirm"]');
// Проверка совпадения паролей
function validatePasswords() {
if (newPassword.value !== newPasswordConfirm.value) {
newPasswordConfirm.setCustomValidity('Пароли не совпадают');
} else {
newPasswordConfirm.setCustomValidity('');
}
}
newPassword.addEventListener('input', validatePasswords);
newPasswordConfirm.addEventListener('input', validatePasswords);
form.addEventListener('htmx:afterRequest', function(event) {
if (event.detail.xhr.status === 200) {
const resultDiv = document.getElementById('change-password-result');
resultDiv.innerHTML = '<div class="alert alert-success mt-3">Пароль успешно изменен</div>';
setTimeout(() => {
window.location.href = '/';
}, 2000);
} else {
const resultDiv = document.getElementById('change-password-result');
try {
const response = JSON.parse(event.detail.xhr.responseText);
resultDiv.innerHTML = `<div class="alert alert-danger mt-3">${response.detail || 'Ошибка при смене пароля'}</div>`;
} catch (e) {
resultDiv.innerHTML = '<div class="alert alert-danger mt-3">Ошибка при смене пароля</div>';
}
}
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}Вход - DevOpsLab{% endblock %}
{% block content %}
<div class="login-container">
<div class="login-card">
<div class="card-header">
<i class="fas fa-cogs fa-3x mb-3"></i>
</div>
<div class="card-body">
<form
hx-post="/api/v1/auth/login"
hx-target="#login-result"
hx-swap="innerHTML"
class="login-form"
>
<div class="form-group">
<label class="form-label">Имя пользователя</label>
<input
type="text"
name="username"
class="form-control"
placeholder="admin"
required
autofocus
>
</div>
<div class="form-group">
<label class="form-label">Пароль</label>
<input
type="password"
name="password"
class="form-control"
placeholder="••••••••"
required
>
</div>
<div id="login-result" class="mt-3"></div>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-sign-in-alt me-2"></i>
Войти
</button>
</form>
<div class="mt-4 text-center">
<p class="text-muted small">
По умолчанию: <strong>admin</strong> / <strong>admin</strong>
</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Проверяем, истекла ли сессия
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('expired') === '1') {
const resultDiv = document.getElementById('login-result');
resultDiv.innerHTML = '<div class="alert alert-warning mt-3"><i class="fas fa-exclamation-triangle me-2"></i>Ваша сессия истекла. Пожалуйста, войдите снова.</div>';
}
const form = document.querySelector('.login-form');
form.addEventListener('htmx:afterRequest', function(event) {
if (event.detail.xhr.status === 200) {
const response = JSON.parse(event.detail.xhr.responseText);
// Токен сохраняется в cookie автоматически сервером
// Перенаправление на главную
window.location.href = '/';
} else {
// Показываем ошибку
const resultDiv = document.getElementById('login-result');
resultDiv.innerHTML = '<div class="alert alert-danger mt-3">Неверное имя пользователя или пароль</div>';
}
});
});
</script>
{% endblock %}