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

310 lines
12 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 %}Деплой {{ 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="deployRunner()">
<!-- Предупреждение -->
<div class="alert alert-warning mb-3" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>⚠️ ВНИМАНИЕ:</strong> Вы собираетесь изменить реальные серверы! Убедитесь, что вы понимаете последствия.
</div>
<!-- Проверка наличия необходимых файлов -->
{% if not inventory_exists %}
<div class="alert alert-danger mb-3" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>❌ Inventory файл не найден!</strong>
<p class="mb-0 mt-2">Создайте файл <code>inventory/hosts.ini</code> с вашими серверами или используйте <a href="/deploy/inventory" class="alert-link">редактор inventory</a>.</p>
</div>
{% endif %}
{% if not deploy_playbook_exists %}
<div class="alert alert-danger mb-3" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>❌ Playbook deploy.yml не найден!</strong>
<p class="mb-0 mt-2">Создайте файл <code>roles/deploy.yml</code> для развертывания ролей.</p>
</div>
{% endif %}
<!-- Настройки деплоя -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Настройки деплоя</h5>
</div>
<div class="card-body">
<form @submit.prevent="startDeploy">
<div class="mb-3">
<label class="form-label">Inventory файл</label>
<input
type="text"
x-model="deployConfig.inventory"
value="inventory/hosts.ini"
class="form-control"
placeholder="inventory/hosts.ini"
>
<div class="form-text">
Путь к inventory файлу относительно корня проекта
</div>
</div>
<div class="mb-3">
<label class="form-label">Limit (опционально)</label>
<input
type="text"
x-model="deployConfig.limit"
class="form-control"
placeholder="webservers или host1,host2"
>
<div class="form-text">
Ограничение на хосты для деплоя
</div>
</div>
<div class="mb-3">
<label class="form-label">Tags (опционально)</label>
<input
type="text"
x-model="deployConfig.tags"
class="form-control"
placeholder="web,database или оставьте пустым для всех тегов"
>
<div class="form-text">
Теги для фильтрации задач (через запятую)
</div>
</div>
<div class="mb-3">
<label class="form-label">Дополнительные переменные (JSON, опционально)</label>
<textarea
x-model="deployConfig.extra_vars"
rows="3"
class="form-control font-monospace"
placeholder='{"app_version": "1.0.0", "nginx_enabled": true}'
></textarea>
<div class="form-text">
Укажите переменные в формате JSON
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
x-model="deployConfig.check"
id="deploy-check"
>
<label class="form-check-label" for="deploy-check">
Dry-run режим (--check) - изменения не будут применены
</label>
</div>
</div>
<button
type="submit"
class="btn btn-primary"
:disabled="deployRunning || !inventoryExists || !deployPlaybookExists"
>
<i class="fas fa-rocket me-2" x-show="!deployRunning"></i>
<i class="fas fa-spinner fa-spin me-2" x-show="deployRunning"></i>
<span x-show="!deployRunning">Запустить деплой</span>
<span x-show="deployRunning">Деплой выполняется...</span>
</button>
</form>
</div>
</div>
<!-- Live логи -->
<div class="card" x-show="deployRunning || logs.length > 0">
<div class="card-header">
<h5 class="mb-0">Логи деплоя</h5>
</div>
<div class="card-body">
<div class="log-container" id="deploy-logs" x-ref="logContainer" style="max-height: 500px; overflow-y: auto; background: #1e1e1e; color: #d4d4d4; padding: 1rem; border-radius: 0.25rem; font-family: 'Courier New', monospace; font-size: 0.875rem;">
<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',
'log-success': log.type === 'success'
}"
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 deployRunner() {
return {
deployRunning: false,
logs: [],
deployConfig: {
inventory: 'inventory/hosts.ini',
limit: '',
tags: '{{ role_name }}',
extra_vars: '',
check: false
},
inventoryExists: {{ 'true' if inventory_exists else 'false' }},
deployPlaybookExists: {{ 'true' if deploy_playbook_exists else 'false' }},
ws: null,
async startDeploy() {
if (!this.inventoryExists || !this.deployPlaybookExists) {
alert('❌ Необходимые файлы не найдены!');
return;
}
this.deployRunning = true;
this.logs = [];
// Формируем deploy_id
const deployId = `deploy-{{ role_name }}-${this.deployConfig.tags || 'none'}`;
// Создание WebSocket подключения
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
this.ws = new WebSocket(`${protocol}//${window.location.host}/ws/deploy/${deployId}`);
this.ws.onopen = () => {
// Отправляем параметры деплоя
this.ws.send(JSON.stringify({
type: 'start',
role_name: '{{ role_name }}',
inventory: this.deployConfig.inventory,
limit: this.deployConfig.limit || null,
tags: this.deployConfig.tags ? this.deployConfig.tags.split(',').map(t => t.trim()) : null,
check: this.deployConfig.check,
extra_vars: this.deployConfig.extra_vars ? JSON.parse(this.deployConfig.extra_vars) : null
}));
};
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.deployRunning = false;
this.logs.push({
id: Date.now(),
type: data.status === 'success' ? 'success' : 'error',
data: data.data || `✅ Деплой завершен: ${data.status}`
});
this.ws.close();
} else if (data.type === 'error') {
this.deployRunning = false;
this.logs.push({
id: Date.now(),
type: 'error',
data: data.data || `❌ Ошибка`
});
this.ws.close();
}
};
this.ws.onerror = (error) => {
this.deployRunning = false;
this.logs.push({
id: Date.now(),
type: 'error',
data: `❌ Ошибка подключения: ${error}`
});
};
this.ws.onclose = () => {
this.deployRunning = false;
};
},
detectLogLevel(line) {
const lower = line.toLowerCase();
if (lower.includes('error') || lower.includes('failed') || lower.includes('fatal')) return 'error';
if (lower.includes('warning') || lower.includes('warn')) return 'warning';
if (lower.includes('changed') || lower.includes('ok') || lower.includes('success')) return 'success';
if (lower.includes('skipping') || lower.includes('ok')) return 'info';
return 'info';
},
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 = `deploy-{{ role_name }}-${Date.now()}.log`;
a.click();
URL.revokeObjectURL(url);
}
}
}
</script>
<style>
.log-line {
margin: 0;
padding: 0.25rem 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.log-error {
color: #f48771;
}
.log-warning {
color: #dcdcaa;
}
.log-info {
color: #569cd6;
}
.log-success {
color: #4ec9b0;
}
</style>
{% endblock %}