сделай чтобы активность arm1 была когда онлайн не прямолинейной на графике а хаотичной как arm5,arm6
from flask import Flask, render_template, request, redirect, make_response, jsonify, send_file, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
from datetime import datetime, timedelta, date
import pandas as pd
from io import BytesIO
import json
import queue
import logging
from sqlalchemy import cast, Integer, or_
import os
from collections import deque
import random



app = Flask(__name__)


app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///table_data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

ACTIVITY_HISTORY_LENGTH = 10  # Количество последних значений для графика
activity_data = {}

class TableRow(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), nullable=False)
    row_number = db.Column(db.String(10))
    timestamp = db.Column(db.String(50))
    number = db.Column(db.String(50))
    serial = db.Column(db.String(50))
    dl_number = db.Column(db.String(50))
    worker_name = db.Column(db.String(100))
    pack_time = db.Column(db.String(50))
    arma_type = db.Column(db.String(50))


class DLCounter(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    counter = db.Column(db.Integer, nullable=False)

    def __repr__(self):
        return f'<DLCounter {self.date}: {self.counter}>'


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    login_history = db.relationship('LoginHistory', backref='user', lazy=True)


class LoginHistory(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    login_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

class Statistics(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    generate_clicks = db.Column(db.Integer, default=0)
    pack_clicks = db.Column(db.Integer, default=0)

    def __repr__(self):
        return f'<Statistics {self.date}: Generate={self.generate_clicks}, Pack={self.pack_clicks}>'

# Добавьте это в раздел моделей вашего Flask-приложения

class Device(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(10), unique=True, nullable=False)  # Имена: ARM1, ARM2 и т.д.
    arma_type = db.Column(db.String(10), nullable=False, default='Other')  # Тип АРМА
    last_active = db.Column(db.DateTime, nullable=True)  # Время последней активности
    internet_up = db.Column(db.Boolean, nullable=False, default=True)  # Статус интернет-соединения

    def to_dict(self, current_time=None):
        if current_time is None:
            current_time = datetime.utcnow()
        status = 'offline'
        if self.last_active:
            delta = current_time - self.last_active
            if delta <= timedelta(seconds=65):
                if self.internet_up:
                    status = 'online'
                else:
                    status = 'no_internet'
        return {
            "id": self.id,
            "name": self.name,
            "arma_type": self.arma_type,
            "last_active": self.last_active.isoformat() if self.last_active else None,
            "internet_up": self.internet_up,
            "status": status
        }

tables = {}
table = [['№', 'Время отправки', 'Номер ', 'Serial', 'Графа', 'Имя отправителя','Время упаковки' ,'Тип АРМА',  'Действие']]
# Создаем очередь для событий
update_queue = queue.Queue()


@app.route('/get_device_status', methods=['GET'])
def get_device_status():
    logging.debug("Received request for device status")
    devices = Device.query.all()  # Получаем все устройства из базы данных
    device_statuses = {}
    threshold = timedelta(seconds=20)  # Увеличенный порог времени для соответствия HEARTBEAT_INTERVAL
    now = datetime.utcnow()

    for device in devices:
        name = device.name
        if name not in activity_data:
            logging.warning(f"Activity data not initialized for device '{name}'")
            activity_data[name] = deque([0]*ACTIVITY_HISTORY_LENGTH, maxlen=ACTIVITY_HISTORY_LENGTH)

        if (device.last_active and now - device.last_active <= threshold) and device.internet_up:
            status = 'online'
            activity_value = 1  # Используем 1 для обозначения активности (можно адаптировать под требования)
            activity_data[name].append(activity_value)
        elif (device.last_active and now - device.last_active <= threshold) and not device.internet_up:
            status = 'no_internet'
            activity_data[name].append(0)  # Нулевая активность при отсутствии интернета
        else:
            status = 'offline'
            activity_data[name].clear()
            activity_data[name].extend([0]*ACTIVITY_HISTORY_LENGTH)
        device_statuses[name] = status
        logging.debug(f"Device '{name}': status={status}, activity={list(activity_data[name])}")

    activity_data_serializable = {name: list(data) for name, data in activity_data.items()}

    return jsonify({
        'status': 'success',
        'devices': device_statuses,
        'activity': activity_data_serializable
    })


def initialize_activity_data():
    device_names = ['ARM1', 'ARM2', 'ARM3', 'ARM4', 'ARM5', 'ARM6']
    for name in device_names:
        activity_data[name] = deque([0]*ACTIVITY_HISTORY_LENGTH, maxlen=ACTIVITY_HISTORY_LENGTH)

def create_devices():
    device_types = {
        'ARM1': 'CFA',
        'ARM2': 'VND',
        'ARM3': 'SRC',
        'ARM4': 'SRC',
        'ARM5': 'RPA',
        'ARM6': 'RPA'
    }
    for name, arma_type in device_types.items():
        device = Device.query.filter_by(name=name).first()
        if not device:
            device = Device(name=name, arma_type=arma_type)
            db.session.add(device)
    db.session.commit()
    initialize_activity_data()  # Инициализируем данные об активности

@app.route('/register_user', methods=['POST'])
def register_user():
    data = request.json
    worker_name = data.get('worker_name')
    internet_up = data.get('internet_up', False)
    status = data.get('status', 'offline')
    logging.debug(f"Registering user: {worker_name}, Internet up: {internet_up}, Status: {status}")

    if not worker_name:
        logging.error("worker_name is missing in the request")
        return jsonify({"status": "error", "message": "worker_name is required"}), 400

    device = Device.query.filter_by(name=worker_name).first()  # Получаем устройство из базы данных
    if not device:
        logging.error(f"Device '{worker_name}' not found")
        return jsonify({"status": "error", "message": "Device not found"}), 404

    device.last_active = datetime.utcnow()
    device.internet_up = internet_up
    db.session.commit()
    logging.debug(f"Device '{worker_name}' updated: last_active={device.last_active}, internet_up={device.internet_up}")

    # Обновляем activity_data
    if status == 'online':
        activity_data[device.name].append(1)  # Онлайн
        logging.debug(f"Device '{device.name}' is online. Activity data: {list(activity_data[device.name])}")
    elif status == 'no_internet':
        activity_data[device.name].append(0)  # Нет интернета
        logging.debug(f"Device '{device.name}' has no internet. Activity data: {list(activity_data[device.name])}")
    elif status == 'error_copying':
        activity_data[device.name].append(0)  # Ошибка при копировании
        logging.debug(f"Device '{device.name}' encountered copying error. Activity data: {list(activity_data[device.name])}")
    else:
        activity_data[device.name].append(0)  # Оффлайн или не определено
        logging.debug(f"Device '{device.name}' is offline or unknown status. Activity data: {list(activity_data[device.name])}")

    return jsonify({"status": "success"})


@app.route('/get_unpacked_days', methods=['GET'])
def get_unpacked_days():
    year = request.args.get('year')
    month = request.args.get('month')
    
    if not year or not month:
        return jsonify({"status": "error", "message": "Необходимо указать год и месяц"}), 400
    
    try:
        year = int(year)
        month = int(month)
    except ValueError:
        return jsonify({"status": "error", "message": "Год и месяц должны быть числами"}), 400

    # Формируем формат даты для фильтрации
    month_str = f"{month:02d}"
    
    # Запрашиваем уникальные дни, где pack_time отсутствует и arma_type равен 'RPA'
    unpacked_dates = TableRow.query.filter(
        TableRow.date.like(f"{year}-{month_str}-%"),
        TableRow.arma_type == 'RPA',
        or_(TableRow.pack_time == None, TableRow.pack_time == '')
    ).with_entities(TableRow.date).distinct().all()
    
    # Извлекаем дни из дат
    unpacked_days = set()
    for date_str, in unpacked_dates:
        try:
            day = int(date_str.split('-')[2])
            unpacked_days.add(day)
        except (IndexError, ValueError):
            continue  # Пропускаем некорректные форматы дат

    return jsonify({'status': 'success', 'unpacked_days': list(unpacked_days)})

@app.route('/stream')
def stream():
    def event_stream():
        while True:
            try:
                data = update_queue.get(timeout=20)
                yield f'data: {data}\n\n'
            except queue.Empty:
                yield ': keep-alive\n\n'
    return Response(event_stream(), mimetype='text/event-stream')


# Удален маршрут /ping, так как он использовал ActiveUser


@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')  # Новый параметр фильтра

    app.logger.debug(f"Получен запрос таблицы для даты: {date}, фильтр: {filter_arma}")

    # Начинаем построение запроса
    query = TableRow.query.filter_by(date=date)

    # Если фильтр активирован, добавляем условие
    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    rows = query.all()
    app.logger.debug(f"Найдено записей: {len(rows)} для даты {date} с фильтром {filter_arma}")

    data = []
    numbers = {}
    serials = {}
    dl_numbers = {}

    for row in rows:
        data.append({
            'id': row.id,
            'row_number': row.row_number,
            'timestamp': row.timestamp,
            'number': row.number,
            'serial': row.serial,
            'dl_number': row.dl_number,
            'worker_name': row.worker_name,
            'pack_time': row.pack_time,
            'arma_type': row.arma_type,
        })

        # Собираем информацию о дубликатах
        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number))
            if clean_number in numbers:
                numbers[clean_number].append(len(data) - 1)
            else:
                numbers[clean_number] = [len(data) - 1]

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial))
            if clean_serial in serials:
                serials[clean_serial].append(len(data) - 1)
            else:
                serials[clean_serial] = [len(data) - 1]

        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number))
            if clean_dl_number in dl_numbers:
                dl_numbers[clean_dl_number].append(len(data) - 1)
            else:
                dl_numbers[clean_dl_number] = [len(data) - 1]

    # Фильтруем только реальные дубликаты
    duplicate_numbers = {k: v for k, v in numbers.items() if len(v) > 1}
    duplicate_serials = {k: v for k, v in serials.items() if len(v) > 1}
    duplicate_dl_numbers = {k: v for k, v in dl_numbers.items() if len(v) > 1}

    return jsonify({
        "data": data,
        "duplicates": {
            "numbers": duplicate_numbers,
            "serials": duplicate_serials,
            "dl_numbers": duplicate_dl_numbers
        }
    })
@app.route('/update_table', methods=['POST'])
def update_table():
    data = request.json
    print(data)
    date = data['date']
    worker_name = data['worker_name']
    arma_type = data['arma_type']

    # Получаем количество существующих записей для этой даты
    row_count = TableRow.query.filter_by(date=date).count() + 1

    # Создаём новую строку в таблице
    if not data.get('number') and not data.get('serial'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial='',  # Пустое значение для серийного номера
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
        )
    elif not data.get('number'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial=data['serial'],
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
        )
    else:
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number=data['number'],
            serial=data['serial'],
            dl_number=data.get('dl_number', ''),
            worker_name=worker_name,
            arma_type=arma_type,
        )

    # Сохраняем запись в базе данных
    db.session.add(new_row)

    device = Device.query.filter_by(name=worker_name).first()
    if device:
        device.last_active = datetime.utcnow()
        db.session.commit()
    # Ранее здесь была логика обновления ActiveUser, удалим ее

    db.session.commit()

    # Отправляем сообщение в очередь для обновления клиентов
    update_queue.put(json.dumps({"type": "update", "date": date}))

    return jsonify({"status": "success"})


@app.route('/update_graf', methods=['POST'])
def update_graf():
    try:
        data = request.json
        row_id = int(data.get('id'))
        new_value = data.get('new_value')
        date = data.get('date')

        if not row_id or not date:
            return jsonify({"status": "error", "message": "Missing 'id' or 'date' in request"}), 400

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"}), 404
    except (ValueError, TypeError) as e:
        return jsonify({"status": "error", "message": f"Invalid input: {str(e)}"}), 400
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/update_arma_type', methods=['POST'])
def update_arma_type():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        row = TableRow.query.filter_by(date=date, serial=serial).first()
        if row:
            row.arma_type = arma_type
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})


@app.route('/update_number', methods=['POST'])
def update_number():
    try:
        data = request.json
        row_id = int(data['id'])  # Используем 'id' вместо 'row_index'
        new_value = data['new_value']
        date = data['date']

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})

@app.route('/pack_item', methods=['POST'])
def pack_item():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']

        rows = TableRow.query.filter_by(id=row_id, date=date).all()
        if rows:
            row = rows[0]
            pack_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            row.pack_time = pack_time
            db.session.commit()
            
            # Обновляем статистику упаковки
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.pack_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=0, pack_clicks=1)  # pack_clicks=1
                db.session.add(statistics)
            db.session.commit()
            
            # Отправляем сообщение в очередь для обновления клиентов
            update_queue.put(json.dumps({"type": "update", "date": date}))
            
            return jsonify({"status": "success", "pack_time": pack_time})

        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        print(f"Ошибка в pack_item: {str(e)}")
        return jsonify({"status": "error", "message": str(e)}), 500



@app.route('/set_initial_dl', methods=['POST'])
def set_initial_dl():
    try:
        data = request.json
        if not data:
            return jsonify({"status": "error", "message": "No data received"})

        date = data.get('date')
        initial_number = data.get('initial_number')

        if not date or not initial_number:
            return jsonify({
                "status": "error", 
                "message": "Missing required fields",
                "received": {"date": date, "initial_number": initial_number}
            })

        try:
            initial_number = int(initial_number)
        except ValueError:
            return jsonify({
                "status": "error",
                "message": "Initial number must be an integer"
            })

        print(f"Setting initial DL for date {date}: {initial_number}")  # Отладочный вывод

        dl_counter = DLCounter.query.filter_by(date=date).first()
        if dl_counter:
            dl_counter.counter = initial_number
            print(f"Updated existing counter for {date}")  # Отладочный вывод
        else:
            dl_counter = DLCounter(date=date, counter=initial_number)
            db.session.add(dl_counter)
            print(f"Created new counter for {date}")  # Отладочный вывод

        db.session.commit()
        return jsonify({
            "status": "success",
            "message": f"DL counter set to {initial_number} for date {date}"
        })

    except Exception as e:
        print(f"Error in set_initial_dl: {str(e)}")  # Отладочный вывод
        db.session.rollback()
        return jsonify({
            "status": "error",
            "message": str(e)
        })


@app.route('/generate_dl', methods=['POST'])
def generate_dl():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']
        
        # Получаем или создаем счетчик DL для данной даты
        dl_counter = DLCounter.query.filter_by(date=date).first()
        if not dl_counter:
            return jsonify({"status": "error", "message": "DL counter не инициализирован"}), 400

        # Генерируем новый DL номер
        dl_number = f"DL{dl_counter.counter:06d}"

        # Находим и обновляем нужную запись по id
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = dl_number
            dl_counter.counter += 1  # Увеличиваем счетчик

            # Обновляем статистику генерации
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.generate_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=1, pack_clicks=0)
                db.session.add(statistics)

            db.session.commit()
            return jsonify({"status": "success", "dl_number": dl_number, "id": row_id})
        
        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500




@app.route('/export_excel')
def export_excel():
    try:
        date = request.args.get('date')
        sort_column = request.args.get('sort_column')
        sort_order = request.args.get('sort_order')

        if not date:
            return jsonify({"error": "Date parameter is required"}), 400

        # Начинаем построение запроса с фильтрации по дате и типу АРМА "RPA"
        query = TableRow.query.filter_by(date=date, arma_type='RPA')

        # Добавляем дополнительные условия фильтрации:
        # - dl_number не пустой
        # - pack_time не пустой
        query = query.filter(
            TableRow.dl_number.isnot(None),
            TableRow.dl_number != '',
            TableRow.pack_time.isnot(None),
            TableRow.pack_time != ''
        )

        # Применяем сортировку, если указана
        if sort_column and sort_order:
            column_mapping = {
                '0': TableRow.row_number,
                '1': TableRow.timestamp,
                '2': TableRow.number,
                '3': TableRow.serial,
                '4': TableRow.dl_number,
                '5': TableRow.worker_name,
                '6': TableRow.pack_time,  # Время упаковки
                '7': TableRow.arma_type,  # Тип АРМА
    
            }
            if sort_column in column_mapping:
                column = column_mapping[sort_column]

                # Явно приводим столбец "Номер" к целому числу для корректной сортировки
                if sort_column == '2':
                    column = cast(column, Integer)

                if sort_order == 'desc':
                    query = query.order_by(column.desc())
                else:
                    query = query.order_by(column.asc())
            else:
                return jsonify({"error": "Invalid sort column"}), 400

        rows = query.all()

        # Создаем DataFrame только с отфильтрованными данными
        data = []
        for row in rows:
            # Безопасное преобразование номера
            try:
                number = int(row.number) if row.number and row.number.strip() else None
            except ValueError:
                number = None

            data.append({
                '№': row.row_number,
                'Время отправки': row.timestamp,
                'Номер': number,
                'Serial': row.serial,
                'Графа': row.dl_number,
                'Имя отправителя': row.worker_name,
                'Время упаковки': row.pack_time,  # Время упаковки
                'Тип АРМА': row.arma_type,  # Тип АРМА
                
            })

        if not data:
            return jsonify({"error": "Нет записей для экспорта с заданными критериями."}), 400

        df = pd.DataFrame(data)

        # Создаем Excel файл
        output = BytesIO()
        with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
            df.to_excel(writer, index=False, sheet_name='Sheet1')

            # Настраиваем форматирование
            workbook = writer.book
            worksheet = writer.sheets['Sheet1']

            # Автоматическая ширина столбцов
            for idx, col in enumerate(df.columns):
                series = df[col]
                max_len = max(
                    series.astype(str).map(len).max(),
                    len(str(col))
                ) + 2
                worksheet.set_column(idx, idx, max_len)

                # Форматирование числовых столбцов
                if col in ['Номер', 'Графа']:
                    num_format = workbook.add_format({'num_format': '0'})
                    worksheet.set_column(idx, idx, max_len, num_format)

        output.seek(0)

        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'export_{date}.xlsx'
        )

    except Exception as e:
        print(f"Export error: {str(e)}")
        return jsonify({"error": str(e)}), 500


@app.route('/update_serial', methods=['POST'])
def update_serial():
    try:
        data = request.json
        row_index = int(data['row_index'])
        new_value = data['new_value']
        date = data['date']

        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            row.serial = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Invalid row index"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})


def initialize_table_for_date(date):
    # Проверяем, существуют ли уже записи для этой даты
    existing = TableRow.query.filter_by(date=date).first()
    if not existing:
        # Создаем новую запись только в случае отсутствия данных
        new_row = TableRow(
            date=date,
            row_number=str(1),
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            number='',
            serial='',
            dl_number='',
            worker_name='',
            arma_type='',
            pack_time='',
        )
        db.session.add(new_row)
        
        # Инициализируем статистику для новой даты
        statistics = Statistics(date=date, generate_clicks=0, pack_clicks=0)
        db.session.add(statistics)
        
        db.session.commit()


@app.route('/update_serial_arma', methods=['POST'])
def update_serial_arma():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        # Создаем новую запись в таблице
        new_row = TableRow(
            date=date,
            serial=serial,
            arma_type=arma_type,
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        )

        db.session.add(new_row)
        db.session.commit()

        return jsonify({"status": "success"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/get_statistics')
def get_statistics():
    try:
        date = request.args.get('date')
        app.logger.debug(f"Получен запрос статистики для даты: {date}")
        
        # Получаем статистику генерации и упаковки
        statistics = Statistics.query.filter_by(date=date).first()
        if not statistics:
            generate_clicks = 0
            pack_clicks = 0
            app.logger.debug(f"Статистика для даты {date} не найдена. Установлено значение 0.")
        else:
            generate_clicks = statistics.generate_clicks
            pack_clicks = statistics.pack_clicks
            app.logger.debug(f"Статистика для даты {date}: Генераций = {generate_clicks}, Упаковок = {pack_clicks}")
        
        # Получаем количество записей типа "RPA"
        rpa_records = TableRow.query.filter_by(date=date, arma_type='RPA').count()
        app.logger.debug(f"Количество записей типа RPA для даты {date}: {rpa_records}")
        
        return jsonify({
            "generate_clicks": generate_clicks,
            "pack_clicks": pack_clicks,
            "rpa_records": rpa_records
        })
    except Exception as e:
        app.logger.error(f"Ошибка в get_statistics: {str(e)}")
        return jsonify({
            "generate_clicks": 0,
            "pack_clicks": 0,
            "rpa_records": 0,
            "error": str(e)
        })

@app.route('/delete_row', methods=['POST'])
def delete_row():
    try:
        data = request.json
        row_id = data.get('id')

        if not row_id:
            return jsonify({'status': 'error', 'message': 'ID записи не предоставлен'}), 400

        row = TableRow.query.get(row_id)
        if not row:
            return jsonify({'status': 'error', 'message': 'Запись не найдена'}), 404

        date = row.date  # Получаем дату записи для обновления статистики

        # Получаем запись статистики для этой даты
        statistics = Statistics.query.filter_by(date=date).first()

        if statistics:
            # Если запись имела сгенерированный DL номер, уменьшаем generate_clicks
            if row.dl_number:
                if statistics.generate_clicks > 0:
                    statistics.generate_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement generate_clicks below zero for date {date}")

            # Если запись была упакована, уменьшаем pack_clicks
            if row.pack_time:
                if statistics.pack_clicks > 0:
                    statistics.pack_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement pack_clicks below zero for date {date}")

            db.session.commit()
        else:
            app.logger.warning(f"No Statistics entry found for date {date} when deleting row {row_id}")

        # Удаляем запись из таблицы
        db.session.delete(row)
        db.session.commit()

        # Отправляем сообщение в очередь для обновления клиентов
        update_queue.put(json.dumps({"type": "delete", "date": date, "id": row.id}))

        return jsonify({'status': 'success'})
    except Exception as e:
        db.session.rollback()
        app.logger.error(f"Ошибка в delete_row: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/', methods=['GET', 'POST'])
def main():
    if request.method == 'POST':
        worker = request.form.get('worker')
        name = request.form.get('name')

        # Сохраняем информацию о входе
        user = User.query.filter_by(username=name).first()
        if not user:
            user = User(username=name)
            db.session.add(user)

        login_history = LoginHistory(user=user)
        db.session.add(login_history)
        db.session.commit()

        resp = make_response(redirect('/welcome'))
        resp.set_cookie('worker', worker)
        resp.set_cookie('name', name)
        return resp

    return render_template('main.html')


@app.route('/get_current_dl', methods=['GET'])
def get_current_dl():
    date = request.args.get('date')
    if not date:
        return jsonify({"status": "error", "message": "Дата обязательна"}), 400

    dl_counter = DLCounter.query.filter_by(date=date).first()
    if dl_counter:
        return jsonify({"status": "success", "current_dl": dl_counter.counter})
    else:
        return jsonify({"status": "error", "message": "Счетчик DL не инициализирован для этой даты"}), 404


@app.route('/welcome')
def welcome():
    worker = request.cookies.get('worker')
    name = request.cookies.get('name')

    if not worker or not name:
        return redirect('/')

    current_time = datetime.utcnow()
    devices = [device.to_dict(current_time=current_time) for device in Device.query.order_by(Device.name).all()]
    current_time_str = current_time.isoformat()

    return render_template('welcome.html', name=name, worker=worker, table=table, devices=devices, current_time=current_time_str)


@app.route('/logout')
def logout():
    resp = make_response(redirect('/'))
    resp.delete_cookie('worker')
    resp.delete_cookie('name')
    return resp



if __name__ == '__main__':
    with app.app_context():
        db.create_all()
        create_devices()


    logging.disable(logging.CRITICAL)
    os.system('title СЕРВЕР')
    app.run(host='192.168.50.99', port=80, debug=True)  #192.168.50.99

import os
import time
import RPi.GPIO as GPIO
import requests
import shutil  # Для копирования файлов
from datetime import datetime
import threading

# URL сервера Flask
FLASK_SERVER_URL = "http://192.168.50.99:80"  # Адрес сервера
worker_name = 'ARM1'  # Имя устройства

# Путь к файлам, которые нужно скопировать
source_files = ['/home/moon/force_upgrade', '/home/moon/ModelC-A_FW_V2.0.9.zip']

# Интервал отправки heartbeat в секундах
HEARTBEAT_INTERVAL = 60  # Отправлять каждые 2 секунд

# Путь к USB-монтированным накопителям
USB_MOUNT_PATH = '/media/moon/'

# Настройка GPIO
GPIO.setmode(GPIO.BCM)  # Устанавливаем режим BCM (широкий)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)  # Настраиваем пин 17 как вход с подтяжкой вверх

def check_internet():
    """Проверка доступности интернета."""
    try:
        requests.get('https://www.google.com/', timeout=5)
        return True
    except requests.RequestException:
        return False

def is_usb_connected():
    """Проверяет наличие подключенного USB-накопителя."""
    try:
        # Список всех директорий в USB_MOUNT_PATH
        usb_drives = [
            d for d in os.listdir(USB_MOUNT_PATH)
            if os.path.isdir(os.path.join(USB_MOUNT_PATH, d))
        ]
        return len(usb_drives) > 0
    except Exception as e:
        print(f"Ошибка при проверке USB: {e}")
        return False

def copy_files_to_usb():
    """Копирует файлы в первый найденный USB-накопитель."""
    try:
        usb_drives = [
            os.path.join(USB_MOUNT_PATH, d) for d in os.listdir(USB_MOUNT_PATH)
            if os.path.isdir(os.path.join(USB_MOUNT_PATH, d))
        ]
        if not usb_drives:
            print("Нет подключенных USB накопителей для копирования.")
            return False

        destination = usb_drives[0]
        for file in source_files:
            if os.path.exists(file):
                try:
                    if os.path.isdir(file):
                        shutil.copytree(file, os.path.join(destination, os.path.basename(file)), dirs_exist_ok=True)
                    else:
                        shutil.copy2(file, destination)
                    print(f"Файл {file} успешно скопирован в {destination}.")
                except Exception as e:
                    print(f"Ошибка при копировании файла {file}: {e}")
                    return False
            else:
                print(f"Исходный файл {file} не найден.")
                return False
        return True
    except Exception as e:
        print(f"Ошибка в функции copy_files_to_usb: {e}")
        return False

def heartbeat():
    """Функция heartbeat для отправки статуса устройства на сервер."""
    while True:
        try:
            usb_connected = is_usb_connected()
            if usb_connected:
                print("USB накопитель обнаружен. Начинаю копирование файлов.")
                copy_success = copy_files_to_usb()
                if copy_success:
                    internet_status = check_internet()  # Дополнительно проверяем интернет
                    status = 'online' if internet_status else 'no_internet'
                else:
                    internet_status = False
                    status = 'error_copying'
            else:
                internet_status = False
                status = 'offline'

            response = requests.post(
                f"{FLASK_SERVER_URL}/register_user",
                json={
                    'worker_name': worker_name,
                    'internet_up': internet_status,  # Статус интернет-соединения
                    'status': status  # Статус устройства
                },
                headers={'Content-Type': 'application/json'}
            )
            if response.status_code != 200:
                print(f"Ошибка при отправке heartbeat, код {response.status_code}")
        except Exception as e:
            print(f"Ошибка при отправке heartbeat: {e}")  # Для отладки
        time.sleep(HEARTBEAT_INTERVAL)  # Отправлять каждые 60 секунд

def gpio_listener():
    """Функция для прослушивания событий GPIO."""
    while True:
        try:
            # Проверяем состояние кнопки
            if GPIO.input(17) == False:
                print("Кнопка нажата!")

                # Находим подключенные USB накопители
                usb_drives = [f'/media/moon/{d}' for d in os.listdir('/media/moon/') if os.path.isdir(f'/media/moon/{d}')]

                # Копируем файлы на USB накопитель
                if len(usb_drives) > 0:
                    destination = usb_drives[0]
                    for file in source_files:
                        filename = os.path.basename(file)
                        try:
                            if os.path.isdir(file):
                                shutil.copytree(file, os.path.join(destination, filename), dirs_exist_ok=True)
                            else:
                                shutil.copy2(file, destination)
                            print(f"Файл {filename} скопирован!")
                        except Exception as e:
                            print(f"Ошибка при копировании файла {filename}: {e}")

                    arma_type = 'CFA'
                    # Автоматически определяем текущую дату
                    selected_date = datetime.now().strftime("%Y-%m-%d")

                    # Получаем текущую дату и время
                    current_datetime = datetime.now()

                    # Отправляем данные на сервер Flask
                    data = {
                        'date': selected_date,
                        'worker_name': worker_name,
                        'timestamp': current_datetime.strftime("%Y-%m-%d %H:%M:%S"),
                        'arma_type': arma_type,
                    }

                    try:
                        headers = {
                            'Content-Type': 'application/json'
                        }

                        response = requests.post(
                            f"{FLASK_SERVER_URL}/update_table",
                            json=data,
                            headers=headers
                        )

                        # После успешной отправки данных на сервер
                        if response.status_code == 200:
                            response_data = response.json()
                            if response_data.get('status') == 'success':
                                print(f"Данные успешно отправлены на сервер для даты {selected_date}")

                                # Здесь добавьте код для автоматического обновления таблицы
                                # Например, вы можете вызвать функцию updateTable() через AJAX
                                try:
                                    update_response = requests.get(f"{FLASK_SERVER_URL}/get_table_data", params={'date': selected_date})
                                    if update_response.status_code == 200:
                                        print("Таблица успешно обновлена.")
                                    else:
                                        print("Ошибка при обновлении таблицы.")
                                except Exception as e:
                                    print(f"Ошибка при обновлении таблицы: {e}")
                            else:
                                print(f"Ошибка на сервере: {response_data.get('message', 'Неизвестная ошибка')}")
                        else:
                            print(f"Ошибка при отправке данных на сервер, код {response.status_code}")

                    except requests.exceptions.ConnectionError:
                        print("Ошибка подключения к серверу. Сервер недоступен.")
                        print("Данные будут сохранены локально")
                        # Здесь можно добавить логику сохранения данных локально
                    except Exception as e:
                        print(f"Ошибка при отправке данных: {e}")
                        print("Данные будут сохранены локально")
                else:
                    print("Нет подключенных USB накопителей!")

            time.sleep(0.1)  # Короткая задержка для предотвращения перегрузки CPU

        except KeyboardInterrupt:
            print("\nПрограмма завершена пользователем.")
            break
        except Exception as e:
            print(f"Произошла ошибка: {e}")

try:
    # Запуск heartbeat в отдельном потоке
    heartbeat_thread = threading.Thread(target=heartbeat, daemon=True)
    heartbeat_thread.start()

    # Запуск слушателя GPIO в отдельном потоке
    gpio_thread = threading.Thread(target=gpio_listener, daemon=True)
    gpio_thread.start()

    # Начальная регистрация устройства
    try:
        usb_connected_initial = is_usb_connected()
        if usb_connected_initial:
            copy_success_initial = copy_files_to_usb()
            initial_status = 'online' if (copy_success_initial and check_internet()) else 'error_copying'
            internet_initial = check_internet() if copy_success_initial else False
        else:
            initial_status = 'offline'
            internet_initial = False

        response = requests.post(
            f"{FLASK_SERVER_URL}/register_user",
            json={
                'worker_name': worker_name,
                'internet_up': internet_initial,
                'status': initial_status
            },
            headers={'Content-Type': 'application/json'}
        )
        if response.status_code == 200:
            print("Успешная регистрация на сервере")
        else:
            print(f"Ошибка при регистрации на сервере, код {response.status_code}")
    except Exception as e:
        print(f"Ошибка при начальной регистрации: {e}")

    # Основной цикл программы
    while True:
        time.sleep(1)  # Основной поток может быть пустым, т.к. все работают в отдельных потоках

except KeyboardInterrupt:
    print("\nПрограмма завершена пользователем.")

finally:
    # Очистка настроек GPIO при выходе
    GPIO.cleanup()
    print("GPIO очищены")