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,176 @@
{% extends "base.html" %}
{% block title %}Создать Playbook - DevOpsLab{% endblock %}
{% block page_title %}Создать Playbook{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<form
hx-post="/api/v1/playbooks"
hx-target="#playbook-result"
hx-swap="innerHTML"
id="playbook-form"
>
<div class="mb-3">
<label class="form-label">Название playbook</label>
<input
type="text"
name="name"
class="form-control"
placeholder="my-playbook"
required
pattern="[a-z0-9-]+"
title="Только строчные буквы, цифры и дефисы"
>
<div class="form-text">
Только строчные буквы, цифры и дефисы
</div>
</div>
<div class="mb-3">
<label class="form-label">Описание</label>
<textarea
name="description"
class="form-control"
rows="3"
placeholder="Описание playbook..."
></textarea>
</div>
<div class="mb-3">
<label class="form-label">Роли</label>
<div class="form-text mb-2">
Выберите роли для включения в playbook
</div>
<div class="row g-2">
{% for role in roles %}
<div class="col-12 col-md-6 col-lg-4">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
name="roles"
value="{{ role }}"
id="role-{{ role }}"
>
<label class="form-check-label" for="role-{{ role }}">
{{ role }}
</label>
</div>
</div>
{% endfor %}
</div>
{% if not roles %}
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
Роли не найдены. Создайте роли перед созданием playbook.
</div>
{% endif %}
</div>
<div class="mb-3">
<label class="form-label">Переменные (YAML)</label>
<textarea
name="variables"
id="variables-editor"
class="form-control font-monospace"
rows="10"
placeholder="vars:
key1: value1
key2: value2"
></textarea>
<div class="form-text">
Переменные в формате YAML (опционально)
</div>
</div>
<div class="mb-3">
<label class="form-label">Инвентарь (YAML)</label>
<textarea
name="inventory"
id="inventory-editor"
class="form-control font-monospace"
rows="10"
placeholder="all:
hosts:
host1:
ansible_host: 192.168.1.10
host2:
ansible_host: 192.168.1.11"
></textarea>
<div class="form-text">
Инвентарь в формате YAML (опционально)
</div>
</div>
<div id="playbook-result"></div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-2"></i>
Создать playbook
</button>
<a href="/playbooks" class="btn btn-secondary">
<i class="fas fa-times me-2"></i>
Отмена
</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Инициализация редакторов
if (typeof CodeEditor !== 'undefined') {
const varsEditor = CodeEditor.init('variables-editor', 'yaml');
const invEditor = CodeEditor.init('inventory-editor', 'yaml');
// Валидация при изменении
if (varsEditor) {
varsEditor.on('change', function() {
const content = varsEditor.getValue();
if (content.trim()) {
const validation = CodeEditor.validateYAML(content);
if (!validation.valid) {
CodeEditor.showErrors(varsEditor, validation.errors);
}
}
});
}
if (invEditor) {
invEditor.on('change', function() {
const content = invEditor.getValue();
if (content.trim()) {
const validation = CodeEditor.validateYAML(content);
if (!validation.valid) {
CodeEditor.showErrors(invEditor, validation.errors);
}
}
});
}
}
// Обработка формы
const form = document.getElementById('playbook-form');
form.addEventListener('htmx:afterRequest', function(event) {
if (event.detail.xhr.status === 200) {
const response = JSON.parse(event.detail.xhr.responseText);
window.location.href = `/playbooks/${response.id}`;
} else {
const resultDiv = document.getElementById('playbook-result');
try {
const response = JSON.parse(event.detail.xhr.responseText);
resultDiv.innerHTML = `<div class="alert alert-danger mt-3">${response.detail || 'Ошибка при создании playbook'}</div>`;
} catch (e) {
resultDiv.innerHTML = '<div class="alert alert-danger mt-3">Ошибка при создании playbook</div>';
}
}
});
});
</script>
{% endblock %}