<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPI Mock Service Dashboard</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        .header {
            background: #2c3e50;
            color: white;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        .card {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        .button {
            background: #3498db;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            margin-right: 10px;
            margin-bottom: 10px;
        }
        .button:hover {
            background: #2980b9;
        }
        .button.danger {
            background: #e74c3c;
        }
        .button.danger:hover {
            background: #c0392b;
        }
        .button.success {
            background: #27ae60;
        }
        .button.success:hover {
            background: #229954;
        }
        .metrics {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
        }
        .metric-card {
            background: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            text-align: center;
        }
        .metric-value {
            font-size: 2em;
            font-weight: bold;
            color: #2c3e50;
        }
        .metric-label {
            color: #7f8c8d;
            margin-top: 5px;
        }
        .test-results {
            margin-top: 20px;
        }
        .test-result {
            background: white;
            padding: 15px;
            border-radius: 8px;
            margin-bottom: 10px;
            border-left: 4px solid #3498db;
        }
        .test-result.success {
            border-left-color: #27ae60;
        }
        .test-result.error {
            border-left-color: #e74c3c;
        }
        .status {
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 0.8em;
            font-weight: bold;
        }
        .status.active {
            background: #27ae60;
            color: white;
        }
        .status.inactive {
            background: #95a5a6;
            color: white;
        }
        .controls {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        input[type="text"] {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            width: 200px;
        }
        .endpoints {
            background: white;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        .endpoint {
            background: #ecf0f1;
            padding: 10px;
            margin: 5px 0;
            border-radius: 4px;
            font-family: monospace;
        }
        .endpoint-item {
            background: #ffffff;
            border: 1px solid #e1e8ed;
            border-radius: 8px;
            padding: 15px;
            margin: 10px 0;
            box-shadow: 0 2px 4px rgba(0,0,0,0.05);
        }
        .endpoint-header {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }
        .endpoint-method {
            padding: 4px 12px;
            border-radius: 4px;
            font-weight: bold;
            font-size: 0.9em;
            margin-right: 15px;
            min-width: 70px;
            text-align: center;
        }
        .method-GET {
            background: #2ecc71;
            color: white;
        }
        .method-POST {
            background: #3498db;
            color: white;
        }
        .method-PUT {
            background: #f39c12;
            color: white;
        }
        .method-DELETE {
            background: #e74c3c;
            color: white;
        }
        .method-PATCH {
            background: #9b59b6;
            color: white;
        }
        .endpoint-path {
            font-family: 'Courier New', monospace;
            font-size: 1.1em;
            color: #2c3e50;
            font-weight: bold;
            flex-grow: 1;
        }
        .endpoint-service {
            color: #7f8c8d;
            font-size: 0.9em;
        }
        .endpoint-responses {
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #e1e8ed;
        }
        .endpoint-responses h5 {
            margin: 0 0 8px 0;
            color: #2c3e50;
            font-size: 0.9em;
        }
        .response-code {
            display: inline-block;
            background: #f8f9fa;
            border: 1px solid #e9ecef;
            padding: 4px 8px;
            margin: 2px 4px 2px 0;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-size: 0.8em;
        }
        .response-code.success {
            background: #d4edda;
            border-color: #c3e6cb;
            color: #155724;
        }
        .response-code.error {
            background: #f8d7da;
            border-color: #f5c6cb;
            color: #721c24;
        }
        .log {
            background: #2c3e50;
            color: #ecf0f1;
            padding: 15px;
            border-radius: 8px;
            font-family: monospace;
            font-size: 12px;
            max-height: 300px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-wrap: break-word;
            line-height: 1.4;
        }
        .error-codes {
            margin-top: 15px;
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 15px;
        }
        .error-category {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 6px;
            border-left: 4px solid #3498db;
        }
        .error-code {
            background: white;
            padding: 8px 12px;
            margin-bottom: 5px;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-size: 0.9em;
            border-left: 3px solid #e74c3c;
        }
        .error-code.success {
            border-left-color: #2ecc71;
        }
        .collapsible {
            background: #34495e;
            color: white;
            cursor: pointer;
            padding: 15px;
            border: none;
            text-align: left;
            outline: none;
            font-size: 16px;
            font-weight: bold;
            border-radius: 8px 8px 0 0;
            width: 100%;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .collapsible:hover {
            background: #2c3e50;
        }
        .collapsible.active {
            background: #2c3e50;
        }
        .collapsible-content {
            padding: 0;
            overflow: hidden;
            background: white;
            border-radius: 0 0 8px 8px;
            max-height: 0;
            transition: max-height 0.3s ease-out;
        }
        .collapsible-content.active {
            max-height: 400px;
            padding: 15px;
            overflow-y: auto;
        }
        .chevron {
            font-size: 18px;
            transition: transform 0.3s ease;
        }
        .chevron.down {
            transform: rotate(90deg);
        }
        .test-status-badge {
            display: inline-block;
            padding: 6px 12px;
            border-radius: 15px;
            font-size: 0.85em;
            font-weight: bold;
            margin-left: 10px;
        }
        .test-status-badge.inactive {
            background: #e74c3c;
            color: white;
        }
        .test-status-badge.active {
            background: #27ae60;
            color: white;
        }
        .mock-inactive {
            opacity: 0.6;
            filter: grayscale(50%);
        }
        .mock-active {
            opacity: 1;
            filter: none;
        }
        .endpoint-status {
            display: inline-block;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            margin-right: 10px;
        }
        .endpoint-status.active {
            background: #27ae60;
        }
        .endpoint-status.inactive {
            background: #e74c3c;
        }
        .load-test-features {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 15px;
            margin-top: 15px;
        }
        .feature-card {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #3498db;
        }
        .feature-card h4 {
            margin: 0 0 10px 0;
            color: #2c3e50;
        }
        .feature-card p {
            margin: 0;
            font-size: 0.9em;
            color: #7f8c8d;
        }
        .metrics-chart {
            background: white;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .chart-container {
            position: relative;
            height: 300px;
            width: 100%;
            border: 1px solid #e9ecef;
            border-radius: 4px;
            background: #fafafa;
        }
        .chart-canvas {
            width: 100%;
            height: 100%;
        }
        .chart-placeholder {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 300px;
            background: #f8f9fa;
            border: 2px dashed #dee2e6;
            border-radius: 8px;
            color: #6c757d;
            font-size: 16px;
        }
        .chart-bars {
            display: flex;
            align-items: flex-end;
            height: 200px;
            gap: 10px;
            margin-top: 20px;
        }
        .chart-bar {
            background: linear-gradient(to top, #3498db, #5dade2);
            min-width: 40px;
            border-radius: 4px 4px 0 0;
            display: flex;
            align-items: flex-end;
            justify-content: center;
            color: white;
            font-size: 12px;
            font-weight: bold;
            padding: 5px 0;
            transition: all 0.3s ease;
        }
        .chart-bar:hover {
            background: linear-gradient(to top, #2980b9, #3498db);
            transform: scale(1.05);
        }
        .chart-legend {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-top: 15px;
            flex-wrap: wrap;
        }
        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 0.9em;
        }
        .legend-color {
            width: 16px;
            height: 16px;
            border-radius: 4px;
        }
        .chart-controls {
            margin-bottom: 15px;
            display: flex;
            gap: 10px;
            align-items: center;
        }
        .chart-button {
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            padding: 5px 15px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9em;
            transition: all 0.2s;
        }
        .chart-button:hover {
            background: #e9ecef;
        }
        .chart-button.active {
            background: #3498db;
            color: white;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="header">
        <h1>FastAPI Mock Service Dashboard</h1>
        <p>Библиотечный модуль для mock-сервисов с инфраструктурой нагрузочного тестирования</p>
    </div>

    <div class="card">
        <h2>Управление тестом</h2>
        <div class="controls">
            <input type="text" id="testName" placeholder="Название теста" value="{{ current_test_name }}">
            <button class="button success" onclick="startTest()">Старт теста</button>
            <button class="button danger" onclick="stopTest()">Стоп теста</button>
            <button class="button" onclick="resetMetrics()">Сброс метрик</button>
            <button class="button" onclick="refreshData()">Обновить данные</button>
        </div>
        <div>
            <span class="status" id="testStatus">inactive</span>
            <span id="testInfo">Тест не запущен</span>
            <span class="test-status-badge inactive" id="mockStatus">Моки неактивны</span>
        </div>
    </div>

    <div class="metrics-chart">
        <h3>📊 Реалтайм метрики</h3>
        <p>Визуализация текущих показателей производительности</p>
        <div class="chart-controls">
            <button class="chart-button active" onclick="switchChartView('overview')">Общая статистика</button>
            <button class="chart-button" onclick="switchChartView('endpoints')">По endpoint'ам</button>
            <button class="chart-button" onclick="switchChartView('codes')">По кодам ответов</button>
        </div>
        <div class="chart-container">
            <canvas class="chart-canvas" id="metricsChart"></canvas>
            <div class="chart-placeholder" id="chartPlaceholder" style="display: none;">Запустите тест для просмотра
                метрик
            </div>
        </div>
    </div>

    <div class="card">
        <h2>Возможности для НТ</h2>
        <div class="load-test-features">
            <div class="feature-card">
                <h4> Автоматические метрики</h4>
                <p>Prometheus метрики по кодам ответов, времени отклика и пропускной способности</p>
            </div>
            <div class="feature-card">
                <h4> Сценарии ошибок</h4>
                <p>Настраиваемые коды ошибок для тестирования различных failure scenarios</p>
            </div>
            <div class="feature-card">
                <h4> Реальная статистика</h4>
                <p>Отслеживание RPS, среднего времени ответа и распределения по кодам</p>
            </div>
            <div class="feature-card">
                <h4> Таргетирование</h4>
                <p>Возможность тестировать конкретные endpoint'ы или группы по тегам</p>
            </div>
        </div>
    </div>

    <div class="card">
        <h2>Зарегистрированные Mock Endpoint'ы</h2>
        <button class="collapsible" data-target="endpoints">
            Endpoint'ы ({{ endpoints|length }})
            <span class="chevron">▶</span>
        </button>
        <div class="collapsible-content" id="endpoints">
            <div class="endpoint-list" id="endpointsList">
                {% if endpoints %}
                {% for endpoint in endpoints %}
                <div class="endpoint-item">
                    <div class="endpoint-header">
                        <span class="endpoint-method method-{{ endpoint.method }}">{{ endpoint.method }}</span>
                        <span class="endpoint-path">{{ endpoint.path }}</span>
                        <span class="endpoint-service">{{ endpoint.service }} ({{ endpoint.handler }})</span>
                    </div>
                    <div class="endpoint-responses">
                        <h5>Возможные ответы:</h5>
                        {% for response in endpoint.responses %}
                        <span class="response-code {% if response.code.endswith('.00000') or response.code == '200' %}success{% else %}error{% endif %}">{{ response.code }} - {{ response.description }}</span>
                        {% endfor %}
                    </div>
                </div>
                {% endfor %}
                {% else %}
                <p>Нет зарегистрированных endpoint'ов</p>
                {% endif %}
            </div>
        </div>
    </div>

    <div class="card">
        <h2>Возможные коды ответов</h2>
        <p><strong>Важно:</strong> Все ответы возвращаются с HTTP статусом 200. Ошибки определяются по полю <code>result.code</code>
            в JSON ответе.</p>

        <button class="collapsible" data-target="responseCodes">
            Коды ответов по группам
            <span class="chevron">▶</span>
        </button>
        <div class="collapsible-content" id="responseCodes">
            <div class="error-codes">
                {% if endpoints %}
                {% set grouped_responses = {} %}
                {% for endpoint in endpoints %}
                {% if endpoint.responses %}
                {% for tag in endpoint.tags or ['default'] %}
                {% if grouped_responses.update({tag: grouped_responses.get(tag, []) + endpoint.responses}) %}{% endif %}
                {% endfor %}
                {% endif %}
                {% endfor %}

                {% for tag, responses in grouped_responses.items() %}
                <div class="error-category">
                    <h4>{{ tag.title() }} API</h4>
                    {% set unique_responses = [] %}
                    {% for response in responses %}
                    {% if response not in unique_responses %}
                    {% if unique_responses.append(response) %}{% endif %}
                    <div class="error-code {% if response.code.endswith('.00000') or response.code == '200' %}success{% endif %}">
                        {{ response.code }} - {{ response.description }}
                    </div>
                    {% endif %}
                    {% endfor %}
                </div>
                {% endfor %}
                {% else %}
                <p>Нет зарегистрированных endpoint'ов с определенными кодами ответов.</p>
                {% endif %}
            </div>
        </div>
    </div>

    <div class="metrics" id="metricsContainer">
        <div class="metric-card">
            <div class="metric-value" id="totalRequests">0</div>
            <div class="metric-label">Всего запросов</div>
        </div>
        <div class="metric-card">
            <div class="metric-value" id="avgResponseTime">0.00s</div>
            <div class="metric-label">Среднее время ответа</div>
        </div>
        <div class="metric-card">
            <div class="metric-value" id="topCode">-</div>
            <div class="metric-label">Топ код ответа</div>
        </div>
        <div class="metric-card">
            <div class="metric-value" id="uniqueCodes">0</div>
            <div class="metric-label">Уникальных кодов</div>
        </div>
    </div>

    <div class="card">
        <h2>Распределение по кодам ответов</h2>
        <div id="codeDistribution" style="font-family: monospace; font-size: 14px; line-height: 1.6;">
            Нет данных...
        </div>
    </div>

    <div class="card">
        <h2>История тестов</h2>
        <button class="collapsible" data-target="testResults">
            Результаты тестов ({{ test_results|length }})
            <span class="chevron">▶</span>
        </button>
        <div class="collapsible-content" id="testResults">
            {% for result in test_results %}
            <div class="test-result {% if result.failed_requests == 0 %}success{% else %}error{% endif %}">
                <h4>{{ result.test_name }}</h4>
                <p><strong>Время:</strong> {{ result.start_time.strftime('%Y-%m-%d %H:%M:%S') }} -
                    {% if result.end_time %}{{ result.end_time.strftime('%Y-%m-%d %H:%M:%S') }}{% else %}Активен{% endif
                    %}</p>
                <p><strong>Запросов:</strong> {{ result.total_requests }}</p>
                <p><strong>Среднее время ответа:</strong> {{ "%.3f"|format(result.avg_response_time) }}s</p>
                {% if result.code_distribution %}
                <details>
                    <summary>Детализация по кодам</summary>
                    <pre>{{ result.code_distribution }}</pre>
                </details>
                {% endif %}
                {% if result.summary %}
                <details>
                    <summary>Подробности</summary>
                    <pre>{{ result.summary }}</pre>
                </details>
                {% endif %}
            </div>
            {% endfor %}
        </div>
    </div>

    <div class="card">
        <h2>Лог активности</h2>
        <div class="log" id="logContainer">
            Готов к работе...
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/date-fns@2.28.0/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@2.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>

<script>

    let testActive = false;
    let logMessages = [];
    let metricsChart = null;
    let currentChartView = 'overview';
    let lastMetricsSnapshot = {
        totalRequests: 0,
        avgResponseTime: 0,
        codes: {},
        endpoints: {}
    };
    let chartDataPoints = {
        totalRequests: [],
        responseTime: [],
        codes: {},
        endpoints: {}
    };
    let maxDataPoints = 30; // Максимальное количество точек на графике

    // Функция для управления состоянием моков
    function updateMockStatus(active) {
        const mockStatusBadge = document.getElementById('mockStatus');
        const endpointList = document.getElementById('endpointsList');
        
        if (active) {
            mockStatusBadge.textContent = 'Моки активны';
            mockStatusBadge.className = 'test-status-badge active';
            endpointList.className = 'endpoint-list mock-active';
        } else {
            mockStatusBadge.textContent = 'Моки неактивны';
            mockStatusBadge.className = 'test-status-badge inactive';
            endpointList.className = 'endpoint-list mock-inactive';
        }
    }

    // Функция для получения и отображения UI логов
    async function updateUILogs() {
        try {
            const response = await fetch('/api/ui-logs');
            if (response.ok) {
                const data = await response.json();
                displayUILogs(data.logs);
            }
        } catch (error) {
            console.error('Error fetching UI logs:', error);
        }
    }

    function displayUILogs(logs) {
        const logContainer = document.getElementById('logContainer');

        // Начинаем с системных логов
        let combinedLogs = [...logMessages];

        if (logs && logs.length > 0) {
            // Добавляем разделитель только если есть системные логи
            if (combinedLogs.length > 0) {
                combinedLogs.push('');
                combinedLogs.push('=== MOCK ЗАПРОСЫ ===');
            }

            // Добавляем UI логи с улучшенным форматированием
            logs.forEach(log => {
                const typeIcon = {
                    'request': '📥',
                    'response': '📤',
                    'error': '❌',
                    'system': '⚙️'
                }[log.type] || 'ℹ️';

                combinedLogs.push(`[${log.timestamp}] ${typeIcon} ${log.message}`);
            });
        }

        logContainer.textContent = combinedLogs.join('\n');
        logContainer.scrollTop = logContainer.scrollHeight;
    }

    // Добавляем кнопку очистки логов
    function addClearLogsButton() {
        const controls = document.querySelector('.controls');
        if (!document.querySelector('.clear-logs-btn')) {
            const clearButton = document.createElement('button');
            clearButton.className = 'button clear-logs-btn';
            clearButton.textContent = 'Очистить логи';
            clearButton.onclick = clearUILogs;
            controls.appendChild(clearButton);
        }
    }

    async function clearUILogs() {
        try {
            const response = await fetch('/api/clear-ui-logs', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'}
            });

            if (response.ok) {
                addLog('✓ UI логи очищены');
                // Обновляем сразу
                setTimeout(updateUILogs, 500);
            }
        } catch (error) {
            addLog(`✗ Ошибка очистки логов: ${error.message}`);
        }
    }

    function addLog(message) {
        const timestamp = new Date().toLocaleTimeString();
        logMessages.push(`[${timestamp}] ${message}`);
        if (logMessages.length > 50) {
            logMessages.shift();
        }
        // Сразу обновляем отображение
        setTimeout(updateUILogs, 100);
    }

    async function startTest(customTestName = null) {
        const testName = customTestName || document.getElementById('testName').value || 'Load Test';
        addLog('');
        addLog(`>>> Запуск теста: "${testName}"`);
        addLog('Отправка запроса на /api/start-test...');

        try {
            const response = await fetch('/api/start-test', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({test_name: testName})
            });

            addLog(`Получен ответ: HTTP ${response.status}`);

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json();
            addLog(`Ответ сервера: ${JSON.stringify(data)}`);

            testActive = true;
            document.getElementById('testStatus').className = 'status active';
            document.getElementById('testStatus').textContent = 'active';
            document.getElementById('testInfo').textContent = `Запущен тест: ${testName}`;
            
            // Активируем моки
            updateMockStatus(true);

            addLog('✓ Тест успешно запущен!');
            addLog('✓ Метрики очищены');
            addLog('✓ Mock endpoint\'ы активированы');
            addLog('✓ Система готова к приему mock запросов');
            addLog('');
        } catch (error) {
            addLog(`✗ ОШИБКА запуска теста: ${error.message}`);
        }
    }

    async function stopTest() {
        addLog('');
        addLog('>>> Остановка теста...');
        addLog('Отправка запроса на /api/stop-test...');

        try {
            const response = await fetch('/api/stop-test', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'}
            });

            addLog(`Получен ответ: HTTP ${response.status}`);

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json();
            testActive = false;
            document.getElementById('testStatus').className = 'status inactive';
            document.getElementById('testStatus').textContent = 'inactive';
            document.getElementById('testInfo').textContent = 'Тест завершен';
            
            // Деактивируем моки
            updateMockStatus(false);

            addLog('✓ Тест завершен');
            addLog('✓ Mock endpoint\'ы деактивированы');

            if (data.summary) {
                addLog('');
                addLog('=== ОТЧЕТ О ТЕСТИРОВАНИИ ===');
                addLog(data.summary);
                addLog('===========================');
            }

            addLog('Обновление данных через 2 секунды...');
            setTimeout(() => refreshData(), 2000);
        } catch (error) {
            addLog(`✗ ОШИБКА остановки теста: ${error.message}`);
        }
    }

    async function resetMetrics() {
        addLog('');
        addLog('>>> Сброс метрик...');

        try {
            const response = await fetch('/api/reset-metrics', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'}
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json();
            addLog('✓ Метрики успешно сброшены');
            addLog('Обновление отображения...');
            updateMetrics();
        } catch (error) {
            addLog(`✗ ОШИБКА сброса метрик: ${error.message}`);
        }
    }

    async function updateMetrics() {
        try {
            const response = await fetch('/metrics');

            if (!response.ok) {
                addLog(`⚠ Ошибка получения метрик: HTTP ${response.status}`);
                return;
            }

            const metricsText = await response.text();

            // Парсим строчки метрик
            const lines = metricsText.split('\n');
            let totalRequests = 0;
            let avgResponseTime = 0;
            let codeCounts = {};
            let codeDetails = [];
            let topCode = '-';
            let uniqueCodes = 0;

            // test_code_total exposition example:
            // test_code_total{code="C101"} 234
            // test_code_total{code="C102"} 111

            // avg_response_time exposition example:
            // test_avg_response_time{test="current"} 0.357

            for (const line of lines) {
                if (line.startsWith('test_code_total{') && !line.includes('#')) {
                    // test_code_total{endpoint="customer_list",code="CUSTATMC.00000"} 5.0
                    const codeMatch = line.match(/code="([^"]+)"/);
                    const valueMatch = line.match(/\s+([0-9\.]+)$/);
                    if (codeMatch && valueMatch) {
                        const code = codeMatch[1];
                        const cnt = parseInt(valueMatch[1]);
                        if (cnt > 0) {
                            codeCounts[code] = (codeCounts[code] || 0) + cnt;
                            totalRequests += cnt;
                        }
                    }
                } else if (line.startsWith('http_request_duration_seconds_sum{') && !line.includes('#')) {
                    // Для подсчета среднего времени ответа используем уже имеющиеся метрики
                    const match = line.match(/\s+([0-9\.]+)$/);
                    if (match && (line.includes('/customer-atomic/') || line.includes('/profile/v1/profile/'))) {
                        avgResponseTime += parseFloat(match[1]);
                    }
                }
            }

            // Вычисляем среднее время ответа
            if (totalRequests > 0) {
                avgResponseTime = avgResponseTime / totalRequests;
            }

            // Генерируем секцию распределения кодов
            uniqueCodes = Object.keys(codeCounts).length;
            // Сортировка по частоте убывания
            const sortedCodes = Object.keys(codeCounts).sort((a, b) => codeCounts[b]-codeCounts[a]);
            if (sortedCodes.length > 0) {
                topCode = sortedCodes[0];
            }
            codeDetails = sortedCodes.map(code => `${code.padEnd(8)}: ${codeCounts[code]}`).join('\n');

            document.getElementById('totalRequests').textContent = totalRequests;
            document.getElementById('avgResponseTime').textContent = avgResponseTime ? (avgResponseTime.toFixed(3)+'s') : '0.00s';
            document.getElementById('topCode').textContent = topCode;
            document.getElementById('uniqueCodes').textContent = uniqueCodes;
            document.getElementById('codeDistribution').textContent = codeDetails || 'Нет данных...';

            // Логируем только при изменениях
            const currentTotal = parseInt(document.getElementById('totalRequests').textContent) || 0;
            if (totalRequests !== currentTotal && totalRequests > 0) {
                addLog(
                    `📊 Обновление метрик: всего ${totalRequests}, топ код ${topCode}, уникальных кодов ${uniqueCodes}`
                );
            }
        } catch (error) {
            addLog(`✗ Ошибка обновления метрик: ${error.message}`);
        }
    }

    async function updateMetricsChart() {
        const totalRequests = parseInt(document.getElementById('totalRequests').textContent) || 0;
        const avgResponseTime = parseFloat(document.getElementById('avgResponseTime').textContent) || 0;
        const currentTime = new Date();

        // Получаем данные по кодам и endpoint'ам из метрик
        const { codeCounts, endpointCounts } = await getDetailedMetrics();

        // Проверяем, изменились ли данные с последнего раза
        const dataChanged = 
            totalRequests !== lastMetricsSnapshot.totalRequests ||
            avgResponseTime !== lastMetricsSnapshot.avgResponseTime ||
            JSON.stringify(codeCounts) !== JSON.stringify(lastMetricsSnapshot.codes) ||
            JSON.stringify(endpointCounts) !== JSON.stringify(lastMetricsSnapshot.endpoints);

        // Добавляем новые точки только если данные изменились
        if (dataChanged) {
            // Обновляем основные метрики
            chartDataPoints.totalRequests.push({x: currentTime, y: totalRequests});
            chartDataPoints.responseTime.push({x: currentTime, y: avgResponseTime * 1000}); // Переводим в мс
            
            // Обновляем данные по кодам
            for (const [code, count] of Object.entries(codeCounts)) {
                if (!chartDataPoints.codes[code]) {
                    chartDataPoints.codes[code] = [];
                }
                chartDataPoints.codes[code].push({x: currentTime, y: count});
                
                // Ограничиваем количество точек
                if (chartDataPoints.codes[code].length > maxDataPoints) {
                    chartDataPoints.codes[code].shift();
                }
            }
            
            // Обновляем данные по endpoint'ам
            for (const [endpoint, count] of Object.entries(endpointCounts)) {
                if (!chartDataPoints.endpoints[endpoint]) {
                    chartDataPoints.endpoints[endpoint] = [];
                }
                chartDataPoints.endpoints[endpoint].push({x: currentTime, y: count});
                
                // Ограничиваем количество точек
                if (chartDataPoints.endpoints[endpoint].length > maxDataPoints) {
                    chartDataPoints.endpoints[endpoint].shift();
                }
            }
            
            // Ограничиваем количество точек для основных метрик
            if (chartDataPoints.totalRequests.length > maxDataPoints) {
                chartDataPoints.totalRequests.shift();
                chartDataPoints.responseTime.shift();
            }

            // Обновляем snapshot
            lastMetricsSnapshot = {
                totalRequests,
                avgResponseTime,
                codes: {...codeCounts},
                endpoints: {...endpointCounts}
            };
        }

        // Создаем datasets в зависимости от выбранного вида
        let datasets = [];
        
        if (currentChartView === 'overview') {
            datasets = createOverviewDatasets();
        } else if (currentChartView === 'endpoints') {
            datasets = createEndpointsDatasets();
        } else if (currentChartView === 'codes') {
            datasets = createCodesDatasets();
        }

        // Пересоздаем график только если есть данные для отображения
        if (datasets.length > 0) {
            renderChart(datasets);
        }
    }

    function createOverviewDatasets() {
        return [
            {
                label: 'Всего запросов',
                data: chartDataPoints.totalRequests,
                backgroundColor: 'rgba(52, 152, 219, 0.1)',
                borderColor: 'rgba(52, 152, 219, 1)',
                borderWidth: 3,
                fill: false,
                tension: 0.4
            },
            {
                label: 'Время ответа (мс)',
                data: chartDataPoints.responseTime,
                backgroundColor: 'rgba(46, 204, 113, 0.1)',
                borderColor: 'rgba(46, 204, 113, 1)',
                borderWidth: 2,
                fill: false,
                tension: 0.4,
                yAxisID: 'y1'
            }
        ];
    }

    function createEndpointsDatasets() {
        const colors = [
            'rgba(52, 152, 219, 1)',   // Синий
            'rgba(231, 76, 60, 1)',    // Красный  
            'rgba(46, 204, 113, 1)',   // Зеленый
            'rgba(155, 89, 182, 1)',   // Фиолетовый
            'rgba(241, 196, 15, 1)',   // Желтый
            'rgba(230, 126, 34, 1)',   // Оранжевый
            'rgba(26, 188, 156, 1)',   // Бирюзовый
            'rgba(149, 165, 166, 1)'   // Серый
        ];
        
        let datasets = [];
        let colorIndex = 0;
        
        for (const [endpoint, data] of Object.entries(chartDataPoints.endpoints)) {
            if (data.length > 0) {
                datasets.push({
                    label: endpoint,
                    data: data,
                    backgroundColor: colors[colorIndex % colors.length] + '20',
                    borderColor: colors[colorIndex % colors.length],
                    borderWidth: 2,
                    fill: false,
                    tension: 0.4
                });
                colorIndex++;
            }
        }
        
        return datasets;
    }

    function createCodesDatasets() {
        const colors = [
            'rgba(46, 204, 113, 1)',   // Зеленый для success
            'rgba(231, 76, 60, 1)',    // Красный для ошибок
            'rgba(155, 89, 182, 1)',   // Фиолетовый
            'rgba(241, 196, 15, 1)',   // Желтый
            'rgba(230, 126, 34, 1)',   // Оранжевый
            'rgba(26, 188, 156, 1)',   // Бирюзовый
            'rgba(52, 152, 219, 1)',   // Синий
            'rgba(149, 165, 166, 1)'   // Серый
        ];
        
        let datasets = [];
        let colorIndex = 0;
        
        // Сначала добавляем success коды (зеленым)
        for (const [code, data] of Object.entries(chartDataPoints.codes)) {
            if (data.length > 0) {
                const isSuccessCode = code.endsWith('.00000') || code === '200';
                datasets.push({
                    label: `${code}${isSuccessCode ? ' (OK)' : ''}`,
                    data: data,
                    backgroundColor: colors[isSuccessCode ? 0 : (colorIndex % colors.length)] + '20',
                    borderColor: colors[isSuccessCode ? 0 : (colorIndex % colors.length)],
                    borderWidth: isSuccessCode ? 3 : 2,
                    fill: false,
                    tension: 0.4
                });
                if (!isSuccessCode) colorIndex++;
            }
        }
        
        return datasets;
    }

    function renderChart(datasets) {
        const ctx = document.getElementById('metricsChart').getContext('2d');
        
        // Пересоздаем график
        if (metricsChart) {
            metricsChart.destroy();
        }

        const scales = {
            x: {
                type: 'time',
                time: {
                    unit: 'minute',
                    displayFormats: {
                        minute: 'HH:mm:ss'
                    }
                },
                title: {
                    display: true,
                    text: 'Время'
                }
            },
            y: {
                type: 'linear',
                display: true,
                position: 'left',
                title: {
                    display: true,
                    text: currentChartView === 'overview' ? 'Количество запросов' : 'Количество'
                },
                beginAtZero: true
            }
        };

        // Добавляем вторую ось только для overview
        if (currentChartView === 'overview') {
            scales.y1 = {
                type: 'linear',
                display: true,
                position: 'right',
                title: {
                    display: true,
                    text: 'Время ответа (мс)'
                },
                beginAtZero: true,
                grid: {
                    drawOnChartArea: false,
                }
            };
        }

        metricsChart = new Chart(ctx, {
            type: 'line',
            data: { datasets },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                interaction: {
                    mode: 'index',
                    intersect: false,
                },
                plugins: {
                    legend: {
                        position: 'bottom',
                        labels: {
                            usePointStyle: true,
                            padding: 15,
                            font: {
                                size: 11
                            },
                            maxWidth: 200
                        }
                    },
                    tooltip: {
                        mode: 'index',
                        intersect: false
                    }
                },
                scales: scales
            }
        });
    }

    async function getDetailedMetrics() {
        try {
            const response = await fetch('/metrics');
            if (!response.ok) return { codeCounts: {}, endpointCounts: {} };
            
            const metricsText = await response.text();
            const lines = metricsText.split('\n');
            const codeCounts = {};
            const endpointCounts = {};

            for (const line of lines) {
                if (line.startsWith('test_code_total{') && !line.includes('#')) {
                    const codeMatch = line.match(/code="([^"]+)"/);
                    const valueMatch = line.match(/\s+([0-9\.]+)$/);
                    if (codeMatch && valueMatch) {
                        const code = codeMatch[1];
                        const cnt = parseInt(valueMatch[1]);
                        if (cnt > 0) {
                            codeCounts[code] = (codeCounts[code] || 0) + cnt;
                        }
                    }
                } else if (line.startsWith('test_endpoint_total{') && !line.includes('#')) {
                    const endpointMatch = line.match(/endpoint="([^"]+)"/);
                    const valueMatch = line.match(/\s+([0-9\.]+)$/);
                    if (endpointMatch && valueMatch) {
                        const endpoint = endpointMatch[1];
                        const cnt = parseInt(valueMatch[1]);
                        if (cnt > 0) {
                            endpointCounts[endpoint] = (endpointCounts[endpoint] || 0) + cnt;
                        }
                    }
                }
            }
            
            return { codeCounts, endpointCounts };
        } catch (error) {
            console.error('Error fetching detailed metrics:', error);
            return { codeCounts: {}, endpointCounts: {} };
        }
    }

    async function refreshData() {
        addLog('🔄 Обновление данных...');
        await updateMetrics();
        await updateMetricsChart();
        setTimeout(() => {
            addLog('Перезагрузка страницы...');
            location.reload();
        }, 1000);
    }

    function switchChartView(view) {
        currentChartView = view;
        
        // Обновляем активные кнопки  
        document.querySelectorAll('.chart-button').forEach(btn => btn.classList.remove('active'));
        document.querySelector(`[onclick="switchChartView('${view}')"]`).classList.add('active');
        
        // Перерисовываем график с новым видом
        updateMetricsChart();
        
        const viewNames = {
            'overview': 'Общая статистика',
            'endpoints': 'По endpoint\'ам', 
            'codes': 'По кодам ответов'
        };
        
        addLog(`📊 Переключен вид графика: ${viewNames[view] || view}`);
    }

    // Автообновление метрик каждые 5 секунд
    setInterval(updateMetrics, 5000);
    setInterval(updateMetricsChart, 5000);

    // Автообновление UI логов каждые 3 секунды
    setInterval(updateUILogs, 3000);

    // Инициализация
    document.addEventListener('DOMContentLoaded', function() {
        addClearLogsButton();
        addLog('=== Dashboard загружен ===');
        addLog('Инициализация системы...');
        updateMetrics();
        updateMetricsChart();
        updateUILogs();

        // Проверяем статус моков при загрузке
        fetch('/api/mock-status')
            .then(response => response.json())
            .then(data => {
                updateMockStatus(data.mocks_active);
                addLog(`✓ Статус моков: ${data.mocks_active ? 'активны' : 'неактивны'}`);
                addLog(`✓ Найдено ${data.endpoints_count} зарегистрированных endpoint'ов`);
            })
            .catch(error => {
                addLog(`✗ Ошибка получения статуса моков: ${error.message}`);
            });

        // Проверка API endpoints...
        addLog('Проверка API endpoints...');
        fetch('/api/test-results')
            .then(response => {
                if (response.ok) {
                    addLog('✓ API доступен и работает');
                    return response.json();
                } else {
                    addLog(`✗ API недоступен: HTTP ${response.status}`);
                }
            })
            .then(data => {
                if (data) {
                    addLog(`✓ Найдено ${data.length} записей в истории тестов`);
                }
            })
            .catch(error => {
                addLog(`✗ Ошибка подключения к API: ${error.message}`);
            });

        // Проверка метрик
        fetch('/metrics')
            .then(response => {
                if (response.ok) {
                    addLog('✓ Prometheus метрики доступны');
                } else {
                    addLog(`✗ Метрики недоступны: HTTP ${response.status}`);
                }
            })
            .catch(error => {
                addLog(`✗ Ошибка получения метрик: ${error.message}`);
            });

        addLog('=== Система готова к работе ===');
        addLog('');
        addLog('💡 Возможности для нагрузочного тестирования:');
        addLog('- Используйте быстрые тесты для запуска готовых сценариев');
        addLog('- Старт теста: запускает новую тестовую сессию и активирует моки');
        addLog('- Стоп теста: завершает тест, создает отчет и деактивирует моки');
        addLog('- Сброс метрик: очищает все счетчики и деактивирует моки');
        addLog('- Обновить данные: перезагружает страницу');
        addLog('');
        addLog('🔧 Инструменты для НТ:');
        addLog('- curl: curl -s "http://localhost:8000/endpoint?param=value"');
        addLog('- wrk: wrk -t4 -c100 -d30s --timeout 2s http://localhost:8000/endpoint');
        addLog('- ab: ab -n 1000 -c 10 http://localhost:8000/endpoint');
        addLog('');
        addLog('⚠️  Внимание: Mock endpoint\'ы работают только когда тест активен!');

        // Инициализируем сворачиваемые блоки
        const collapsibles = document.querySelectorAll('.collapsible');
        collapsibles.forEach(collapsible => {
            const targetId = collapsible.getAttribute('data-target');
            const content = document.getElementById(targetId);
            
            collapsible.addEventListener('click', function() {
                this.classList.toggle('active');
                if (content) {
                    content.classList.toggle('active');
                }
                const chevron = this.querySelector('.chevron');
                if (chevron) {
                    chevron.classList.toggle('down');
                }
            });
        });
    });
</script>
</body>
</html>