Files
DevOpsLab/app/templates/pages/presets/detail.html
Сергей Антропов 1fbf9185a2 feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
2026-02-15 22:59:02 +03:00

269 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}{{ preset.name }} - DevOpsLab{% endblock %}
{% block page_title %}Preset: {{ preset.name }}{% endblock %}
{% block header_actions %}
<div class="btn-group">
<button
type="button"
class="btn btn-success btn-sm"
onclick="startPresetTest()"
title="Запустить тест preset'а"
>
<i class="fas fa-play me-2"></i>
Запустить
</button>
<button
type="button"
class="btn btn-warning btn-sm"
onclick="stopPresetTest()"
title="Остановить тест"
id="stop-btn"
style="display: none;"
>
<i class="fas fa-stop me-2"></i>
Остановить
</button>
<button
type="button"
class="btn btn-info btn-sm"
onclick="restartPresetTest()"
title="Перезапустить тест"
id="restart-btn"
style="display: none;"
>
<i class="fas fa-redo me-2"></i>
Перезапустить
</button>
</div>
<a href="/presets/{{ preset.name }}/edit?category={{ preset.category }}" class="btn btn-primary btn-sm">
<i class="fas fa-edit me-2"></i>
Редактировать
</a>
<a href="/presets" 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 col-lg-8">
<!-- Информация о preset'е -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Информация о preset'е</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<strong>Имя:</strong> {{ preset.name }}<br>
<strong>Категория:</strong>
<span class="badge bg-{% if preset.category == 'k8s' %}info{% else %}primary{% endif %}">
{{ preset.category }}
</span>
</div>
<div class="col-md-6">
{% if preset.data %}
<strong>Docker сеть:</strong> {{ preset.data.get('docker_network', 'labnet') }}<br>
<strong>Хостов:</strong> {{ preset.data.get('hosts', [])|length }}
{% endif %}
</div>
</div>
{% if preset.data and preset.data.get('description') %}
<div class="mb-3">
<strong>Описание:</strong>
<p class="text-muted mb-0">{{ preset.data.description }}</p>
</div>
{% endif %}
{% if preset.data and preset.data.get('hosts') %}
<div class="mb-3">
<strong>Хосты:</strong>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Имя</th>
<th>Семейство</th>
<th>Группы</th>
<th>Тип</th>
</tr>
</thead>
<tbody>
{% for host in preset.data.hosts %}
<tr>
<td>{{ host.name }}</td>
<td>
<span class="badge bg-secondary">{{ host.family }}</span>
</td>
<td>
{% if host.groups %}
{% for group in host.groups %}
<span class="badge bg-info me-1">{{ group }}</span>
{% endfor %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td>{{ host.type or '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% if preset.data and preset.data.get('images') %}
<div class="mb-3">
<strong>Docker образы:</strong>
<div class="d-flex flex-wrap gap-2 mt-2">
{% for key, value in preset.data.images.items() %}
<span class="badge bg-success">
<i class="fas fa-cube me-1"></i>
{{ key }}: {{ value.split(':')[-1] if ':' in value else value }}
</span>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
<!-- YAML содержимое -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">YAML содержимое</h5>
</div>
<div class="card-body p-0">
<pre class="mb-0" style="background: #1e1e1e; color: #d4d4d4; padding: 1rem; margin: 0; overflow-x: auto;"><code>{{ preset.content }}</code></pre>
</div>
</div>
</div>
<div class="col-12 col-lg-4">
<!-- Логи тестирования -->
<div class="card" id="test-logs-card" style="display: none;">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Логи тестирования</h5>
<button
type="button"
class="btn btn-sm btn-outline-light"
onclick="clearTestLogs()"
title="Очистить логи"
>
<i class="fas fa-trash"></i>
</button>
</div>
<div class="card-body p-0">
<div class="log-container" id="test-logs" style="height: 600px; overflow-y: auto; background: #1e1e1e; color: #d4d4d4; padding: 1rem; font-family: 'Courier New', monospace; font-size: 0.75rem;">
<!-- Логи будут добавлены через WebSocket -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let testWebSocket = null;
let testRunning = false;
function startPresetTest() {
if (testRunning) {
alert('Тест уже запущен');
return;
}
// Показываем логи
const logsCard = document.getElementById('test-logs-card');
const logsContainer = document.getElementById('test-logs');
logsCard.style.display = 'block';
logsContainer.innerHTML = '';
// Показываем кнопки управления
document.getElementById('stop-btn').style.display = 'inline-block';
document.getElementById('restart-btn').style.display = 'inline-block';
testRunning = true;
// Создаем WebSocket подключение
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/preset/test/{{ preset.name }}?category={{ preset.category }}`);
testWebSocket = ws;
ws.onopen = () => {
ws.send(JSON.stringify({
action: 'start',
preset_name: '{{ preset.name }}',
preset_category: '{{ preset.category }}'
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'log' || data.type === 'info') {
const logLine = document.createElement('div');
logLine.className = `log-line log-${data.level || 'info'}`;
logLine.textContent = data.data;
logsContainer.appendChild(logLine);
logsContainer.scrollTop = logsContainer.scrollHeight;
} else if (data.type === 'error') {
const errorLine = document.createElement('div');
errorLine.className = 'log-line log-error';
errorLine.textContent = data.data;
logsContainer.appendChild(errorLine);
} else if (data.type === 'complete') {
const completeLine = document.createElement('div');
completeLine.className = 'log-line log-info';
completeLine.textContent = data.data || '✅ Тестирование завершено';
logsContainer.appendChild(completeLine);
testRunning = false;
document.getElementById('stop-btn').style.display = 'none';
document.getElementById('restart-btn').style.display = 'none';
ws.close();
}
};
ws.onerror = (error) => {
const errorLine = document.createElement('div');
errorLine.className = 'log-line log-error';
errorLine.textContent = `❌ Ошибка подключения: ${error}`;
logsContainer.appendChild(errorLine);
testRunning = false;
};
ws.onclose = () => {
testRunning = false;
console.log('WebSocket закрыт');
};
}
function stopPresetTest() {
if (testWebSocket && testWebSocket.readyState === WebSocket.OPEN) {
testWebSocket.send(JSON.stringify({ action: 'stop' }));
testWebSocket.close();
}
testRunning = false;
document.getElementById('stop-btn').style.display = 'none';
document.getElementById('restart-btn').style.display = 'none';
}
function restartPresetTest() {
stopPresetTest();
setTimeout(() => {
startPresetTest();
}, 1000);
}
function clearTestLogs() {
document.getElementById('test-logs').innerHTML = '';
}
</script>
{% endblock %}