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,227 @@
{% extends "base.html" %}
{% block title %}Preset'ы - DevOpsLab{% endblock %}
{% block page_title %}Preset'ы Molecule{% endblock %}
{% block header_actions %}
<a href="/presets/create" class="btn btn-primary btn-sm">
<i class="fas fa-plus me-2"></i>
Создать preset
</a>
{% endblock %}
{% block content %}
<!-- Поиск и фильтры -->
<div class="card mb-3">
<div class="card-body">
<form method="get" action="/presets" class="row g-3">
<div class="col-12 col-md-6">
<input
type="text"
name="search"
value="{{ search }}"
placeholder="Поиск по имени или описанию..."
class="form-control"
>
</div>
<div class="col-12 col-md-3">
<select name="category" class="form-select">
<option value="">Все категории</option>
<option value="main" {% if category == 'main' %}selected{% endif %}>Основные</option>
<option value="k8s" {% if category == 'k8s' %}selected{% endif %}>Kubernetes</option>
</select>
</div>
<div class="col-12 col-md-3">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-1"></i>
Поиск
</button>
{% if search or category %}
<a href="/presets" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>
Сброс
</a>
{% endif %}
</div>
</div>
</form>
</div>
</div>
<!-- Таблица preset'ов -->
<div class="card mb-3">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">Список preset'ов</h5>
<span class="text-muted small">
Всего: <strong>{{ total }}</strong>
</span>
</div>
</div>
<div class="card-body p-0">
{% if presets %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th style="width: 25%;">Имя</th>
<th style="width: 15%;">Категория</th>
<th style="width: 30%;">Описание</th>
<th style="width: 15%;">Хосты</th>
<th style="width: 15%;">Действия</th>
</tr>
</thead>
<tbody>
{% for preset in presets %}
<tr>
<td>
<a href="/presets/{{ preset.name }}{% if preset.category == 'k8s' %}?category=k8s{% endif %}" class="text-decoration-none fw-semibold">
{{ preset.name }}
</a>
</td>
<td>
{% if preset.category == 'k8s' %}
<span class="badge bg-primary">Kubernetes</span>
{% else %}
<span class="badge bg-secondary">Основной</span>
{% endif %}
</td>
<td>
<span class="text-muted small">
{{ preset.description[:80] if preset.description else "Нет описания" }}{% if preset.description and preset.description|length > 80 %}...{% endif %}
</span>
</td>
<td>
<span class="badge bg-info">{{ preset.hosts_count|default(0) }}</span>
{% if preset.groups %}
<div class="mt-1">
{% for group in preset.groups[:2] %}
<span class="badge bg-light text-dark small">{{ group }}</span>
{% endfor %}
{% if preset.groups|length > 2 %}
<span class="badge bg-light text-dark small">+{{ preset.groups|length - 2 }}</span>
{% endif %}
</div>
{% endif %}
</td>
<td>
<div class="btn-group btn-group-sm">
<a
href="/presets/{{ preset.name }}/edit{% if preset.category == 'k8s' %}?category=k8s{% endif %}"
class="btn btn-outline-primary"
title="Редактировать"
>
<i class="fas fa-edit"></i>
</a>
<button
hx-delete="/api/v1/presets/{{ preset.name }}?category={{ preset.category or 'main' }}"
hx-confirm="Удалить preset '{{ preset.name }}'?"
hx-target="closest tr"
hx-swap="outerHTML"
class="btn btn-outline-danger"
title="Удалить"
>
<i class="fas fa-trash"></i>
</button>
<a
href="/presets/{{ preset.name }}{% if preset.category == 'k8s' %}?category=k8s{% endif %}"
class="btn btn-outline-secondary"
title="Детали"
>
<i class="fas fa-info-circle"></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% 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">Preset'ы не найдены</p>
<a href="/presets/create" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>
Создать первый preset
</a>
</div>
{% endif %}
</div>
{% if presets and total_pages > 1 %}
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-3">
<!-- Пагинация -->
<nav aria-label="Навигация по страницам">
<ul class="pagination mb-0">
{% if page > 1 %}
<li class="page-item">
<a class="page-link" href="?page={{ page - 1 }}{% if search %}&search={{ search }}{% endif %}{% if category %}&category={{ category }}{% endif %}">
<i class="fas fa-chevron-left"></i>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">
<i class="fas fa-chevron-left"></i>
</span>
</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 == 1 or p == total_pages or (p >= page - 2 and p <= page + 2) %}
<li class="page-item">
<a class="page-link" href="?page={{ p }}{% if search %}&search={{ search }}{% endif %}{% if category %}&category={{ category }}{% endif %}">{{ p }}</a>
</li>
{% elif p == page - 3 or p == page + 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 }}{% if search %}&search={{ search }}{% endif %}{% if category %}&category={{ category }}{% endif %}">
<i class="fas fa-chevron-right"></i>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">
<i class="fas fa-chevron-right"></i>
</span>
</li>
{% endif %}
</ul>
</nav>
<!-- Выбор количества на странице -->
<div class="d-flex align-items-center gap-2">
<span class="text-muted small">На странице:</span>
<select
class="form-select form-select-sm pagination-per-page-select"
style="width: auto;"
onchange="window.location.href = '?page=1&per_page=' + this.value + '{% if search %}&search={{ search }}{% endif %}{% if category %}&category={{ category }}{% endif %}'"
>
<option value="10" {% if per_page == 10 %}selected{% endif %}>10</option>
<option value="25" {% if per_page == 25 %}selected{% endif %}>25</option>
<option value="50" {% if per_page == 50 %}selected{% endif %}>50</option>
<option value="100" {% if per_page == 100 %}selected{% endif %}>100</option>
</select>
</div>
<!-- Информация о странице -->
<div class="text-muted small">
Показано {{ ((page - 1) * per_page) + 1 }} - {{ [page * per_page, total]|min }} из {{ total }}
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}