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,196 @@
{% extends "base.html" %}
{% block title %}Inventory - DevOpsLab{% endblock %}
{% block page_title %}Управление Inventory{% endblock %}
{% block header_actions %}
<a href="/deploy" class="btn btn-secondary btn-sm">
<i class="fas fa-arrow-left me-2"></i>
Назад к деплою
</a>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<!-- Информация -->
<div class="alert alert-info mb-3">
<i class="fas fa-info-circle me-2"></i>
<strong>Информация:</strong> Inventory файл определяет серверы для деплоя.
Используйте стандартный формат Ansible inventory (INI или YAML).
</div>
<!-- Редактор inventory -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Редактор inventory/hosts.ini</h5>
</div>
<div class="card-body">
<form
id="inventory-form"
hx-post="/api/v1/deploy/inventory"
hx-target="#result"
hx-swap="innerHTML"
>
<div class="mb-3">
<label class="form-label">Содержимое inventory</label>
<textarea
id="inventory-editor"
name="content"
class="form-control"
rows="20"
placeholder="[webservers]
web1 ansible_host=192.168.1.10 ansible_user=root
web2 ansible_host=192.168.1.11 ansible_user=root
[database]
db1 ansible_host=192.168.1.20 ansible_user=root
[all:vars]
ansible_python_interpreter=/usr/bin/python3"
>{{ inventory_content }}</textarea>
<div class="form-text">
<i class="fas fa-info-circle me-1"></i>
Поддерживается формат INI и YAML. Редактор автоматически определит формат.
</div>
</div>
<!-- Результат -->
<div id="result" class="mb-3"></div>
<!-- Кнопки -->
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-2"></i>
Сохранить Inventory
</button>
<button type="button" class="btn btn-outline-secondary" onclick="validateInventory()">
<i class="fas fa-check me-2"></i>
Проверить синтаксис
</button>
<a href="/deploy" class="btn btn-secondary">
Отмена
</a>
</div>
</form>
</div>
</div>
<!-- Примеры -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">Примеры Inventory</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-12 col-md-6">
<h6 class="fw-semibold mb-2">Простой пример (INI)</h6>
<pre class="bg-dark text-light p-3 rounded"><code>[webservers]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11
[database]
db1 ansible_host=192.168.1.20
[all:vars]
ansible_user=root
ansible_python_interpreter=/usr/bin/python3</code></pre>
</div>
<div class="col-12 col-md-6">
<h6 class="fw-semibold mb-2">С SSH ключами (INI)</h6>
<pre class="bg-dark text-light p-3 rounded"><code>[webservers]
web1 ansible_host=192.168.1.10 ansible_ssh_private_key_file=~/.ssh/id_rsa
web2 ansible_host=192.168.1.11 ansible_ssh_private_key_file=~/.ssh/id_rsa
[database]
db1 ansible_host=192.168.1.20 ansible_ssh_private_key_file=~/.ssh/id_rsa
[all:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3</code></pre>
</div>
<div class="col-12">
<h6 class="fw-semibold mb-2">YAML формат</h6>
<pre class="bg-dark text-light p-3 rounded"><code>all:
children:
webservers:
hosts:
web1:
ansible_host: 192.168.1.10
web2:
ansible_host: 192.168.1.11
database:
hosts:
db1:
ansible_host: 192.168.1.20
vars:
ansible_user: root
ansible_python_interpreter: /usr/bin/python3</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof CodeMirror !== 'undefined') {
const inventoryEditor = CodeMirror.fromTextArea(
document.getElementById('inventory-editor'),
{
mode: 'yaml', // Начинаем с YAML, но можно переключить на INI
theme: 'monokai',
lineNumbers: true,
indentUnit: 2,
lineWrapping: true,
autofocus: true
}
);
// Определяем формат по содержимому
const content = inventoryEditor.getValue();
if (content.trim().startsWith('[') || content.includes('ansible_host=')) {
// Это INI формат
inventoryEditor.setOption('mode', 'ini');
}
// Обновляем textarea перед отправкой формы
document.getElementById('inventory-form').addEventListener('submit', function() {
inventoryEditor.save();
});
// Сохраняем editor в глобальной переменной для доступа из других функций
window.inventoryEditor = inventoryEditor;
}
});
function validateInventory() {
const editor = window.inventoryEditor;
if (!editor) {
alert('Редактор не инициализирован');
return;
}
const content = editor.getValue();
// Простая валидация
if (!content.trim()) {
alert('Inventory пуст');
return;
}
// Проверяем наличие групп
if (content.includes('[') && !content.match(/\[.*\]/)) {
alert('⚠️ Предупреждение: Не найдено групп в формате [group_name]');
return;
}
alert('✅ Синтаксис inventory корректен');
}
</script>
{% endblock %}