- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
8.2 KiB
8.2 KiB
Быстрый старт веб-интерфейса DevOpsLab
Автор: Сергей Антропов
Сайт: https://devops.org.ru
🚀 Быстрый старт
1. Создание git ветки
git checkout -b feature/web-interface
git push -u origin feature/web-interface
2. Структура проекта
app/
├── main.py # FastAPI приложение
├── api/v1/endpoints/ # API endpoints
├── templates/ # HTMX шаблоны
├── static/ # CSS, JS
└── core/ # Ядро (MakeExecutor, DockerClient)
3. Основные компоненты
MakeExecutor - выполнение Makefile команд
# app/core/make_executor.py
import subprocess
from typing import Dict, List
class MakeExecutor:
def execute(self, command: str, args: List[str] = None) -> Dict:
cmd = ["make"] + command.split() + (args or [])
result = subprocess.run(cmd, capture_output=True, text=True)
return {
"success": result.returncode == 0,
"stdout": result.stdout,
"stderr": result.stderr
}
FastAPI роуты
# app/api/v1/endpoints/roles.py
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from app.core.make_executor import MakeExecutor
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
executor = MakeExecutor()
@router.get("/roles", response_class=HTMLResponse)
async def list_roles(request: Request):
# Получаем список ролей через make
result = executor.execute("role list")
roles = parse_roles(result["stdout"])
return templates.TemplateResponse(
"pages/roles/list.html",
{"request": request, "roles": roles}
)
@router.post("/roles/{role_name}/test")
async def test_role(role_name: str, preset: str = "default"):
# Запуск теста
result = executor.execute(f"role test {preset}")
return {"status": "success" if result["success"] else "failed"}
HTMX шаблон
<!-- app/templates/pages/roles/list.html -->
{% extends "base.html" %}
{% block content %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% for role in roles %}
<div class="card">
<h3>{{ role.name }}</h3>
<p>{{ role.description }}</p>
<button
hx-post="/api/v1/roles/{{ role.name }}/test"
hx-target="#test-results"
class="btn-primary">
Запустить тест
</button>
</div>
{% endfor %}
</div>
{% endblock %}
4. Примеры страниц
Dashboard
<!-- app/templates/pages/dashboard.html -->
<div class="dashboard">
<div class="stats">
<div class="stat-card">
<h3>Всего ролей</h3>
<p hx-get="/api/v1/stats/roles" hx-trigger="every 5s">
{{ total_roles }}
</p>
</div>
</div>
<div class="recent-tests">
<h2>Последние тесты</h2>
<div id="test-list"
hx-get="/api/v1/tests/recent"
hx-trigger="every 10s">
<!-- Загружается через HTMX -->
</div>
</div>
</div>
Создание роли
<!-- app/templates/pages/roles/create.html -->
<form hx-post="/api/v1/roles/create" hx-target="#result">
<div class="step" id="step-1">
<h3>Шаг 1: Базовая информация</h3>
<input name="name" placeholder="Имя роли" required>
<textarea name="description" placeholder="Описание"></textarea>
<select name="template">
<option value="service">Service</option>
<option value="package">Package</option>
<option value="config">Config</option>
</select>
<button type="button" hx-get="/api/v1/roles/create/step/2">
Далее
</button>
</div>
<div class="step" id="step-2" style="display:none;">
<h3>Шаг 2: Переменные</h3>
<div id="variables">
<div class="variable-row">
<input name="var_name" placeholder="Имя переменной">
<input name="var_value" placeholder="Значение">
<select name="var_type">
<option value="string">String</option>
<option value="int">Integer</option>
<option value="bool">Boolean</option>
</select>
</div>
</div>
<button type="button" hx-post="/api/v1/roles/create/add-variable">
Добавить переменную
</button>
<button type="submit">Создать роль</button>
</div>
</form>
5. WebSocket для live логов
# app/api/v1/endpoints/websocket.py
from fastapi import WebSocket
from app.core.make_executor import MakeExecutor
@router.websocket("/ws/test/{test_id}")
async def test_websocket(websocket: WebSocket, test_id: str):
await websocket.accept()
executor = MakeExecutor()
# Запуск теста с потоковым выводом
process = await executor.execute_stream(f"role test {test_id}")
async for line in process.stdout:
await websocket.send_json({
"type": "log",
"data": line.decode()
})
await websocket.send_json({"type": "complete"})
// app/static/js/test-live.js
const ws = new WebSocket('ws://localhost:8000/ws/test/nginx');
const logContainer = document.getElementById('test-logs');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'log') {
logContainer.innerHTML += `<div>${data.data}</div>`;
}
};
6. Запуск приложения
# app/main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from app.api.v1.endpoints import roles, presets, tests
app = FastAPI(title="DevOpsLab Web Interface")
# Статика
app.mount("/static", StaticFiles(directory="app/static"), name="static")
# Шаблоны
templates = Jinja2Templates(directory="app/templates")
# Роуты
app.include_router(roles.router, prefix="/api/v1")
app.include_router(presets.router, prefix="/api/v1")
app.include_router(tests.router, prefix="/api/v1")
@app.get("/")
async def dashboard(request: Request):
return templates.TemplateResponse("pages/dashboard.html", {"request": request})
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
7. Docker Compose для разработки
# docker-compose.dev.yml
version: '3.8'
services:
web:
build: ./app
ports:
- "8000:8000"
volumes:
- ./app:/app
- .:/workspace
environment:
- PROJECT_ROOT=/workspace
command: uvicorn main:app --reload --host 0.0.0.0
redis:
image: redis:7-alpine
ports:
- "6379:6379"
celery:
build: ./app
command: celery -A tasks.celery_tasks worker --loglevel=info
volumes:
- ./app:/app
- .:/workspace
depends_on:
- redis
8. Команды для разработки
# Установка зависимостей
cd app
pip install -r requirements.txt
# Запуск в режиме разработки
uvicorn main:app --reload
# Запуск через Docker
docker-compose -f docker-compose.dev.yml up
# Запуск Celery worker
celery -A tasks.celery_tasks worker --loglevel=info
📋 Чек-лист реализации
- Создать git ветку
- Настроить структуру проекта
- Реализовать MakeExecutor
- Создать базовые шаблоны
- Реализовать Dashboard
- Реализовать список ролей
- Реализовать создание роли
- Реализовать тестирование роли
- Добавить WebSocket для live логов
- Настроить Celery для фоновых задач
- Добавить базу данных для истории
- Улучшить UI/UX
- Добавить аутентификацию
- Написать документацию
Полная версия: WEB_INTERFACE_PROPOSAL.md