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

245 lines
9.8 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 %}Тестирование {{ role_name }} - DevOpsLab{% endblock %}
{% block page_title %}Тестирование роли: {{ role_name }}{% endblock %}
{% block header_actions %}
<a href="/roles/{{ role_name }}" class="btn btn-secondary btn-sm">
<i class="fas fa-arrow-left me-2"></i>
Назад
</a>
{% endblock %}
{% block content %}
<div x-data="testRunner()">
<!-- Настройки теста -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Настройки теста</h5>
</div>
<div class="card-body">
<form @submit.prevent="startTest">
<div class="mb-3">
<label class="form-label">Preset для тестирования</label>
<select
x-model="testConfig.preset"
class="form-select"
>
{% for preset in presets %}
<option value="{{ preset.name }}" data-category="{{ preset.category }}">
{{ preset.name }}
{% if preset.category == 'k8s' %}
<span class="badge bg-info">k8s</span>
{% endif %}
</option>
{% endfor %}
{% if not presets %}
<option value="default">default</option>
{% endif %}
</select>
<div class="form-text">
Выберите preset для тестирования. Preset'ы загружаются из базы данных.
</div>
</div>
<div class="mb-3">
<label class="form-label">Переменные роли (JSON, опционально)</label>
<textarea
x-model="testConfig.variables"
rows="4"
class="form-control font-monospace"
placeholder='{"nginx_version": "1.25.0", "nginx_enabled": true}'
></textarea>
<div class="form-text">
Укажите переменные в формате JSON
</div>
</div>
<div class="mb-3">
<label class="form-label">Опции</label>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
x-model="testConfig.lint"
id="test-lint"
>
<label class="form-check-label" for="test-lint">
Проверка синтаксиса (lint)
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
x-model="testConfig.idempotency"
id="test-idempotency"
>
<label class="form-check-label" for="test-idempotency">
Проверка идемпотентности
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
x-model="testConfig.verbose"
id="test-verbose"
>
<label class="form-check-label" for="test-verbose">
Verbose режим
</label>
</div>
</div>
<button
type="submit"
class="btn btn-success"
:disabled="testRunning"
>
<i class="fas fa-play me-2" x-show="!testRunning"></i>
<i class="fas fa-spinner fa-spin me-2" x-show="testRunning"></i>
<span x-show="!testRunning">Запустить тест</span>
<span x-show="testRunning">Тест выполняется...</span>
</button>
</form>
</div>
</div>
<!-- Live логи -->
<div class="card" x-show="testRunning || logs.length > 0">
<div class="card-header">
<h5 class="mb-0">Логи тестирования</h5>
</div>
<div class="card-body">
<div class="log-container" id="test-logs" x-ref="logContainer" style="max-height: 500px; overflow-y: auto;">
<template x-for="log in logs" :key="log.id">
<div
class="log-line"
:class="{
'log-error': log.type === 'error',
'log-warning': log.type === 'warning',
'log-info': log.type === 'info'
}"
x-text="log.data"
></div>
</template>
</div>
<div class="mt-3 d-flex gap-2">
<button
@click="clearLogs"
class="btn btn-outline-secondary"
>
<i class="fas fa-trash me-2"></i>
Очистить
</button>
<button
@click="downloadLogs"
class="btn btn-outline-secondary"
>
<i class="fas fa-download me-2"></i>
Скачать логи
</button>
</div>
</div>
</div>
</div>
<script>
function testRunner() {
return {
testRunning: false,
logs: [],
testConfig: {
preset: '{{ presets[0].name if presets else "default" }}',
variables: '',
lint: true,
idempotency: false,
verbose: false
},
ws: null,
async startTest() {
this.testRunning = true;
this.logs = [];
// Получаем категорию preset'а из выбранного option
const presetSelect = document.querySelector('select[x-model="testConfig.preset"]');
const selectedOption = presetSelect.options[presetSelect.selectedIndex];
const presetCategory = selectedOption ? (selectedOption.dataset.category || 'main') : 'main';
// Создание WebSocket подключения
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const testId = '{{ role_name }}-' + this.testConfig.preset + '-' + presetCategory;
this.ws = new WebSocket(`${protocol}//${window.location.host}/ws/test/${testId}`);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'log' || data.type === 'info') {
this.logs.push({
id: Date.now() + Math.random(),
type: data.level || this.detectLogLevel(data.data),
data: data.data
});
// Автоскролл
this.$nextTick(() => {
const container = this.$refs.logContainer;
if (container) {
container.scrollTop = container.scrollHeight;
}
});
} else if (data.type === 'complete') {
this.testRunning = false;
this.logs.push({
id: Date.now(),
type: 'info',
data: data.data || `✅ Тест завершен: ${data.status}`
});
this.ws.close();
} else if (data.type === 'error') {
this.testRunning = false;
this.logs.push({
id: Date.now(),
type: 'error',
data: data.data || `❌ Ошибка`
});
this.ws.close();
}
};
this.ws.onerror = (error) => {
this.testRunning = false;
this.logs.push({
id: Date.now(),
type: 'error',
data: `❌ Ошибка подключения: ${error}`
});
};
},
detectLogLevel(line) {
const lower = line.toLowerCase();
if (lower.includes('error') || lower.includes('failed')) return 'error';
if (lower.includes('warning') || lower.includes('warn')) return 'warning';
if (lower.includes('changed') || lower.includes('ok')) return 'info';
return 'debug';
},
clearLogs() {
this.logs = [];
},
downloadLogs() {
const content = this.logs.map(l => l.data).join('\n');
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `test-{{ role_name }}-${Date.now()}.log`;
a.click();
URL.revokeObjectURL(url);
}
}
}
</script>
{% endblock %}