Проблема с обновлением фронтенда Flask-приложения на виртуальной машине

Всем привет! Столкнулся с проблемой при развертывании Flask-приложения на виртуальной машине. При локальном запуске всё работает корректно: фронтенд отображается как задумано, элементы интерфейса (кнопки, статус-бар) функционируют, данные обновляются в реальном времени. Однако при запуске на виртуальной машине (Ubuntu) фронтенд работает некорректно: не отображается кнопка "Остановить", статус-бар не обновляется, прогресс обработки не виден.

Backend: Flask, раздаётся через Gunicorn (-w 4 --timeout 300), проксируется через Nginx. Frontend: HTML-шаблон (index.html) с Tailwind CSS и jQuery, загружаемыми через CDN (cdn.jsdelivr.net, code.jquery.com). AJAX-запросы (/get_status) используются для обновления статуса обработки в реальном времени.

Логика: Пользователь вводит ID вакансии, отправляется POST-запрос на /process_vacancy, запускается обработка в отдельном потоке, а фронтенд опрашивает /get_status каждую секунду для отображения прогресса.

Проблема

Локально: При нажатии кнопки "Обработать" появляется кнопка "Остановить". Статус-бар и счётчик соискателей обновляются в реальном времени. Логи консоли браузера показывают успешные AJAX-запросы (/get_status).

На ВМ: Кнопка "Остановить" не появляется. Статус-бар и счётчик не обновляются.

В логах приложения (app.log) запросы к API обрабатываются

server {
    listen 80;
    server_name;
    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
    }
}

app.py (упрощённый фрагмент)

from flask import Flask, render_template, request, jsonify
import threading
import queue

app = Flask(__name__)

processing_status = {
    'is_processing': False,
    'stop_processing': False,
    'applicant_count': 0,
    'progress': 0,
    'message': '',
    'current_vacancy_id': None
}
status_lock = threading.Lock()
progress_queue = queue.Queue()

@app.route('/')
def index():
    with status_lock:
        status = processing_status.copy()
    return render_template('index.html', **status)

@app.route('/process_vacancy', methods=['POST'])
def process_vacancy():
    with status_lock:
        if processing_status['is_processing']:
            return jsonify({'error': 'Обработка уже выполняется'}), 409
        processing_status['is_processing'] = True
        processing_status['stop_processing'] = False
        processing_status['applicant_count'] = 0
        processing_status['progress'] = 0
        processing_status['message'] = 'Обработка начата'
        processing_status['current_vacancy_id'] = request.form.get('vacancy_id')

    def process(vacancy_id):
        # Обработка вакансии (запросы к API, запись в Google Sheets)
        # ...
        with status_lock:
            processing_status['is_processing'] = False
            processing_status['message'] = f"Обработано: {inserted_count} записей"
            processing_status['current_vacancy_id'] = None

    threading.Thread(target=process, args=(request.form.get('vacancy_id'),), daemon=True).start()
    return jsonify({'status': 'Processing started'}), 202

@app.route('/get_status')
def get_status():
    with status_lock:
        status = processing_status.copy()
    try:
        status['progress'] = progress_queue.get_nowait()
    except queue.Empty:
        pass
    return jsonify(status)

index.html (упрощённый фрагмент)

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Обработка вакансий</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body class="bg-gray-100 flex items-center justify-center h-screen">
    <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
        <form id="process-form" class="space-y-4">
            <input type="text" id="vacancy_id" name="vacancy_id" required>
            <button type="submit" id="process-btn" class="w-full bg-blue-500 text-white p-2 rounded-md">Обработать</button>
        </form>
        <form id="stop-form" class="mt-4 {% if not is_processing %}hidden{% endif %}">
            <button type="submit" class="w-full bg-red-500 text-white p-2 rounded-md">Остановить</button>
        </form>
        <div class="mt-4">
            <p>Найдено соискателей: <span id="applicant_count">{{ applicant_count }}</span></p>
            <div class="w-full bg-gray-200 rounded-full h-4">
                <div id="progress_bar" class="bg-blue-500 h-4 rounded-full" style="width: {{ progress }}%"></div>
            </div>
            <p><span id="progress_text">{{ progress | round(2) }}</span>%</p>
        </div>
    </div>
    <script>
        function updateStatus() {
            console.log('Sending AJAX request to /get_status at ' + new Date().toISOString());
            $.ajax({
                url: '/get_status',
                method: 'GET',
                timeout: 5000,
                success: function(data) {
                    console.log('Received status at ' + new Date().toISOString() + ':', data);
                    $('#applicant_count').text(data.applicant_count || 0);
                    $('#progress_bar').css('width', (data.progress || 0) + '%');
                    $('#progress_text').text((data.progress || 0).toFixed(2));
                    if (data.is_processing) {
                        $('#process-btn').prop('disabled', true);
                        $('#stop-form').removeClass('hidden');
                        setTimeout(updateStatus, 1000);
                    } else {
                        $('#process-btn').prop('disabled', false);
                        $('#stop-form').addClass('hidden');
                    }
                },
                error: function(xhr, status, error) {
                    console.error('Error in /get_status:', {status: status, error: error});
                }
            });
        }

        $(document).ready(function() {
            $('#process-form').on('submit', function(e) {
                e.preventDefault();
                console.log('Submitting process form');
                $.ajax({
                    url: '/process_vacancy',
                    method: 'POST',
                    data: $(this).serialize(),
                    success: function(response) {
                        console.log('Process started:', response);
                        updateStatus();
                    },
                    error: function(xhr) {
                        console.error('Error in /process_vacancy');
                    }
                });
            });
        });
    </script>
</body>
</html>

Проверил загрузку статики (Tailwind, jQuery) через CDN — в Network-вкладке статус 200. Добавлял отладочные логи в JavaScript — запрос /get_status отправляется, но ответ не приходит. Увеличивал таймауты в Gunicorn и Nginx до 300 секунд. Проверял логи сервера — обработка вакансий завершается (иногда быстро, если данные уже есть), ошибок нет. Проверял сетевые настройки: порт 80 открыт, curl http://localhost:5000/get_status возвращает корректный JSON.

Почему фронтенд работает корректно локально, но на виртуальной машине не обновляется (кнопка "Остановить" не появляется, статус-бар не движется)? Может ли это быть связано с настройками Gunicorn/Nginx, сетевыми ограничениями или поведением AJAX-запросов? Какие шаги можно предпринять для диагностики и исправления?

Заранее спасибо за помощь!


Ответы (0 шт):