Files
DevOpsLab/app/templates/pages/presets/detail.html
Сергей Антропов d4b0d6f848 Исправление синтаксической ошибки в molecule_executor.py и обновление k8s preset'ов
- Исправлена незакрытая скобка в _build_test_command (строка 745)
- Добавлена поддержка k8s preset'ов: выполнение create_k8s_cluster.py перед create.yml
- Обновлены образы в k8s preset'ах: заменен недоступный ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy на inecs/ansible-lab:ubuntu22-latest
- Обновлены preset'ы в базе данных через SQL
- Обновлены файлы: k8s-single.yml, k8s-multi.yml, k8s-istio-full.yml
2026-02-16 00:31:09 +03:00

447 lines
15 KiB
HTML
Raw Permalink 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 %}
<button
type="button"
class="btn btn-success btn-sm"
onclick="openPresetTestModal()"
title="Запустить тест preset'а"
data-bs-toggle="modal"
data-bs-target="#presetTestModal"
>
<i class="fas fa-play me-2"></i>
Запустить
</button>
<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">
<!-- Информация о 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 w-100">
<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>
<!-- Модальное окно для тестирования preset'а -->
<div class="modal fade" id="presetTestModal" tabindex="-1" aria-labelledby="presetTestModalLabel" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="presetTestModalLabel">
<i class="fas fa-vial me-2"></i>
Тестирование preset'а: {{ preset.name }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть" onclick="stopPresetTest()"></button>
</div>
<div class="modal-body">
<div class="log-container" id="preset-test-logs">
<div class="text-center text-muted py-5">
<i class="fas fa-spinner fa-spin fa-2x mb-3"></i>
<p>Подключение к серверу...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-warning"
onclick="stopPresetTest()"
id="modal-stop-btn"
title="Остановить тест"
>
<i class="fas fa-stop me-2"></i>
Остановить
</button>
<button
type="button"
class="btn btn-info"
onclick="restartPresetTest()"
id="modal-restart-btn"
title="Перезапустить тест"
>
<i class="fas fa-redo me-2"></i>
Перезапустить
</button>
<button
type="button"
class="btn btn-secondary"
onclick="clearPresetTestLogs()"
title="Очистить логи"
>
<i class="fas fa-trash me-2"></i>
Очистить
</button>
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
onclick="stopPresetTest()"
>
Закрыть
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let testWebSocket = null;
let testRunning = false;
let presetTestModal = null;
// Инициализация модального окна
document.addEventListener('DOMContentLoaded', function() {
presetTestModal = new bootstrap.Modal(document.getElementById('presetTestModal'));
// Обработка закрытия модального окна
document.getElementById('presetTestModal').addEventListener('hidden.bs.modal', function() {
stopPresetTest();
});
});
function openPresetTestModal() {
if (testRunning) {
alert('Тест уже запущен');
return;
}
// Очищаем логи
const logsContainer = document.getElementById('preset-test-logs');
logsContainer.innerHTML = '<div class="text-center text-muted py-5"><i class="fas fa-spinner fa-spin fa-2x mb-3"></i><p>Подключение к серверу...</p></div>';
// Показываем модальное окно
presetTestModal.show();
// Запускаем тест
setTimeout(() => {
startPresetTest();
}, 300);
}
function startPresetTest() {
if (testRunning) {
return;
}
const logsContainer = document.getElementById('preset-test-logs');
logsContainer.innerHTML = '';
// Показываем кнопки управления
document.getElementById('modal-stop-btn').disabled = false;
document.getElementById('modal-restart-btn').disabled = false;
testRunning = true;
// Создаем WebSocket подключение
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/preset/test/{{ preset.name }}?category={{ preset.category }}`;
const ws = new WebSocket(wsUrl);
testWebSocket = ws;
ws.onopen = () => {
addLog('info', '🔌 Подключено к серверу');
ws.send(JSON.stringify({
action: 'start',
preset_name: '{{ preset.name }}',
preset_category: '{{ preset.category }}'
}));
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'log' || data.type === 'info') {
addLog(data.level || 'info', data.data);
} else if (data.type === 'error') {
addLog('error', data.data);
} else if (data.type === 'complete') {
addLog('info', data.data || '✅ Тестирование завершено');
testRunning = false;
document.getElementById('modal-stop-btn').disabled = true;
document.getElementById('modal-restart-btn').disabled = false;
ws.close();
}
} catch (e) {
console.error('Ошибка парсинга сообщения:', e);
}
};
ws.onerror = (error) => {
addLog('error', `❌ Ошибка подключения WebSocket`);
testRunning = false;
document.getElementById('modal-stop-btn').disabled = true;
};
ws.onclose = () => {
testRunning = false;
if (testWebSocket === ws) {
testWebSocket = null;
}
};
}
function stopPresetTest() {
if (testWebSocket && testWebSocket.readyState === WebSocket.OPEN) {
testWebSocket.send(JSON.stringify({ action: 'stop' }));
addLog('info', '⏹️ Остановка тестирования...');
}
if (testWebSocket) {
testWebSocket.close();
testWebSocket = null;
}
testRunning = false;
document.getElementById('modal-stop-btn').disabled = true;
document.getElementById('modal-restart-btn').disabled = false;
}
function restartPresetTest() {
stopPresetTest();
const logsContainer = document.getElementById('preset-test-logs');
logsContainer.innerHTML = '';
setTimeout(() => {
startPresetTest();
}, 500);
}
function clearPresetTestLogs() {
const logsContainer = document.getElementById('preset-test-logs');
logsContainer.innerHTML = '';
}
function addLog(level, message) {
const logsContainer = document.getElementById('preset-test-logs');
const logLine = document.createElement('div');
logLine.className = `log-line log-${level}`;
// Цвета для разных уровней логов
const colors = {
'error': '#f48771',
'warning': '#d19a66',
'info': '#61afef',
'success': '#98c379',
'debug': '#abb2bf'
};
logLine.style.color = colors[level] || colors['debug'];
logLine.textContent = message;
logsContainer.appendChild(logLine);
logsContainer.scrollTop = logsContainer.scrollHeight;
}
</script>
<style>
/* Стили для модального окна тестирования preset'а - как у модального окна логов сборки */
#presetTestModal .modal-dialog {
margin: 0 !important;
max-width: 100% !important;
width: 100% !important;
height: 100vh !important;
max-height: 100vh !important;
}
#presetTestModal .modal-content {
height: 100vh !important;
max-height: 100vh !important;
margin: 0 !important;
border: none !important;
border-radius: 0 !important;
display: flex !important;
flex-direction: column !important;
}
#presetTestModal .modal-header {
flex-shrink: 0 !important;
padding: 1rem !important;
height: auto !important;
}
#presetTestModal .modal-body {
flex: 1 1 0 !important;
overflow: hidden !important;
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
min-height: 0 !important;
max-height: none !important;
height: auto !important;
display: flex !important;
flex-direction: column !important;
position: relative !important;
}
#preset-test-logs {
position: absolute !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
min-height: 0 !important;
max-height: none !important;
box-sizing: border-box !important;
overflow: auto !important;
background: #1e1e1e !important;
color: #d4d4d4 !important;
padding: 1rem !important;
font-family: 'Courier New', monospace !important;
font-size: 0.875rem !important;
white-space: pre !important;
word-wrap: normal !important;
overflow-wrap: normal !important;
}
#presetTestModal .modal-footer {
flex-shrink: 0 !important;
padding: 1rem !important;
height: auto !important;
}
/* Дополнительные стили для гарантии полной высоты */
#presetTestModal.show .modal-body,
#presetTestModal.showing .modal-body {
height: calc(100vh - 180px) !important;
min-height: calc(100vh - 180px) !important;
max-height: calc(100vh - 180px) !important;
}
#presetTestModal.show #preset-test-logs,
#presetTestModal.showing #preset-test-logs {
height: calc(100vh - 180px) !important;
min-height: calc(100vh - 180px) !important;
max-height: calc(100vh - 180px) !important;
}
/* Стили для строк логов */
#preset-test-logs .log-line {
margin-bottom: 0.25rem;
line-height: 1.5;
}
#preset-test-logs .log-error {
color: #f48771 !important;
}
#preset-test-logs .log-warning {
color: #d19a66 !important;
}
#preset-test-logs .log-info {
color: #61afef !important;
}
#preset-test-logs .log-success {
color: #98c379 !important;
}
#preset-test-logs .log-debug {
color: #abb2bf !important;
}
</style>
{% endblock %}