commit c7dc4d6c02c227905f1fce058dd8c359a2fc6fc7 Author: v6ole Date: Fri Aug 8 10:38:10 2025 +0800 开始 diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..498579b --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,23 @@ +# 所有对话和文档都使用中文 +# 文档使用 markdown 格式 +# 该项目的python虚拟环境在"/mnt/nfs/.venv/bin/activate"目录下 + + +## 通用礼节 (General Etiquette) +- 优先保证代码简洁易懂。- 别搞过度设计,简单实用就好。 +- 写代码时,要注意圈复杂度,函数尽量小,尽量可以复用,尽量不写重复代码。 +- 写代码时,注意模块设计,尽量使用设计模式。 +- 给我解释代码的时候,说人话,别拽专业术语。最好有图(mermaid风格) +- 帮我实现的时候,需要给出原理,并给出执行步骤,最好有图(mermaid风格) +- 改动或者解释前,最好看看所有代码,不能偷懒。 +- 改动前,要做最小化修改,尽量不修改到其他模块的代码- 改动后,假定10条case 输入,并给出预期结果。 + +# 实验性规则 (Experimental Rule) +当你被要求修复一个 Bug 时,请遵循以下步骤: +1.  **理解问题 (Understand):** 仔细阅读 Bug 描述和相关代码,复述你对问题的理解。 +2.  **分析原因 (Analyze):** 提出至少两种可能的根本原因。 +3.  **制定计划 (Plan):** 描述你打算如何验证这些原因,并给出修复方案。 +4.  **请求确认 (Confirm):** 在动手修改前,向我确认你的计划。 +5.  **执行修复 (Execute):** 实施修复。 +6.  **审查 (Review):** 查看自己的修改有没有问题。 +7.  **解释说明 (Explain):** 解释你做了哪些修改以及为什么。 \ No newline at end of file diff --git a/4an-stop.sh b/4an-stop.sh new file mode 100755 index 0000000..6559b48 --- /dev/null +++ b/4an-stop.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# 激活虚拟环境 +VENV_PATH="/mnt/nfs/.venv" +if [ -d "$VENV_PATH" ]; then + source "$VENV_PATH/bin/activate" +else + echo "虚拟环境路径不存在:$VENV_PATH" + exit 1 +fi + +# 定义 uWSGI 的启动配置文件路径 +INI_FILE="/opt/1panel/docker/compose/4an/app/uwsgi.ini" + +# 定义 uWSGI 的 PID 文件路径 +PID_FILE="/opt/1panel/docker/compose/4an/uwsgi.pid" + +# 定义日志文件路径 +LOG_FILE="/opt/1panel/docker/compose/4an/uwsgi.log" + +# 检查配置文件是否存在 +if [ ! -f "$INI_FILE" ]; then + echo "uWSGI 配置文件不存在:$INI_FILE" + exit 1 +fi + +# 检查 PID 文件是否存在 +if [ -f "$PID_FILE" ]; then + echo "停止 uWSGI 服务..." + uwsgi --stop "$PID_FILE" + if [ $? -eq 0 ]; then + echo "uWSGI 服务已成功停止。" + else + echo "停止 uWSGI 服务失败,请检查 PID 文件:$PID_FILE" + exit 1 + fi +else + echo "PID 文件不存在,跳过停止服务步骤。" +fi \ No newline at end of file diff --git a/4an.sh b/4an.sh new file mode 100755 index 0000000..9361d9a --- /dev/null +++ b/4an.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# 激活虚拟环境 +VENV_PATH="/mnt/nfs/.venv" +if [ -d "$VENV_PATH" ]; then + source "$VENV_PATH/bin/activate" +else + echo "虚拟环境路径不存在:$VENV_PATH" + exit 1 +fi + +# 定义 uWSGI 的启动配置文件路径 +INI_FILE="/opt/1panel/docker/compose/4an/app/uwsgi.ini" + +# 定义 uWSGI 的 PID 文件路径 +PID_FILE="/opt/1panel/docker/compose/4an/uwsgi.pid" + +# 定义日志文件路径 +LOG_FILE="/opt/1panel/docker/compose/4an/uwsgi.log" + +# 检查配置文件是否存在 +if [ ! -f "$INI_FILE" ]; then + echo "uWSGI 配置文件不存在:$INI_FILE" + exit 1 +fi + +# 检查 PID 文件是否存在 +if [ -f "$PID_FILE" ]; then + echo "停止 uWSGI 服务..." + uwsgi --stop "$PID_FILE" + if [ $? -eq 0 ]; then + echo "uWSGI 服务已成功停止。" + else + echo "停止 uWSGI 服务失败,请检查 PID 文件:$PID_FILE" + exit 1 + fi +else + echo "PID 文件不存在,跳过停止服务步骤。" +fi + +# 等待 2 秒,确保服务已完全停止 +sleep 2 + +# 启动 uWSGI 服务 +echo "启动 uWSGI 服务..." +uwsgi --ini "$INI_FILE" --pidfile "$PID_FILE" --logto "$LOG_FILE" +if [ $? -eq 0 ]; then + echo "uWSGI 服务已成功启动。" +else + echo "uWSGI 服务启动失败,请检查日志:$LOG_FILE" + exit 1 +fi + +# 检查 PID 文件是否重新生成 +if [ -f "$PID_FILE" ]; then + echo "uWSGI 服务已成功重启。" +else + echo "uWSGI 服务启动失败,PID 文件未生成。" + exit 1 +fi diff --git a/4an_deployment_guide.md b/4an_deployment_guide.md new file mode 100755 index 0000000..f07f683 --- /dev/null +++ b/4an_deployment_guide.md @@ -0,0 +1,162 @@ +# 4AN项目部署指南 + +## 环境变量配置 + +配置文件`app/config.py`已更新为支持环境变量。所有配置项可通过环境变量设置,包括: + +- `SECRET_KEY`: 应用密钥 +- `DATABASE_URI`: 数据库连接URI +- `LZMX_WEBHOOK_URL`: 临猪消息Webhook +- `UPLOAD_FOLDER`: 文件上传目录 +- `KEY110_DB_HOST`: 1key110数据库主机 +- `KEY110_DB_USER`: 1key110数据库用户 +- `KEY110_DB_PASSWORD`: 1key110数据库密码 +- `KEY110_DB_DATABASE`: 1key110数据库名称 + +## 使用Supervisor管理项目 + +### 安装依赖 + +项目需要安装python-dotenv包以支持环境变量: + +```bash +/mnt/nfs/.venv/bin/pip install python-dotenv +``` + +### 环境变量文件 + +我们已创建环境变量文件 `/opt/1panel/docker/compose/4an/4an.env`,其中包含所有配置参数: + +``` +# 应用密钥 +SECRET_KEY= + +# 数据库连接 +DATABASE_URI=mysql+pymysql://4an:GtbJp6Ai5azntnTB@10.10.10.253/4an + +# 临猪消息Webhook +LZMX_WEBHOOK_URL=http://imtwo.zdxlz.com/im-external/v1/webhook/send?key=ef1dc605827f425e96491ce5725b5274 + +# 文件上传目录 +UPLOAD_FOLDER=static/uploads + +# 1key110数据库配置 +KEY110_DB_HOST=10.10.10.253 +KEY110_DB_USER=1key110 +KEY110_DB_PASSWORD=KzdKtf7KfkXe7iCN +KEY110_DB_DATABASE=1key110 +``` + +在启动应用前需要加载这些环境变量: + +```bash +# 加载环境变量文件 +export $(cat /opt/1panel/docker/compose/4an/4an.env | grep -v '^#' | xargs) +``` + +### 配置Supervisor + +1. 确保修改app/config.py使其支持环境变量后,再使用Supervisor管理应用 + +2. 将`4an_supervisor.conf`文件复制到Supervisor配置目录: + +```bash +sudo cp 4an_supervisor.conf /etc/supervisor/conf.d/ +``` + +3. 在配置文件目录中创建一个启动脚本,用于加载环境变量并启动应用: + +```bash +sudo nano /opt/1panel/docker/compose/4an/start_with_env.sh +``` + +脚本内容: + +```bash +#!/bin/bash +# 加载环境变量 +export $(cat /opt/1panel/docker/compose/4an/4an.env | grep -v '^#' | xargs) + +# 启动uwsgi +exec /mnt/nfs/.venv/bin/uwsgi --ini /opt/1panel/docker/compose/4an/app/uwsgi.ini +``` + +设置脚本权限: + +```bash +sudo chmod +x /opt/1panel/docker/compose/4an/start_with_env.sh +``` + +4. 修改Supervisor配置使用该脚本: + +```bash +sudo nano /etc/supervisor/conf.d/4an_supervisor.conf +``` + +将command行修改为: + +``` +command=/opt/1panel/docker/compose/4an/start_with_env.sh +``` + +5. 更新Supervisor配置: + +```bash +sudo supervisorctl reread +sudo supervisorctl update +``` + +6. 管理4an服务: + +```bash +# 启动服务 +sudo supervisorctl start 4an + +# 停止服务 +sudo supervisorctl stop 4an + +# 重启服务 +sudo supervisorctl restart 4an + +# 查看服务状态 +sudo supervisorctl status 4an + +# 查看日志 +sudo tail -f /opt/1panel/docker/compose/4an/supervisor.log +``` + +## 使用systemd(可选) + +如果需要使用systemd而不是Supervisor,可以创建服务文件: + +```bash +sudo nano /etc/systemd/system/4an.service +``` + +服务文件内容示例: + +```ini +[Unit] +Description=4AN Application +After=network.target + +[Service] +User=root +WorkingDirectory=/opt/1panel/docker/compose/4an/app +ExecStart=/mnt/nfs/.venv/bin/uwsgi --ini /opt/1panel/docker/compose/4an/app/uwsgi.ini +Restart=always +StandardOutput=file:/opt/1panel/docker/compose/4an/systemd.log +StandardError=file:/opt/1panel/docker/compose/4an/systemd_error.log +EnvironmentFile=/opt/1panel/docker/compose/4an/4an.env + +[Install] +WantedBy=multi-user.target +``` + +然后启用并启动服务: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable 4an +sudo systemctl start 4an +``` \ No newline at end of file diff --git a/4an_supervisor.conf b/4an_supervisor.conf new file mode 100755 index 0000000..f4def63 --- /dev/null +++ b/4an_supervisor.conf @@ -0,0 +1,14 @@ +[program:4an] +command=/mnt/nfs/.venv/bin/uwsgi --ini /opt/1panel/docker/compose/4an/app/uwsgi.ini +directory=/opt/1panel/docker/compose/4an/app +user=root +autostart=true +autorestart=true +stopasgroup=true +killasgroup=true +redirect_stderr=true +stdout_logfile=/opt/1panel/docker/compose/4an/supervisor.log +stdout_logfile_maxbytes=10MB +stdout_logfile_backups=5 +environment=PYTHONPATH="/opt/1panel/docker/compose/4an/app" +environment_file=/opt/1panel/docker/compose/4an/.env \ No newline at end of file diff --git a/app/.env b/app/.env new file mode 100755 index 0000000..9afdeec --- /dev/null +++ b/app/.env @@ -0,0 +1,36 @@ +# 应用密钥 +SECRET_KEY=kU/W5y3uFbWuK1x+RTTOPgLp38yM6Z7d + +# 数据库连接 +DATABASE_URI=mysql+pymysql://4an:GtbJp6Ai5azntnTB@10.10.10.253/4an + +# 量子密信消息Webhook +LZMX_WEBHOOK_URL=http://imtwo.zdxlz.com/im-external/v1/webhook/send?key=ef1dc605827f425e96491ce5725b5274 + +# 文件上传目录 +UPLOAD_FOLDER=static/uploads + +# 1key110数据库配置 +KEY110_DB_HOST=10.10.10.253 +KEY110_DB_USER=1key110 +KEY110_DB_PASSWORD=KzdKtf7KfkXe7iCN +KEY110_DB_DATABASE=1key110 + +# uWSGI基本配置 +UWSGI_HTTP=0.0.0.0:18019 +UWSGI_PROCESSES=4 +UWSGI_THREADS=2 +UWSGI_LOG_FILE=/opt/1panel/docker/compose/4an/uwsgi.log +UWSGI_VIRTUALENV=/mnt/nfs/.venv +UWSGI_CHDIR=/opt/1panel/docker/compose/4an/app +UWSGI_PID_FILE=/opt/1panel/docker/compose/4an/uwsgi.pid +UWSGI_SOCKET=/opt/1panel/docker/compose/4an/uwsgi.sock + +# uWSGI性能优化配置 +UWSGI_MASTER=true +UWSGI_HARAKIRI=30 +UWSGI_MAX_REQUESTS=1000 +UWSGI_BUFFER_SIZE=32768 +UWSGI_DISABLE_LOGGING=true +UWSGI_MEMORY_REPORT=true +UWSGI_ENABLE_THREADS=true \ No newline at end of file diff --git a/app/__pycache__/app.cpython-312.pyc b/app/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000..d986838 Binary files /dev/null and b/app/__pycache__/app.cpython-312.pyc differ diff --git a/app/__pycache__/config.cpython-312.pyc b/app/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..1e540bd Binary files /dev/null and b/app/__pycache__/config.cpython-312.pyc differ diff --git a/app/app.py b/app/app.py new file mode 100755 index 0000000..e8cb1cb --- /dev/null +++ b/app/app.py @@ -0,0 +1,95 @@ +from flask import Flask, redirect, url_for +from flask_login import LoginManager +from flask_migrate import Migrate +from scripts.models import db, User, Device, WorkOrder, WorkOrderHandling, ManualWorkOrder +from config import Config +from views.auth import init_auth_routes +from views.work_order import init_work_order_routes +from views.statistics import init_statistics_routes +from views.history import init_history_routes +from views.manual import init_manual_routes +from views.device import init_device_routes +from views.about import init_about_routes +from views.dashboard import init_dashboard_routes +import logging +from logging.handlers import RotatingFileHandler +import os +from utils.error_handlers import ErrorHandler + +def create_app(): + # 创建应用实例 + app = Flask(__name__) + app.config.from_object(Config) + + # 配置日志记录 + if not os.path.exists('logs'): + os.makedirs('logs') + + # 创建日志处理器,设置最大文件大小为10MB,保留5个备份文件 + file_handler = RotatingFileHandler( + 'logs/app.log', + maxBytes=10*1024*1024, # 10MB + backupCount=5, + encoding='utf-8' + ) + + # 设置日志格式 + formatter = logging.Formatter( + '%(asctime)s %(levelname)s: %(message)s', + '%Y-%m-%d %H:%M:%S' + ) + file_handler.setFormatter(formatter) + + # 设置日志级别 + file_handler.setLevel(logging.INFO) + app.logger.addHandler(file_handler) + app.logger.setLevel(logging.INFO) + app.logger.info('应用启动') + + # 初始化数据库 + db.init_app(app) + migrate = Migrate(app, db) + + # 初始化异常处理器 + error_handler = ErrorHandler(app) + + # 初始化登录管理器 + login_manager = LoginManager() + login_manager.init_app(app) + login_manager.login_view = 'login' + + @login_manager.user_loader + def load_user(user_id): + try: + return db.session.get(User, user_id) + except Exception as e: + app.logger.error(f"加载用户时出错: {str(e)}") + return None + + # 注册路由 + @app.route('/') + def index(): + return redirect(url_for('dashboard')) + + # 初始化各模块路由 + init_auth_routes(app) + init_work_order_routes(app) + init_statistics_routes(app) + init_history_routes(app) + init_manual_routes(app) + init_device_routes(app) + init_about_routes(app) + init_dashboard_routes(app) + + return app + +app = create_app() + +if __name__ == '__main__': + with app.app_context(): + try: + db.create_all() + app.logger.info("数据库表创建成功") + except Exception as e: + app.logger.error(f"创建数据库表时出错: {str(e)}") + app.run(host='0.0.0.0', port=18061, debug=True) \ No newline at end of file diff --git a/app/config.py b/app/config.py new file mode 100755 index 0000000..343c13e --- /dev/null +++ b/app/config.py @@ -0,0 +1,25 @@ +import os +from dotenv import load_dotenv + +# 加载.env文件 (相对于应用根目录) +load_dotenv() + + +class Config: + """配置类,支持从环境变量获取配置 + 环境变量优先级高于默认值,可在系统环境变量中设置这些参数 + """ + SECRET_KEY = os.environ.get('SECRET_KEY', os.urandom(24)) + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') + LZMX_WEBHOOK_URL = os.environ.get('LZMX_WEBHOOK_URL') + SQLALCHEMY_TRACK_MODIFICATIONS = False + UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', 'static/uploads') + ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'} + + # 添加1key110数据库配置 + KEY110_DB = { + 'host': os.environ.get('KEY110_DB_HOST'), + 'user': os.environ.get('KEY110_DB_USER'), + 'password': os.environ.get('KEY110_DB_PASSWORD'), + 'database': os.environ.get('KEY110_DB_DATABASE') + } \ No newline at end of file diff --git a/app/logs/app.log b/app/logs/app.log new file mode 100755 index 0000000..be54495 --- /dev/null +++ b/app/logs/app.log @@ -0,0 +1,594 @@ +2025-04-23 09:18:08 INFO: 应用启动 +2025-04-23 09:18:19 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-04-23 09:18:34 INFO: 用户 韦矍森 访问统计页面 +2025-04-23 09:18:35 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-23 09:18:36 INFO: 用户 韦矍森 访问统计页面 +2025-04-23 09:18:38 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-23 09:18:42 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-23 09:18:44 INFO: 用户 韦矍森 访问统计页面 +2025-04-23 09:18:45 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-23 09:18:46 INFO: 用户 韦矍森 访问统计页面 +2025-04-23 10:56:36 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-23 10:56:49 INFO: 用户 韦矍森 访问统计页面 +2025-04-23 11:05:57 INFO: 用户 韦矍森 开始同步离线设备 +2025-04-23 11:05:57 INFO: 同步完成:创建了 77 个工单,恢复了 0 个设备 +2025-04-23 11:05:59 INFO: 用户请求验证码 +2025-04-23 11:05:59 INFO: 验证码获取成功 +2025-04-23 11:06:04 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-04-23 11:06:04 INFO: 尝试登录中兴平台 +2025-04-23 11:06:04 INFO: 登录成功,开始获取在线信息 +2025-04-23 11:06:04 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-04-23 11:06:04 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 589 个 +2025-04-23 11:06:05 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-04-23 11:06:05 INFO: 查询到 1294 个需要同步的设备 +2025-04-23 11:06:06 INFO: 发现 472 个离线设备需要创建工单 +2025-04-23 11:06:07 INFO: 成功创建 472 个工单 +2025-04-23 11:06:42 INFO: 应用启动 +2025-04-23 11:06:47 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-04-23 11:06:58 INFO: 用户 韦矍森 开始同步离线设备 +2025-04-23 11:06:58 INFO: 同步完成:创建了 77 个工单,恢复了 0 个设备 +2025-04-23 11:07:01 INFO: 用户请求验证码 +2025-04-23 11:07:01 INFO: 验证码获取成功 +2025-04-23 11:07:05 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-04-23 11:07:05 INFO: 尝试登录中兴平台 +2025-04-23 11:07:06 INFO: 登录成功,开始获取在线信息 +2025-04-23 11:07:06 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-04-23 11:07:06 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 589 个 +2025-04-23 11:07:09 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-04-23 11:07:09 INFO: 查询到 1107 个需要同步的设备 +2025-04-23 11:07:10 INFO: 发现 308 个离线设备需要创建工单 +2025-04-23 11:07:10 INFO: 成功创建 308 个工单 +2025-04-23 11:07:18 INFO: 用户 韦矍森 访问统计页面 +2025-04-24 10:28:55 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-04-24 10:31:33 INFO: 用户 韦矍森 访问统计页面 +2025-04-24 10:32:55 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-04-24 10:33:16 INFO: 用户 韦矍森 访问统计页面 +2025-04-24 10:33:17 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-24 10:35:18 INFO: 用户 韦矍森 开始同步离线设备 +2025-04-24 10:35:18 INFO: 同步完成:创建了 3 个工单,恢复了 3 个设备 +2025-04-24 16:01:53 INFO: 用户 韦宗兴(15307786493) 登录成功 +2025-04-24 17:25:05 INFO: 用户 韦宗兴 访问统计页面 +2025-04-24 17:25:09 INFO: 用户 韦宗兴 访问统计页面 +2025-04-24 17:25:46 INFO: 用户 韦宗兴 访问统计页面 +2025-04-24 17:29:40 INFO: 用户 韦宗兴 访问统计页面 +2025-04-24 17:32:35 INFO: 用户 韦宗兴 开始创建手工故障单 +2025-04-24 17:32:39 INFO: 用户 韦宗兴 开始创建手工故障单 +2025-04-24 17:32:51 INFO: 用户 韦宗兴 创建手工故障单成功:M202504240001 +2025-04-24 17:32:51 INFO: 用户 韦宗兴 访问历史记录页面 +2025-04-24 17:32:54 INFO: 用户 韦宗兴 访问历史记录页面 +2025-04-24 17:36:32 INFO: 用户 韦宗兴 开始创建手工故障单 +2025-04-24 17:36:36 INFO: 用户 韦宗兴 创建手工故障单成功:M202504240002 +2025-04-24 17:36:36 INFO: 用户 韦宗兴 访问历史记录页面 +2025-04-24 17:36:51 INFO: 用户 韦宗兴 查看工单 M202504240001 的详情 +2025-04-24 17:36:54 INFO: 用户 韦宗兴 查看工单 M202504240001 的详情 +2025-04-25 10:00:56 INFO: 应用启动 +2025-04-28 09:36:26 INFO: 应用启动 +2025-04-29 15:10:25 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-04-29 15:11:42 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-29 15:12:16 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-29 15:12:17 INFO: 用户 韦矍森 访问统计页面 +2025-04-29 15:13:14 INFO: 用户 覃建(18174938515) 登录成功 +2025-04-29 15:14:07 INFO: 用户 韦矍森(19977899008) 登出 +2025-04-29 15:14:12 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-04-29 15:14:19 INFO: 用户 覃建 访问统计页面 +2025-04-29 15:14:42 INFO: 用户 韦矍森 访问历史记录页面 +2025-04-29 15:20:42 INFO: 用户 覃建 开始创建手工故障单 +2025-04-29 15:20:45 INFO: 用户 覃建 创建手工故障单成功:M202504290001 +2025-04-29 15:20:45 INFO: 用户 覃建 访问历史记录页面 +2025-05-03 01:30:02 INFO: 应用启动 +2025-05-07 08:50:16 INFO: 应用启动 +2025-05-08 08:49:44 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-08 08:54:37 INFO: 应用启动 +2025-05-08 09:13:33 INFO: 应用启动 +2025-05-08 09:13:47 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-08 09:13:49 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-08 09:19:29 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-08 09:19:31 INFO: 用户 韦矍森 访问统计页面 +2025-05-08 09:20:48 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-08 09:30:46 INFO: 应用启动 +2025-05-08 09:30:54 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-12 14:59:10 INFO: 用户 覃建(18174938515) 登录成功 +2025-05-12 15:06:39 INFO: 用户 覃建 访问统计页面 +2025-05-12 15:06:57 INFO: 用户 覃建 访问历史记录页面 +2025-05-12 15:07:04 INFO: 用户 覃建 访问统计页面 +2025-05-12 15:08:43 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-12 15:14:27 INFO: 用户 韦矍森 开始同步离线设备 +2025-05-12 15:14:27 INFO: 同步完成:创建了 4 个工单,恢复了 1 个设备 +2025-05-12 15:14:30 INFO: 用户请求验证码 +2025-05-12 15:14:30 INFO: 验证码获取成功 +2025-05-12 15:14:34 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-05-12 15:14:34 INFO: 尝试登录中兴平台 +2025-05-12 15:14:34 INFO: 登录成功,开始获取在线信息 +2025-05-12 15:14:34 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-05-12 15:14:35 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 585 个 +2025-05-12 15:14:47 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-05-12 15:14:47 INFO: 查询到 1107 个需要同步的设备 +2025-05-12 15:14:48 INFO: 发现 21 个离线设备需要创建工单 +2025-05-12 15:14:48 INFO: 成功创建 21 个工单 +2025-05-12 15:15:49 INFO: 用户 覃建 访问统计页面 +2025-05-12 18:04:43 INFO: 应用启动 +2025-05-12 18:06:38 WARNING: 登录失败:用户不存在 (手机号: 199778990008) +2025-05-12 18:06:41 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-12 18:06:43 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-12 18:06:57 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-12 18:06:58 INFO: 用户 韦矍森 访问统计页面 +2025-05-12 18:07:25 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-13 08:11:30 INFO: 用户 覃建(18174938515) 登录成功 +2025-05-13 08:11:36 INFO: 用户 覃建 访问历史记录页面 +2025-05-13 11:13:52 INFO: 用户 韦宗兴(15307786493) 登录成功 +2025-05-13 11:32:42 INFO: 用户 韦矍森 访问统计页面 +2025-05-13 11:57:15 INFO: 应用启动 +2025-05-13 11:57:24 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-13 13:05:12 INFO: 用户 韦宗兴(15307786493) 登录成功 +2025-05-14 17:45:07 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:45:35 INFO: 用户 韦矍森 检查设备工单状态,设备数量:4 +2025-05-14 17:45:35 INFO: 发现 0 个设备存在故障工单 +2025-05-14 17:45:54 INFO: 用户 韦矍森 检查设备工单状态,设备数量:3 +2025-05-14 17:45:54 INFO: 发现 0 个设备存在故障工单 +2025-05-14 17:46:34 INFO: 用户 易靖欢(18174939957) 登录成功 +2025-05-14 17:49:50 INFO: 用户 易靖欢(18174939957) 登出 +2025-05-14 17:50:06 INFO: 用户 黄建(19977818669) 登录成功 +2025-05-14 17:51:52 INFO: 用户 黄建 访问历史记录页面 +2025-05-14 17:51:54 INFO: 用户 黄建 访问统计页面 +2025-05-14 17:51:58 INFO: 用户 黄建(19977818669) 登出 +2025-05-14 17:52:02 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-14 17:52:04 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:52:05 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:53:39 INFO: 用户 韦矍森 导出月度统计报表 +2025-05-14 17:54:13 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:54:18 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:54:59 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:57:08 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:57:46 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:57:50 INFO: 用户 韦矍森 查看工单 202505140007 的详情 +2025-05-14 17:57:53 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:57:54 INFO: 用户 韦矍森 查看工单 202505140005 的详情 +2025-05-14 17:57:54 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:57:55 INFO: 用户 韦矍森 查看工单 202505140001 的详情 +2025-05-14 17:57:57 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:57:58 INFO: 用户 韦矍森 查看工单 202505120022 的详情 +2025-05-14 17:58:13 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:58:18 INFO: 用户 韦矍森 查看工单 M202504240002 的详情 +2025-05-14 17:58:24 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:58:26 INFO: 用户 韦矍森 查看工单 M202504220001 的详情 +2025-05-14 17:58:28 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:58:29 INFO: 用户 韦矍森 查看工单 M202504220001 的详情 +2025-05-14 17:58:32 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:58:33 INFO: 用户 韦矍森 查看工单 202504230324 的详情 +2025-05-14 17:58:49 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:58:53 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:59:08 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-14 17:59:09 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 17:59:19 INFO: 用户 韦矍森 访问统计页面 +2025-05-14 18:04:25 INFO: 用户 韦矍森 检查设备工单状态,设备数量:1 +2025-05-14 18:04:25 INFO: 发现 0 个设备存在故障工单 +2025-05-14 18:10:19 INFO: 应用启动 +2025-05-14 18:10:30 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-15 14:44:38 INFO: 用户 韦矍森 访问统计页面 +2025-05-19 21:47:13 INFO: 用户 韦矍森 访问统计页面 +2025-05-19 21:47:33 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-19 21:47:39 INFO: 用户 韦矍森 访问统计页面 +2025-05-19 21:49:07 INFO: 用户 韦矍森 导出月度统计报表 +2025-05-19 23:58:35 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-19 23:58:40 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-19 23:58:42 INFO: 用户 韦矍森 访问统计页面 +2025-05-19 23:58:51 INFO: 用户 韦矍森 导出月度统计报表 +2025-05-20 00:07:56 INFO: 应用启动 +2025-05-20 00:08:02 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-20 00:08:04 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-20 00:08:06 INFO: 用户 韦矍森 访问统计页面 +2025-05-20 00:08:06 ERROR: Exception on /statistics [GET] +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app + response = self.full_dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request + rv = self.handle_user_exception(e) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/utils.py", line 290, in decorated_view + return current_app.ensure_sync(func)(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/opt/1panel/docker/compose/4an/app/views/statistics.py", line 179, in statistics + return render_template('statistics.html', + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/templating.py", line 150, in render_template + return _render(app, template, context) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/templating.py", line 131, in _render + rv = template.render(context) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/jinja2/environment.py", line 1304, in render + self.environment.handle_exception() + File "/mnt/nfs/.venv/lib/python3.12/site-packages/jinja2/environment.py", line 939, in handle_exception + raise rewrite_traceback_stack(source=source) + File "/opt/1panel/docker/compose/4an/app/templates/statistics.html", line 1, in top-level template code + {% extends "base.html" %} + File "/opt/1panel/docker/compose/4an/app/templates/base.html", line 108, in top-level template code + {% block content %}{% endblock %} + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/opt/1panel/docker/compose/4an/app/templates/statistics.html", line 74, in block 'content' + 📥︎ 导出本月明细 + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1121, in url_for + return self.handle_url_build_error(error, endpoint, values) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1110, in url_for + rv = url_adapter.build( # type: ignore[union-attr] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/werkzeug/routing/map.py", line 924, in build + raise BuildError(endpoint, values, method, self) +werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'export_monthly_stats'. Did you forget to specify values ['month', 'year']? +2025-05-20 00:11:14 INFO: 应用启动 +2025-05-20 00:11:17 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-20 00:11:19 INFO: 用户 韦矍森 访问统计页面 +2025-05-20 00:11:24 INFO: 用户 韦矍森 导出 2025年1月 统计报表 +2025-05-20 00:11:24 INFO: 用户 韦矍森 访问统计页面 +2025-05-20 00:11:31 INFO: 用户 韦矍森 导出 2025年4月 统计报表 +2025-05-20 00:13:12 INFO: 用户 韦矍森 访问统计页面 +2025-05-20 00:13:13 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-20 15:21:10 INFO: 用户 韦矍森 开始同步离线设备 +2025-05-20 15:21:11 INFO: 同步完成:创建了 71 个工单,恢复了 0 个设备 +2025-05-20 15:25:19 INFO: 用户 唐运莹(18176136697) 登录成功 +2025-05-20 17:17:14 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-22 08:40:07 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-22 08:40:12 INFO: 用户 韦矍森 访问统计页面 +2025-05-27 08:48:59 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-05-27 08:53:47 INFO: 用户 韦矍森 开始同步离线设备 +2025-05-27 08:53:47 INFO: 同步完成:创建了 6 个工单,恢复了 3 个设备 +2025-05-27 08:53:59 INFO: 用户 韦矍森 访问统计页面 +2025-05-27 08:54:00 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-27 14:49:09 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-28 09:48:26 INFO: 用户 韦矍森 访问历史记录页面 +2025-05-29 08:39:59 INFO: 用户 韦矍森 访问历史记录页面 +2025-06-03 01:30:03 INFO: 应用启动 +2025-06-15 15:48:55 INFO: 应用启动 +2025-06-17 09:43:15 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-06-17 09:43:30 INFO: 用户 韦矍森 访问历史记录页面 +2025-06-17 09:43:31 INFO: 用户 韦矍森 访问统计页面 +2025-06-17 09:43:52 INFO: 用户 韦矍森 开始同步离线设备 +2025-06-17 09:43:53 INFO: 同步完成:创建了 11 个工单,恢复了 4 个设备 +2025-06-17 09:43:55 INFO: 用户请求验证码 +2025-06-17 09:43:55 INFO: 验证码获取成功 +2025-06-17 09:44:02 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-06-17 09:44:02 INFO: 尝试登录中兴平台 +2025-06-17 09:44:02 INFO: 登录成功,开始获取在线信息 +2025-06-17 09:44:03 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-06-17 09:44:03 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 605 个 +2025-06-17 09:44:07 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-06-17 09:44:07 INFO: 查询到 1107 个需要同步的设备 +2025-06-17 09:44:07 INFO: 发现 25 个离线设备需要创建工单 +2025-06-17 09:44:08 INFO: 成功创建 25 个工单 +2025-06-17 17:52:13 INFO: 应用启动 +2025-06-17 17:54:03 INFO: 应用启动 +2025-06-17 17:54:13 INFO: 应用启动 +2025-06-17 17:55:25 INFO: 应用启动 +2025-06-17 17:55:35 INFO: 应用启动 +2025-06-17 17:55:52 INFO: 应用启动 +2025-06-17 17:56:38 INFO: 应用启动 +2025-06-17 17:58:50 INFO: 应用启动 +2025-06-17 18:00:20 ERROR: Exception on /statistics [GET] +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app + response = self.full_dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request + rv = self.handle_user_exception(e) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/utils.py", line 285, in decorated_view + return current_app.login_manager.unauthorized() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/login_manager.py", line 187, in unauthorized + flash(self.login_message, category=self.login_message_category) + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/helpers.py", line 335, in flash + session["_flashes"] = flashes + ~~~~~~~^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/sessions.py", line 104, in _fail + raise RuntimeError( +RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret. +2025-06-17 18:01:38 INFO: 应用启动 +2025-06-17 18:02:09 ERROR: Exception on /statistics [GET] +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app + response = self.full_dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request + rv = self.handle_user_exception(e) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/utils.py", line 285, in decorated_view + return current_app.login_manager.unauthorized() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/login_manager.py", line 187, in unauthorized + flash(self.login_message, category=self.login_message_category) + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/helpers.py", line 335, in flash + session["_flashes"] = flashes + ~~~~~~~^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/sessions.py", line 104, in _fail + raise RuntimeError( +RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret. +2025-06-17 18:02:12 ERROR: Exception on /statistics [GET] +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app + response = self.full_dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request + rv = self.handle_user_exception(e) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/utils.py", line 285, in decorated_view + return current_app.login_manager.unauthorized() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/login_manager.py", line 187, in unauthorized + flash(self.login_message, category=self.login_message_category) + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/helpers.py", line 335, in flash + session["_flashes"] = flashes + ~~~~~~~^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/sessions.py", line 104, in _fail + raise RuntimeError( +RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret. +2025-06-17 18:03:23 INFO: 应用启动 +2025-06-17 18:05:02 ERROR: Exception on /statistics [GET] +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app + response = self.full_dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request + rv = self.handle_user_exception(e) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/utils.py", line 285, in decorated_view + return current_app.login_manager.unauthorized() + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask_login/login_manager.py", line 187, in unauthorized + flash(self.login_message, category=self.login_message_category) + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/helpers.py", line 335, in flash + session["_flashes"] = flashes + ~~~~~~~^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/sessions.py", line 104, in _fail + raise RuntimeError( +RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret. +2025-06-17 18:05:57 INFO: 应用启动 +2025-06-17 18:06:01 INFO: 应用启动 +2025-06-17 18:07:16 INFO: 应用启动 +2025-06-17 18:08:32 INFO: 应用启动 +2025-06-17 18:08:46 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-06-18 06:51:33 INFO: 应用启动 +2025-06-18 06:52:12 INFO: 应用启动 +2025-06-19 21:16:20 INFO: 应用启动 +2025-06-21 06:04:07 INFO: 应用启动 +2025-06-21 06:04:47 INFO: 应用启动 +2025-06-23 12:12:28 INFO: 应用启动 +2025-07-16 06:19:45 INFO: 应用启动 +2025-07-25 17:02:48 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-07-25 17:02:52 INFO: 用户 韦矍森 开始同步离线设备 +2025-07-25 17:02:52 INFO: 同步完成:创建了 33 个工单,恢复了 7 个设备 +2025-07-25 17:02:55 INFO: 用户请求验证码 +2025-07-25 17:02:55 INFO: 验证码获取成功 +2025-07-25 17:03:00 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-07-25 17:03:00 INFO: 尝试登录中兴平台 +2025-07-25 17:03:00 INFO: 登录成功,开始获取在线信息 +2025-07-25 17:03:00 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-07-25 17:03:01 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 674 个 +2025-07-25 17:03:04 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-07-25 17:03:04 INFO: 查询到 1107 个需要同步的设备 +2025-07-25 17:03:05 INFO: 发现 82 个离线设备需要创建工单 +2025-07-25 17:03:05 INFO: 成功创建 82 个工单 +2025-07-25 17:03:36 INFO: 用户 韦矍森 开始同步离线设备 +2025-07-25 17:03:36 INFO: 同步完成:创建了 107 个工单,恢复了 0 个设备 +2025-07-25 17:04:04 INFO: 用户请求验证码 +2025-07-25 17:04:05 INFO: 验证码获取成功 +2025-07-25 17:04:10 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-07-25 17:04:10 INFO: 尝试登录中兴平台 +2025-07-25 17:04:10 WARNING: 登录中兴平台失败:验证码错误 +2025-07-25 17:04:13 INFO: 用户请求验证码 +2025-07-25 17:04:13 INFO: 验证码获取成功 +2025-07-25 17:04:17 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-07-25 17:04:17 INFO: 尝试登录中兴平台 +2025-07-25 17:04:17 WARNING: 登录中兴平台失败:验证码错误 +2025-07-25 17:04:18 INFO: 用户请求验证码 +2025-07-25 17:04:18 INFO: 验证码获取成功 +2025-07-25 17:04:22 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-07-25 17:04:22 INFO: 尝试登录中兴平台 +2025-07-25 17:04:23 INFO: 登录成功,开始获取在线信息 +2025-07-25 17:04:23 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-07-25 17:04:23 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 674 个 +2025-07-25 17:04:24 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-07-25 17:04:25 INFO: 查询到 1107 个需要同步的设备 +2025-07-25 17:04:25 INFO: 发现 382 个离线设备需要创建工单 +2025-07-25 17:04:26 INFO: 成功创建 382 个工单 +2025-07-25 17:05:07 INFO: 用户 韦矍森 开始同步离线设备 +2025-07-25 17:05:07 INFO: 同步完成:创建了 107 个工单,恢复了 0 个设备 +2025-07-25 17:05:15 INFO: 用户 韦矍森 访问历史记录页面 +2025-07-25 17:05:19 INFO: 用户 韦矍森 访问统计页面 +2025-07-25 17:08:06 INFO: 用户 韦矍森 访问统计页面 +2025-07-25 17:08:09 INFO: 用户 韦矍森 访问历史记录页面 +2025-07-25 17:13:13 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-07-25 17:13:14 INFO: 用户 韦矍森 访问历史记录页面 +2025-07-25 18:13:01 INFO: 应用启动 +2025-07-25 18:13:01 INFO: 错误处理器初始化完成 +2025-07-25 18:13:03 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-25 18:13:12 INFO: 用户 韦矍森 访问历史记录页面 +2025-07-25 18:13:13 INFO: 用户 韦矍森 访问统计页面 +2025-07-25 18:13:14 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-28 08:27:08 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-28 08:29:41 INFO: 用户 韦矍森 开始同步离线设备 +2025-07-28 08:29:41 INFO: 同步完成:创建了 3 个工单,恢复了 0 个设备 +2025-07-28 08:29:43 INFO: 用户请求验证码 +2025-07-28 08:29:43 INFO: 验证码获取成功 +2025-07-28 08:29:52 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-07-28 08:29:52 INFO: 尝试登录中兴平台 +2025-07-28 08:29:52 WARNING: 登录中兴平台失败:验证码错误 +2025-07-28 08:29:53 INFO: 用户请求验证码 +2025-07-28 08:29:53 INFO: 验证码获取成功 +2025-07-28 08:29:58 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-07-28 08:29:58 INFO: 尝试登录中兴平台 +2025-07-28 08:29:59 INFO: 登录成功,开始获取在线信息 +2025-07-28 08:30:00 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-07-28 08:30:00 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 678 个 +2025-07-28 08:30:01 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-07-28 08:30:01 INFO: 查询到 1107 个需要同步的设备 +2025-07-28 08:30:02 INFO: 发现 9 个离线设备需要创建工单 +2025-07-28 08:30:02 INFO: 成功创建 9 个工单 +2025-07-28 08:30:04 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-28 08:30:38 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-28 08:51:47 INFO: 用户 韦矍森 访问统计页面 +2025-07-28 08:51:49 INFO: 用户 韦矍森 访问历史记录页面 +2025-07-28 08:52:02 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-28 08:52:54 INFO: 用户 韦矍森 访问历史记录页面 +2025-07-28 08:52:55 INFO: 用户 韦矍森 访问统计页面 +2025-07-28 08:52:58 INFO: 用户 韦矍森 访问仪表盘页面 +2025-07-28 23:35:22 INFO: 应用启动 +2025-07-28 23:35:22 INFO: 错误处理器初始化完成 +2025-07-28 23:49:56 INFO: 应用启动 +2025-07-28 23:49:56 INFO: 错误处理器初始化完成 +2025-07-30 07:37:35 ERROR: 未处理异常: 405 Method Not Allowed: The method is not allowed for the requested URL. +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 891, in dispatch_request + self.raise_routing_exception(req) + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 500, in raise_routing_exception + raise request.routing_exception # type: ignore[misc] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/ctx.py", line 362, in match_request + result = self.url_adapter.match(return_rule=True) # type: ignore + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/werkzeug/routing/map.py", line 624, in match + raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None +werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL. + +2025-08-05 06:56:05 INFO: 应用启动 +2025-08-05 06:56:05 INFO: 错误处理器初始化完成 +2025-08-05 11:37:35 WARNING: 登录失败:用户不存在 (手机号: 199778999008) +2025-08-05 11:37:39 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-08-05 11:37:39 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-05 11:37:53 INFO: 用户 韦矍森 开始同步离线设备 +2025-08-05 11:37:53 INFO: 同步完成:创建了 105 个工单,恢复了 0 个设备 +2025-08-05 11:37:56 INFO: 用户请求验证码 +2025-08-05 11:37:56 INFO: 验证码获取成功 +2025-08-05 11:37:59 INFO: 用户 韦矍森 开始同步摄像头第一步 +2025-08-05 11:37:59 INFO: 尝试登录中兴平台 +2025-08-05 11:37:59 INFO: 登录成功,开始获取在线信息 +2025-08-05 11:38:00 INFO: 获取到 1436 个设备信息,开始更新数据库 +2025-08-05 11:38:00 INFO: 数据库更新完成,更新了 1436 条记录,其中离线设备 665 个 +2025-08-05 11:38:01 INFO: 用户 韦矍森 开始同步摄像头第二步 +2025-08-05 11:38:01 INFO: 查询到 1107 个需要同步的设备 +2025-08-05 11:38:02 INFO: 发现 378 个离线设备需要创建工单 +2025-08-05 11:38:02 INFO: 成功创建 378 个工单 +2025-08-05 11:38:05 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-05 11:38:42 INFO: 用户 韦矍森 访问历史记录页面 +2025-08-05 11:38:44 INFO: 用户 韦矍森 访问统计页面 +2025-08-06 08:31:05 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-06 12:46:40 INFO: 应用启动 +2025-08-06 12:46:40 INFO: 错误处理器初始化完成 +2025-08-07 16:39:57 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-08-07 16:39:57 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 09:54:17 INFO: 用户 韦矍森(19977899008) 登录成功 +2025-08-08 09:54:18 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 09:54:30 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 09:54:43 INFO: 用户 韦矍森 访问历史记录页面 +2025-08-08 10:12:48 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:13:00 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:13:04 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:13:13 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:17 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:13:17 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:20 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:28 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:28 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:30 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:30 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:31 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:13:31 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:24 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:24 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:26 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:26 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:28 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:28 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:29 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:29 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:17:39 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:17:41 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:17:57 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:18:06 INFO: 用户 韦矍森 访问历史记录页面 +2025-08-08 10:18:20 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:21:25 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:26:58 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:27:02 INFO: 用户 韦矍森 访问历史记录页面 +2025-08-08 10:27:08 INFO: 用户 韦矍森 访问仪表盘页面 +2025-08-08 10:29:37 INFO: 应用启动 +2025-08-08 10:29:37 INFO: 错误处理器初始化完成 +2025-08-08 10:30:45 INFO: 应用启动 +2025-08-08 10:30:45 INFO: 错误处理器初始化完成 +2025-08-08 10:33:52 INFO: 应用启动 +2025-08-08 10:33:52 INFO: 错误处理器初始化完成 +2025-08-08 10:36:13 ERROR: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"5+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2025-08-08 10:36:13 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:36:17 ERROR: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"6+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2025-08-08 10:36:17 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:36:21 ERROR: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"4+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2025-08-08 10:36:21 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:36:27 ERROR: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"4+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2025-08-08 10:36:27 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:36:33 ERROR: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '一键报警', 'project_type_1': '校园安防"4+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2025-08-08 10:36:33 INFO: 用户 韦矍森 访问统计页面 +2025-08-08 10:36:35 INFO: 用户 韦矍森 访问统计页面 diff --git a/app/logs/zte.log b/app/logs/zte.log new file mode 100755 index 0000000..e69de29 diff --git a/app/scripts/models/__init__.py b/app/scripts/models/__init__.py new file mode 100755 index 0000000..506c23b --- /dev/null +++ b/app/scripts/models/__init__.py @@ -0,0 +1,90 @@ +from flask_sqlalchemy import SQLAlchemy +from flask_login import UserMixin +from datetime import datetime, timezone + +db = SQLAlchemy() + +# 用户表 +class User(UserMixin, db.Model): + __tablename__ = 'user' + phone = db.Column(db.String(20), primary_key=True, comment="手机号码") + name = db.Column(db.String(50), comment="姓名") + branch = db.Column(db.String(50), comment="所属支局") + role = db.Column(db.String(20), comment="角色") + def get_id(self): + return self.phone # 使用手机号码作为唯一标识符 + +# 设备表 +class Device(db.Model): + __tablename__ = 'device' + device_id = db.Column(db.String(100), primary_key=True, comment="设备编号") + town = db.Column(db.String(50), comment="所属乡镇") + branch = db.Column(db.String(50), comment="所属支局") + unit_name = db.Column(db.String(100), comment="单位名称") + project_type = db.Column(db.String(50), comment="项目类型") + account = db.Column(db.String(20), comment="关联宽带账号") + device_type = db.Column(db.String(50), comment="设备类型") + puid = db.Column(db.String(100), comment="通道标识") # 添加 puid 字段 + remark = db.Column(db.String(200), comment="备注") + in_use = db.Column(db.Boolean, default=True, comment="是否在用") + is_normal = db.Column(db.Boolean, default=True, comment="是否正常") + +# 工单表 +class WorkOrder(db.Model): + __tablename__ = 'work_order' + order_id = db.Column(db.String(20), primary_key=True, comment="工单号(日期+编号)") + device_id = db.Column(db.String(100), db.ForeignKey('device.device_id'), comment="设备编号") # 从20改为100 + dispatch_time = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), comment="派单时间") + status = db.Column(db.String(20), default='待处理', comment="工单状态") + branch = db.Column(db.String(50), comment="所属支局") + # 与设备表的关系 + device = db.relationship('Device', backref='work_orders') + +# 处理工单信息表 +class WorkOrderHandling(db.Model): + __tablename__ = 'work_order_handling' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + order_id = db.Column(db.String(20), db.ForeignKey('work_order.order_id'), comment="工单号") + device_id = db.Column(db.String(100), db.ForeignKey('device.device_id'), comment="设备编号") # 从20改为100 + branch = db.Column(db.String(50), comment="所属支局") + restore_time = db.Column(db.DateTime, comment="恢复时间") + fault_type = db.Column(db.String(100), comment="故障类型") + contact_person = db.Column(db.String(50), comment="故障对接人") + contact_phone = db.Column(db.String(20), comment="联系电话") + handler = db.Column(db.String(50), comment="故障处理人") + handle_description = db.Column(db.String(500), comment="处理描述") + photo_paths = db.Column(db.String(500), comment="现场图片路径") + distance_from_branch = db.Column(db.Float, comment="距离支局公里数") + remark = db.Column(db.String(500), comment="备注") + # 与工单表的关系 + work_order = db.relationship('WorkOrder', backref='handling_info') + +# 手工故障单表 +class ManualWorkOrder(db.Model): + __tablename__ = 'manual_work_order' + order_id = db.Column(db.String(20), primary_key=True, comment="工单号") + project_type = db.Column(db.String(50), comment="项目类型") + device_id = db.Column(db.String(100), comment="故障设备") + device_location = db.Column(db.String(200), comment="设备位置") + fault_time = db.Column(db.DateTime, comment="故障时间") + handler = db.Column(db.String(50), comment="故障处理人") + fault_type = db.Column(db.String(100), comment="故障类型") + handle_description = db.Column(db.String(500), comment="处理描述") + photo_paths = db.Column(db.String(500), comment="现场图片路径") + contact_person = db.Column(db.String(50), comment="故障对接人") + contact_phone = db.Column(db.String(20), comment="联系电话") + distance_from_branch = db.Column(db.Float, comment="距离支局公里数") + remark = db.Column(db.String(500), comment="备注") + create_time = db.Column(db.DateTime, default=datetime.now, comment="创建时间") + branch = db.Column(db.String(50), comment="所属支局") + +class Channel(db.Model): + __tablename__ = 'channels' + + puid = db.Column(db.String(50), primary_key=True) + pu_name = db.Column(db.String(200)) + status = db.Column(db.String(20)) + update_time = db.Column(db.String(50)) + + def __repr__(self): + return f'' diff --git a/app/scripts/models/__pycache__/__init__.cpython-312.pyc b/app/scripts/models/__pycache__/__init__.cpython-312.pyc new file mode 100755 index 0000000..55da567 Binary files /dev/null and b/app/scripts/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/scripts/models/__pycache__/__init__.cpython-313.pyc b/app/scripts/models/__pycache__/__init__.cpython-313.pyc new file mode 100755 index 0000000..e7b22ce Binary files /dev/null and b/app/scripts/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/scripts/update/__pycache__/zte.cpython-312.pyc b/app/scripts/update/__pycache__/zte.cpython-312.pyc new file mode 100755 index 0000000..341b2ea Binary files /dev/null and b/app/scripts/update/__pycache__/zte.cpython-312.pyc differ diff --git a/app/scripts/update/__pycache__/zte.cpython-313.pyc b/app/scripts/update/__pycache__/zte.cpython-313.pyc new file mode 100755 index 0000000..492c5de Binary files /dev/null and b/app/scripts/update/__pycache__/zte.cpython-313.pyc differ diff --git a/app/scripts/update/zte.py b/app/scripts/update/zte.py new file mode 100755 index 0000000..32bcfcf --- /dev/null +++ b/app/scripts/update/zte.py @@ -0,0 +1,269 @@ +import requests +import os +import time +from datetime import datetime +import hashlib +import uuid +from sqlalchemy import select, create_engine, Table, Column, String, MetaData, text +from sqlalchemy.dialects.mysql import insert + +class LoginClient: + def __init__(self): + self.base_url = "https://116.10.244.81:28001" + self.session = requests.Session() + # 禁用SSL验证警告 + requests.packages.urllib3.disable_warnings() + + def get_captcha(self): + """获取验证码图片""" + # 生成随机cid + cid = uuid.uuid4().hex.encode() + cid = hashlib.sha256(cid).hexdigest() + + url = f"{self.base_url}/api/vss-auth-center/v1/captcha" + headers = { + "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0", + "Referer": f"{self.base_url}/vss-em-iui/" + } + params = {"cid": cid} + + response = self.session.get(url, headers=headers, params=params, verify=False) + + # 缓存验证码到本地 + captcha_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + 'static', 'captcha.png') + with open(captcha_path, "wb") as f: + f.write(response.content) + + return cid + + def login(self, account, password, validcode, cid): + """执行登录""" + url = f"{self.base_url}/api/vss-auth-center/v1/user/login" + + headers = { + "Accept": "application/json, text/plain, */*", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0", + "Referer": f"{self.base_url}/vss-em-iui/", + "language": "zh-CN" + } + + params = { + "cutype": "12" + } + + data = { + "account": account, + "password": password, + "validcode": validcode, + "cid": cid + } + + response = self.session.post( + url, + headers=headers, + params=params, + json=data, + verify=False + ) + + return response.json() + + def get_online_info(self, token, page=1, page_size=1500): + """获取在线信息""" + url = f"{self.base_url}/api/vss-user-business/v1/pus/queryPage/4109" + + headers = { + "Accept": "application/json, text/plain, */*", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "Authoration": token, + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "Content-Type": "application/json", + "Referer": f"{self.base_url}/vss-em-iui/", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0", + "language": "zh-CN" + } + + # 生成防伪标识 + forgery_defense = uuid.uuid4().hex + + params = { + "page_size": page_size, + "page": page, + "pu_name": "", + "puid": "", + "protocol": "", + "nodeid": "", + "popunitid": "", + "status": "", + "forgerydefense": forgery_defense, + "csrftoken": forgery_defense + } + + response = self.session.get( + url, + headers=headers, + params=params, + verify=False + ) + + return response.json() + +def get_existing_channels(engine, channels_table): + """获取数据库中现有的通道状态""" + with engine.connect() as conn: + query = select(channels_table.c.puid, channels_table.c.status) + result = conn.execute(query) + return {row.puid: row.status for row in result} + +def update_device_status(engine): + """更新设备状态""" + with engine.connect() as conn: + # 获取channels表和device表的数据 + channels_query = """ + SELECT puid, status, pu_name + FROM channels + """ + channels_result = conn.execute(text(channels_query)) + channels_data = {row.puid: {'status': row.status, 'pu_name': row.pu_name} for row in channels_result} + + # 获取device表中的puid + device_query = """ + SELECT puid + FROM device + """ + device_result = conn.execute(text(device_query)) + + # 更新设备状态 + updates = [] + for row in device_result: + puid = row.puid + if puid in channels_data: + # 更新在线状态 + is_normal = 1 if channels_data[puid]['status'] == 'online' else 0 + + # 检查使用状态 + pu_name = channels_data[puid]['pu_name'] + in_use = 0 if any(keyword in pu_name for keyword in ['无学生', '丢失']) else 1 + + # 构建更新语句 + update_query = """ + UPDATE device + SET is_normal = :is_normal, in_use = :in_use + WHERE puid = :puid + """ + updates.append({ + 'puid': puid, + 'is_normal': is_normal, + 'in_use': in_use + }) + + if updates: + update_stmt = text(""" + UPDATE device + SET is_normal = :is_normal, in_use = :in_use + WHERE puid = :puid + """) + conn.execute(update_stmt, updates) + conn.commit() + print(f"成功更新 {len(updates)} 个设备状态") + +def main(): + client = LoginClient() + + while True: + cid = client.get_captcha() + print("验证码已保存为captcha.png") + validcode = input("请查看captcha.png并输入验证码: ") + password = "Root1234#" + hashed_password = hashlib.sha256(password.encode()).hexdigest() + + result = client.login( + account="DHDX001", + password=hashed_password, + validcode=validcode, + cid=cid + ) + + if result.get("result") == 0: + token = result.get("token") + + online_info = client.get_online_info(token) + if online_info.get("result") == 0: + rows = online_info.get("rows", []) + filtered_rows = [ + row for row in rows + if "大化4+N摄像头" not in row.get("pu_name", "") + and "大化幼儿园监控" not in row.get("pu_name", "") + ] + + engine = create_engine('mysql+pymysql://4an:GtbJp6Ai5azntnTB@10.10.10.253/4an') + metadata = MetaData() + channels = Table( + 'channels', metadata, + Column('puid', String(50), primary_key=True), + Column('pu_name', String(200)), + Column('status', String(20)), + Column('update_time', String(50)) + ) + metadata.create_all(engine) + + existing_channels = get_existing_channels(engine, channels) + + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + update_data = [] + + for row in filtered_rows: + puid = row.get('puid') + current_status = row.get('status') + + if puid not in existing_channels or existing_channels[puid] != current_status: + + update_data.append({ + 'puid': puid, + 'pu_name': row.get('pu_name'), + 'status': current_status, + 'update_time': current_time + }) + + # 执行更新 + if update_data: + with engine.connect() as conn: + stmt = insert(channels).on_duplicate_key_update( + pu_name=channels.c.pu_name, + status=channels.c.status, + update_time=channels.c.update_time + ) + conn.execute(stmt, update_data) + conn.commit() + print(f"\n成功更新 {len(update_data)} 条记录") + else: + print("\n没有需要更新的记录") + + # 更新设备状态 + print("\n开始更新设备状态...") + update_device_status(engine) + + return + else: + print("获取通道信息失败!") + print(f"错误信息: {online_info.get('err_msg', '未知错误')}") + return # 获取信息失败也退出程序 + + else: + print("登录失败!") + print(result) + if result.get("result") == 12: # 验证码错误 + retry = input("验证码错误,是否重新获取验证码?(y/n): ") + if retry.lower() != 'y': + return + else: + return # 其他错误直接退出 + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/app/scripts/weekly_stats.py b/app/scripts/weekly_stats.py new file mode 100755 index 0000000..92a43b1 --- /dev/null +++ b/app/scripts/weekly_stats.py @@ -0,0 +1,110 @@ +from datetime import datetime, timedelta +from collections import defaultdict +import sys +import os + +# 添加项目根目录到 Python 路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from scripts.models import db, Device, WorkOrder, WorkOrderHandling, ManualWorkOrder +from utils.lzmx import send_weekly_stats_message +from flask import Flask + +# 创建一个简单的 Flask 应用 +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://4an:GtbJp6Ai5azntnTB@10.10.10.253/4an' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +LZMX_WEBHOOK_URL = "http://imtwo.zdxlz.com/im-external/v1/webhook/send?key=ef1dc605827f425e96491ce5725b5274" + +# 初始化数据库 +db.init_app(app) + +def get_reward_by_distance(project_type, distance): + if project_type == '校园安防"4+N"项目': + if distance <= 5: return 20 + elif distance <= 30: return 40 + elif distance <= 50: return 50 + else: return 60 + elif project_type == '综治视联网': + if distance <= 10: return 30 + elif distance <= 30: return 40 + elif distance <= 50: return 50 + else: return 60 + elif project_type == '教育城域网': + if distance <= 5: return 20 + elif distance <= 30: return 30 + elif distance <= 50: return 40 + else: return 50 + elif project_type == '其他': + return 30 + return 0 + +def weekly_stats(): + with app.app_context(): + # 计算本周的开始和结束时间 + now = datetime.now() + week_start = now - timedelta(days=now.weekday()) + week_start = week_start.replace(hour=0, minute=0, second=0, microsecond=0) + week_end = now + + # 统计工单数据 + total_orders = WorkOrder.query.filter( + WorkOrder.dispatch_time >= week_start, + WorkOrder.dispatch_time < week_end + ).count() + + completed_orders = WorkOrder.query.filter( + WorkOrder.dispatch_time >= week_start, + WorkOrder.dispatch_time < week_end, + WorkOrder.status == '已处理' + ).count() + + manual_orders = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= week_start, + ManualWorkOrder.create_time < week_end + ).count() + + # 获取处理人统计 + handler_stats = defaultdict(float) + + # 统计普通工单奖励 + handlings = WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= week_start, + WorkOrderHandling.restore_time < week_end + ).all() + + for handling in handlings: + device = db.session.get(Device, handling.device_id) # 更新为新的方法 + if device: + distance = float(handling.distance_from_branch) + reward = get_reward_by_distance(device.project_type, distance) + handler_stats[handling.handler] += reward + + # 统计手工故障单奖励 + manual_orders_list = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= week_start, + ManualWorkOrder.create_time < week_end + ).all() + + for order in manual_orders_list: + distance = float(order.distance_from_branch) + reward = get_reward_by_distance(order.project_type, distance) + handler_stats[order.handler] += reward + + # 获取前三名 + top_handlers = sorted(handler_stats.items(), key=lambda x: x[1], reverse=True)[:3] + + # 准备统计数据 + stats = { + 'total_orders': total_orders, + 'completed_orders': completed_orders, + 'pending_orders': total_orders - completed_orders, + 'manual_orders': manual_orders, + 'top_handlers': top_handlers + } + + # 发送统计消息 + send_weekly_stats_message(LZMX_WEBHOOK_URL, stats) + +if __name__ == '__main__': + weekly_stats() \ No newline at end of file diff --git a/app/static/captcha.png b/app/static/captcha.png new file mode 100755 index 0000000..f978c92 Binary files /dev/null and b/app/static/captcha.png differ diff --git a/app/static/css/about.css b/app/static/css/about.css new file mode 100755 index 0000000..d95c889 --- /dev/null +++ b/app/static/css/about.css @@ -0,0 +1,121 @@ +.quick-links { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px; + margin: 20px 0; + list-style: none; + padding: 0; +} + +.quick-link-item { + background: white; + border-radius: 12px; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.05); + overflow: hidden; +} + +.quick-link-item:hover { + transform: translateY(-4px); + box-shadow: 0 4px 12px rgba(0,0,0,0.1); +} + +.quick-link { + display: block; + padding: 16px 20px; + color: #2c3e50; + text-decoration: none; + position: relative; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); +} + +.quick-link:hover { + color: #1a73e8; +} + +.quick-link::after { + content: '→'; + position: absolute; + right: 20px; + opacity: 0; + transform: translateX(-10px); + transition: all 0.3s ease; +} + +.quick-link:hover::after { + opacity: 1; + transform: translateX(0); +} +.about-container { + max-width: 800px; + margin: 30px auto; + padding: 40px; + background: #fff; + border-radius: 16px; + box-shadow: 0 4px 20px rgba(0,0,0,0.08); +} + +.about-header { + text-align: center; + margin-bottom: 40px; +} + +.about-header h1 { + color: #2c3e50; + font-size: 2.2em; + margin-bottom: 15px; + font-weight: 600; +} + +.about-content { + line-height: 1.8; +} + +.about-section { + margin-bottom: 35px; +} + +.about-section h2 { + color: #34495e; + font-size: 1.5em; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid #eee; +} + +.feature-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin: 20px 0; +} + +.feature-item { + background: #f8f9fa; + padding: 20px; + border-radius: 12px; + transition: transform 0.2s; +} + +.feature-item:hover { + transform: translateY(-5px); + box-shadow: 0 4px 15px rgba(0,0,0,0.05); +} + +.dev-info { + background: #f8f9fa; + padding: 25px; + border-radius: 12px; + margin-top: 30px; +} + +.dev-info p { + margin: 10px 0; +} + +.footer { + margin-top: 40px; + text-align: center; + color: #7f8c8d; + font-size: 0.9em; +} \ No newline at end of file diff --git a/app/static/css/bootstrap.min.css b/app/static/css/bootstrap.min.css new file mode 100755 index 0000000..1472dec --- /dev/null +++ b/app/static/css/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:.2rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/app/static/css/dashboard.css b/app/static/css/dashboard.css new file mode 100755 index 0000000..97c5402 --- /dev/null +++ b/app/static/css/dashboard.css @@ -0,0 +1,315 @@ +/* 仪表盘容器 */ +.dashboard_container { + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0,0,0,0.05); +} + +/* 仪表盘标题 */ +.dashboard_title { + font-size: 24px; + font-weight: 600; + color: #333; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 1px solid #e9ecef; +} + +/* 仪表盘区块 */ +.dashboard_section { + margin-bottom: 30px; + background: white; + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0,0,0,0.05); +} + +/* 区块标题 */ +.section_title { + font-size: 18px; + font-weight: 600; + color: #333; + margin-bottom: 20px; + display: flex; + align-items: center; +} + +/* 图表容器 */ +.chart_container { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-bottom: 20px; +} + +/* 图表包装器 */ +.chart_wrapper { + flex: 1; + min-width: 300px; + height: 300px; + background: white; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 5px rgba(0,0,0,0.05); +} + +/* 卡片网格布局 */ +.card_grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; +} + +/* 仪表盘卡片 */ +.dashboard_card { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); + transition: transform 0.2s, box-shadow 0.2s; +} + +.dashboard_card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.12); +} + +/* 卡片头部 */ +.card_header { + padding: 15px; + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; +} + +/* 卡片标题 */ +.card_title { + font-size: 16px; + font-weight: 600; + color: #333; + margin: 0; +} + +/* 卡片内容 */ +.card_content { + padding: 15px; +} + +/* 进度条容器 */ +.progress_container { + height: 24px; + background: #e9ecef; + border-radius: 12px; + margin-bottom: 15px; + overflow: hidden; +} + +/* 进度条 */ +.progress_bar { + height: 100%; + background: linear-gradient(90deg, #0061f2 0%, #00a3ff 100%); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + transition: width 0.5s ease; + min-width: 30px; +} + +/* 进度条文本 */ +.progress_text { + color: white; + font-weight: 600; + font-size: 14px; +} + +/* 统计详情 */ +.stats_details { + display: flex; + justify-content: space-between; +} + +/* 统计项 */ +.stats_item { + display: flex; + flex-direction: column; + align-items: center; +} + +/* 统计标签 */ +.stats_label { + font-size: 12px; + color: #6c757d; + margin-bottom: 5px; +} + +/* 统计值 */ +.stats_value { + font-size: 16px; + font-weight: 600; + color: #333; +} + +/* 表格容器 */ +.table_container { + overflow-x: auto; + margin-top: 10px; +} + +/* 仪表盘表格 */ +.dashboard_table { + width: 100%; + border-collapse: collapse; +} + +.dashboard_table th, +.dashboard_table td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #e9ecef; +} + +.dashboard_table th { + background: #f8f9fa; + font-weight: 600; + color: #495057; +} + +.dashboard_table tr:hover { + background: #f8f9fa; +} + +/* 支局单元格 */ +.branch_cell { + font-weight: 600; + background: #f8f9fa; +} + +/* 状态标签 */ +.status_badge { + display: inline-block; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; +} + +.status_badge.good { + background: #d4edda; + color: #155724; +} + +.status_badge.normal { + background: #fff3cd; + color: #856404; +} + +.status_badge.bad { + background: #f8d7da; + color: #721c24; +} + +/* 手风琴容器 */ +.accordion_container { + margin-top: 10px; +} + +/* 手风琴项 */ +.accordion_item { + margin-bottom: 10px; + border: 1px solid #e9ecef; + border-radius: 8px; + overflow: hidden; +} + +/* 手风琴头部 */ +.accordion_header { + padding: 15px; + background: #f8f9fa; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + transition: background 0.2s; +} + +.accordion_header:hover { + background: #e9ecef; +} + +/* 手风琴标题 */ +.accordion_title { + font-size: 16px; + font-weight: 600; + color: #333; +} + +/* 手风琴摘要 */ +.accordion_summary { + display: flex; + align-items: center; + gap: 15px; +} + +/* 手风琴图标 */ +.accordion_icon { + font-size: 12px; + color: #6c757d; + transition: transform 0.2s; +} + +/* 手风琴内容 */ +.accordion_content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; +} + +/* 详情表格 */ +.detail_table { + width: 100%; + border-collapse: collapse; +} + +.detail_table th, +.detail_table td { + padding: 10px 15px; + text-align: left; + border-bottom: 1px solid #e9ecef; +} + +.detail_table th { + background: #f8f9fa; + font-weight: 600; + color: #495057; +} + +.detail_table tr:hover { + background: #f8f9fa; +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .card_grid { + grid-template-columns: 1fr; + } + + .dashboard_table { + min-width: 600px; + } + + .accordion_summary { + flex-direction: column; + align-items: flex-start; + gap: 5px; + } + + .chart_wrapper { + min-width: 100%; + height: 250px; + } + + .chart_container { + flex-direction: column; + } +} \ No newline at end of file diff --git a/app/static/css/dispatch.css b/app/static/css/dispatch.css new file mode 100755 index 0000000..d75866e --- /dev/null +++ b/app/static/css/dispatch.css @@ -0,0 +1,475 @@ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); +} + +.modal-content { + background-color: white; + margin: 15% auto; + padding: 20px; + width: 300px; + text-align: center; +} + +.modal-buttons { + margin-top: 15px; +} + +.modal-buttons button { + margin: 0 10px; + padding: 5px 15px; +} + +/* 备注编辑样式 */ +.remark-display { + cursor: pointer; + color: #666; + transition: color 0.2s; + min-height: 20px; + display: inline-block; + min-width: 80px; +} + +.remark-display:hover { + color: #007bff; +} + +.remark-input { + padding: 4px 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + min-width: 120px; + transition: border-color 0.2s; +} + +.remark-input:focus { + outline: none; + border-color: #007bff; +} + +/* 模态框基础样式 */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + backdrop-filter: blur(5px); +} + +.modal-content { + position: relative; + background-color: #fff; + margin: 15vh auto; + padding: 25px; + width: 90%; + max-width: 500px; + border-radius: 12px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + animation: modalSlideIn 0.3s ease-out; +} + +@keyframes modalSlideIn { + from { + transform: translateY(-30px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.modal-content h3 { + margin: 0 0 20px 0; + color: #333; + font-size: 1.5em; + font-weight: 600; +} + +.modal-content p { + margin: 15px 0; + color: #666; + line-height: 1.5; +} + +.modal-buttons { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 25px; +} + +.modal-buttons button { + padding: 8px 20px; + border: none; + border-radius: 6px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; +} + +.modal-buttons button:first-child { + background-color: #4CAF50; + color: white; +} + +.modal-buttons button:last-child { + background-color: #f5f5f5; + color: #333; +} + +.modal-buttons button:hover { + transform: translateY(-2px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +/* 验证码模态框特殊样式 */ +#captchaModal .modal-content { + text-align: center; +} + +#captchaImage { + margin: 15px 0; + border-radius: 4px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); +} + +#captchaInput { + width: 200px; + padding: 8px 12px; + margin: 15px 0; + border: 2px solid #eee; + border-radius: 6px; + font-size: 16px; + text-align: center; + transition: border-color 0.2s ease; +} + +#captchaInput:focus { + outline: none; + border-color: #4CAF50; +} + +/* 结果显示样式 */ +#updatedCount { + font-weight: bold; + color: #4CAF50; +} + +#resultMessage { + padding: 15px; + background-color: #f8f9fa; + border-radius: 8px; + text-align: center; +} + + +.dispatch_filter_group { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + flex-wrap: nowrap; + gap: 10px; +} + +.dispatch_search_form { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: nowrap; + max-width: 1000px; + width: 100%; +} + +.dispatch_action_group { + display: flex; + gap: 10px; +} +.dispatch_action_bar { + display: flex; + gap: 12px; + justify-content: center; + align-items: center; + width: 100%; +} + +.dispatch_btn { + min-width: 100px; + padding: 8px 15px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; +} + +.dispatch_btn_primary { + background-color: #007bff; + color: white; +} + +.dispatch_btn_primary:hover { + background-color: #5677fc; +} + +.dispatch_btn_secondary_qx { + background-color: #607d8b; + color: white; +} + +.dispatch_btn_secondary_qx:hover { + background-color: #b0bec5; +} + +.dispatch_btn_secondary_fx { + background-color: #9e9e9e; + color: white; +} + +.dispatch_btn_secondary_fx:hover { + background-color: #e0e0e0; +} + +.dispatch_btn_secondary_bj { + background-color: #ff5722; + color: white; +} + +.dispatch_btn_secondary_bj:hover { + background-color: #ff8a65; +} + +.dispatch_btn_secondary_sxt { + background-color: #259b24; + color: white; +} + +.dispatch_btn_secondary_sxt:hover { + background-color: #72d572; +} + +.dispatch_filter { + position: sticky; + top: 0; + background-color: #f8f9fa; + padding: 15px; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + margin-bottom: 10px; + z-index: 2; + display: flex; + justify-content: center; +} + +.dispatch_search { + flex: 1; + min-width: 150px; + max-width: 250px; + padding: 8px 15px; + border: 1px solid #ddd; + border-radius: 4px; +} + + +.dispatch_btn { + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; +} + + + +.dispatch_btn_secondary { + background: #6c757d; + color: white; + margin-left: 10px; +} + +.dispatch_btn_secondary:hover { + background: #545b62; +} + +.dispatch_table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + position: relative; + z-index: 1; +} + +.dispatch_table th { + background: #f8f9fa; + padding: 12px; + text-align: left; + font-weight: 600; + color: #495057; + border-bottom: 2px solid #dee2e6; +} + +.dispatch_table td { + padding: 12px; + border-bottom: 1px solid #dee2e6; + color: #495057; +} + +.dispatch_table tr:hover { + background-color: #f5f5f5; +} + +.dispatch_checkbox { + width: 18px; + height: 18px; + cursor: pointer; +} + +.dispatch_pagination { + margin-top: 10px; + text-align: center; + clear: both; + position: relative; + z-index: 3; + display: flex; + align-items: center; + justify-content: center; + gap: 15px; +} + + +/* 移动端适配 */ +@media (max-width: 991.98px) { + + .dispatch_search_form { + flex-direction: column; + gap: 12px; + width: 100%; + } + + .dispatch_action_group { + width: 100%; + } + + .dispatch_search_form .dispatch_btn, + .dispatch_action_group .dispatch_btn { + flex: 1; + } + + .dispatch_search { + width: 100%; + max-width: 100%; + margin: 0; + } + + .dispatch_filter { + position: static; /* 移除固定定位 */ + display: flex; + flex-direction: column; + gap: 12px; + padding: 12px; + } + + .dispatch_filter form { + display: flex; + flex-direction: column; + gap: 10px; + } + + .dispatch_filter_group { + flex-direction: column; + gap: 15px; + } + + .dispatch_search { + width: 100%; + margin: 0; + } + + .dispatch_action_bar { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + width: 100%; + } + + .dispatch_btn { + min-width: unset; + width: 100%; + padding: 12px 15px; + font-size: 14px; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + } + + .dispatch_btn_primary { + grid-column: 1 / -1; + } + + .dispatch_table { + display: block; + overflow-x: auto; + white-space: nowrap; + -webkit-overflow-scrolling: touch; + } + + .dispatch_table th, + .dispatch_table td { + min-width: 120px; + padding: 8px; + } + + .dispatch_table th:first-child, + .dispatch_table td:first-child { + position: sticky; + left: 0; + background: white; + z-index: 1; + min-width: 60px; + } + + .dispatch_checkbox { + width: 20px; + height: 20px; + } + + .dispatch_pagination { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 10px; + flex-wrap: nowrap; + margin-top: 15px; + } + + .dispatch_page_link { + white-space: nowrap; + padding: 8px 15px; + margin: 0; + } + + .dispatch_page_info { + width: 100%; + text-align: center; + margin: 10px 0; + } + +} + diff --git a/app/static/css/receive.css b/app/static/css/receive.css new file mode 100755 index 0000000..b677575 --- /dev/null +++ b/app/static/css/receive.css @@ -0,0 +1,400 @@ +/* 筛选区域样式 */ +.receive_filter { + margin: 20px 0; + padding: 20px; + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); +} + +.filter_form { + display: flex; + align-items: center; + gap: 15px; + flex-wrap: wrap; +} + +.filter_select { + padding: 8px 12px; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 14px; + background: white; + min-width: 150px; + outline: none; + transition: border-color 0.3s; +} + +.filter_select:focus { + border-color: #4f46e5; +} + +/* 统计卡片样式 */ +.receive_stats { + margin: 20px 0; + padding: 20px; + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); +} + +.stats_item { + display: flex; + align-items: center; + margin-bottom: 20px; + padding: 15px; + background: linear-gradient(135deg, #6366f1, #4f46e5); + border-radius: 8px; + color: white; +} + +.stats_label { + font-size: 1.1em; + opacity: 0.9; +} + +.stats_value { + font-size: 1.8em; + font-weight: bold; + margin-left: auto; +} + +.stats_branches { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; + margin-top: 20px; +} + +.stats_branches_row { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 15px 0; +} + +.branch_stat { + background: #f8fafc; + padding: 8px 15px; + border-radius: 6px; + border: 2px solid #e2e8f0; + transition: all 0.2s; + text-decoration: none; + color: inherit; + display: flex; + align-items: center; + gap: 8px; +} + +.branch_stat:hover { + background: #f1f5f9; + transform: translateY(-1px); +} + +.branch_stat.active { + background: #7eb6ff3b; + color: white; + border-color: #2563eb; +} + +.branch_name { + font-size: 0.9em; +} + +.branch_count { + font-weight: bold; + color: currentColor; +} + +.branch_stat:hover { + transform: translateY(-2px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.branch_name { + color: #64748b; + font-size: 0.9em; +} + +.branch_count { + display: block; + color: #1e293b; + font-size: 1.2em; + font-weight: bold; + margin-top: 5px; +} + +.receive_actions { + display: flex; + gap: 12px; + margin-top: 15px; +} + +.receive_btn { + padding: 8px 16px; + border: none; + border-radius: 6px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 14px; + font-weight: 500; +} + +.receive_btn_warning { + background-color: #ff9800; + color: white; +} + +.receive_btn_warning:hover { + background-color: #ffc772; + color: white; +} + +.receive_btn_secondary { + background-color: #008cff; + color: white; + text-decoration: none; + text-align: center; +} + +.receive_btn_secondary:hover { + background-color: #43abff; + text-decoration: none; + color: white; +} + +.receive_btn_primary { + background-color: #4CAF50; + color: white; +} + + + +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + backdrop-filter: blur(5px); +} + +.modal-content { + background-color: #fff; + margin: 15vh auto; + padding: 25px; + width: 90%; + max-width: 400px; + border-radius: 12px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + animation: modalSlideIn 0.3s ease-out; +} + +.modal-body { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin: 20px 0; +} + +.modal-icon { + width: 48px; + height: 48px; + border-radius: 50%; + background-position: center; + background-repeat: no-repeat; + background-size: 24px; +} + +.modal-icon.warning { + background-color: #fff3e0; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ff5722' d='M12 2L1 21h22M12 6l7.53 13H4.47M11 10v4h2v-4m-2 6v2h2v-2'/%3E%3C/svg%3E"); +} + +.modal-icon.info { + background-color: #e8f5e9; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%234CAF50' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z'/%3E%3C/svg%3E"); +} + +.modal-buttons { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 20px; +} + +@keyframes modalSlideIn { + from { + transform: translateY(-30px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* 分页样式 */ +.receive_pagination { + margin: 25px 0; + display: flex; + justify-content: center; + align-items: center; + gap: 15px; +} + +.page_link { + padding: 8px 16px; + background: #f1f5f9; + border-radius: 6px; + text-decoration: none; + color: #475569; + transition: all 0.3s; +} + +.page_link:hover { + background: #e2e8f0; + color: #1e293b; +} + +.per_page_select { + padding: 8px; + border-radius: 6px; + border: 1px solid #e2e8f0; + outline: none; +} + +.receive_card { + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; + text-decoration: none; + color: inherit; + border: 1px solid #e9ecef; +} + +.receive_card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.receive_card_header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #e9ecef; +} + +.receive_card_title { + font-weight: 600; + color: #2c3e50; +} + +.receive_card_duration { + color: #6c757d; + font-size: 0.9em; +} + +.receive_card_content { + display: grid; + gap: 8px; +} + +.receive_card_item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 0; +} + +.receive_card_label { + color: #6c757d; + font-size: 0.9em; +} + +.receive_card_value { + color: #2c3e50; + font-weight: 500; +} + +.receive_card_value[data-device-type="摄像头"] { + background-color: #e3f2fd; + color: #1976d2; + padding: 4px 8px; + border-radius: 4px; +} + +.receive_card_value[data-device-type="紫色"] { + background-color: #f3e5f5; + color: #7b1fa2; + padding: 4px 8px; + border-radius: 4px; +} + +.receive_card_value[data-device-type="绿色"] { + background-color: #e8f5e9; + color: #388e3c; + padding: 4px 8px; + border-radius: 4px; +} + +.receive_card_value[data-device-type="一键报警"] { + background-color: #fff3e0; + color: #f57c00; + padding: 4px 8px; + border-radius: 4px; +} + +.receive_card_value[data-device-type="灰色"] { + background-color: #f5f5f5; + color: #616161; + padding: 4px 8px; + border-radius: 4px; +} + +/* 移动端适配 */ +@media (max-width: 768px) { + .receive_actions { + flex-direction: column; + width: 100%; + } + + .receive_btn { + width: 100%; + padding: 12px 16px; + } + + .modal-content { + margin: 10vh auto; + padding: 20px; + } + .receive_card { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + padding: 20px; + } + + .receive_card_header { + margin-bottom: 16px; + } + + .receive_card_title { + font-size: 1.1em; + } + + .receive_card_content { + gap: 12px; + } + + .receive_card_item { + padding: 6px 0; + } +} \ No newline at end of file diff --git a/app/static/css/receive_detail.css b/app/static/css/receive_detail.css new file mode 100755 index 0000000..693dca0 --- /dev/null +++ b/app/static/css/receive_detail.css @@ -0,0 +1,46 @@ +.form_tips { + background-color: #f8f9fa; + padding: 15px; + border-radius: 4px; + margin-bottom: 20px; +} + +.form_tips ul { + margin: 10px 0 0 20px; + padding: 0; +} + +.form_tips li { + margin-bottom: 5px; + color: #666; +} + +.required { + color: #dc3545; + margin-left: 3px; +} + +.detail_form input::placeholder, +.detail_form textarea::placeholder { + color: #999; + font-size: 13px; +} + +.detail_form select { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + color: #333; +} + +.detail_form select option:first-child { + color: #999; +} + +.detail_form input[type="tel"] { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; +} \ No newline at end of file diff --git a/app/static/css/statistics.css b/app/static/css/statistics.css new file mode 100755 index 0000000..9c586c7 --- /dev/null +++ b/app/static/css/statistics.css @@ -0,0 +1,268 @@ +/* 容器样式 */ +.statistics_container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +.statistics_section { + margin-bottom: 30px; +} + +/* 标题和按钮样式 */ +.statistics_title { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 20px; + font-size: 1.5em; + color: #1e293b; +} + +/* 统一按钮基础样式 */ +.button_base { + padding: 8px 16px; + border: none; + border-radius: 6px; + font-size: 0.9em; + cursor: pointer; + text-decoration: none; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + height: 36px; +} + +.export_button { + float: right; + padding: 6px 12px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + text-decoration: none; + font-size: 14px; +} + +.export_button:hover { + background-color: #45a049; + color: #fff; +} + +.incentive_button { + float: right; + padding: 6px 12px; + background-color: #dc2626; + color: white; + border: none; + border-radius: 4px; + text-decoration: none; + font-size: 14px; +} + +.incentive_button:hover { + background-color: #b91c1c; + color: #fff; +} + +/* 卡片样式 */ +.statistics_card { + background: white; + border-radius: 10px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.statistics_card h3 { + margin: 0 0 15px 0; + color: #334155; + font-size: 1.2em; +} + +/* 网格布局 */ +.statistics_grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; +} + +.statistics_item { + display: flex; + flex-direction: column; + gap: 8px; +} + +.statistics_label { + color: #64748b; + font-size: 0.9em; +} + +.statistics_value { + color: #1e293b; + font-size: 1.5em; + font-weight: bold; +} + +/* 总计奖励样式 */ +.total_reward { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid #e2e8f0; +} + +/* 表格样式 */ +.handler_stats_table { + overflow-x: auto; +} + +.handler_stats_table table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; +} + +.handler_stats_table th, +.handler_stats_table td { + padding: 12px; + text-align: center; + border-bottom: 1px solid #e2e8f0; +} + +.handler_stats_table th { + background-color: #f8fafc; + color: #475569; + font-weight: 600; +} + +.handler_stats_table tr:hover { + background-color: #f8fafc; +} + +/* 统计页面样式 */ +.statistics_container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +.statistics_section { + margin-bottom: 30px; +} + +.statistics_title { + color: #333; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid #eee; +} + +.statistics_card { + background: white; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.statistics_card h3 { + color: #666; + margin-bottom: 15px; + font-size: 1.1em; +} + +.statistics_grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; +} + +.statistics_item { + display: flex; + flex-direction: column; + align-items: center; + padding: 15px; + background: #f8f9fa; + border-radius: 6px; +} + +.statistics_label { + color: #666; + font-size: 0.9em; + margin-bottom: 5px; +} + +.statistics_value { + color: #2c3e50; + font-size: 1.5em; + font-weight: bold; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .statistics_grid { + grid-template-columns: 1fr; + } + + .statistics_card { + padding: 15px; + } + + .statistics_value { + font-size: 1.2em; + } +} + +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal-content { + background-color: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + width: 300px; +} + +.month-picker-form { + display: flex; + flex-direction: column; + gap: 15px; + margin-top: 15px; +} + +.month-input { + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 16px; +} + +.modal-buttons { + display: flex; + gap: 10px; + justify-content: flex-end; +} + +.cancel_button { + padding: 8px 16px; + border: none; + border-radius: 4px; + background-color: #6c757d; + color: white; + cursor: pointer; +} + +.cancel_button:hover { + background-color: #5a6268; +} \ No newline at end of file diff --git a/app/static/css/styles.css b/app/static/css/styles.css new file mode 100755 index 0000000..2d22a3c --- /dev/null +++ b/app/static/css/styles.css @@ -0,0 +1,978 @@ +.per_page_select { + padding: 6px 10px; + width: auto; + border: 1px solid #ddd; + border-radius: 4px; + background-color: white; + color: #495057; + font-size: 14px; + cursor: pointer; +} + +.per_page_select:focus { + border-color: #007bff; + outline: none; +} + +.dispatch_page_link { + display: inline-block; + padding: 8px 16px; + margin: 0 5px; + border-radius: 4px; + background: white; + color: #007bff; + text-decoration: none; + transition: all 0.3s ease; +} + +.dispatch_page_link:hover { + background: #007bff; + color: white; +} + +.dispatch_page_info { + display: inline-block; + padding: 8px 16px; + color: #6c757d; +} + + +/* 导航栏样式 */ +.nav_container { + background: linear-gradient(135deg, #0061f2 0%, #00a3ff 100%); + padding: 0.5rem 1rem; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.nav_brand { + font-size: 1.5rem; + font-weight: 600; + color: white !important; + text-decoration: none; + transition: opacity 0.3s; +} + +.nav_brand:hover { + opacity: 0.9; +} + +.nav_toggler { + border: none !important; + padding: 0.5rem; + border-radius: 4px; + background-color: rgba(255,255,255,0.1) !important; +} + +.nav_toggler_icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important; +} + +.nav_menu { + margin-left: 1rem; +} + +/* 导航栏样式 */ +.nav_link { + color: rgba(255,255,255,0.9) !important; + padding: 0.5rem 1rem !important; + border-radius: 4px; + transition: all 0.3s; + position: relative; + font-weight: 500; + display: block; /* 确保按钮独占一行 */ + text-decoration: none !important; /* 移除默认下划线 */ +} + +.nav_link:hover { + color: white !important; + background: rgba(255,255,255,0.1); +} + +.nav_link.active { + background: rgba(255,255,255,0.2); + color: white !important; +} + +/* 接单平台样式 */ +.receive_container { + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.receive_filter { + background: white; + padding: 15px 20px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + margin-bottom: 20px; +} + +.receive_select { + padding: 8px 15px; + border: 1px solid #ddd; + border-radius: 4px; + margin-right: 10px; + min-width: 130px; +} + +.receive_table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.receive_table th { + background: #f8f9fa; + padding: 12px; + text-align: left; + font-weight: 600; + color: #495057; + border-bottom: 2px solid #dee2e6; +} + +.receive_table td { + padding: 12px; + border-bottom: 1px solid #dee2e6; + color: #495057; +} + +.receive_table tr:hover { + background-color: #f5f5f5; +} + +.receive_action { + color: #007bff; + text-decoration: none; + padding: 4px 8px; + border-radius: 4px; + transition: all 0.3s; +} + +.receive_action:hover { + background: #007bff; + color: white; +} + +.card_container { + display: none; +} + +/* 移动端卡片样式 */ +@media (max-width: 991.98px) { + .receive_table { + display: none; + } + + .card_container { + display: flex; + flex-direction: column; + gap: 15px; + } + + .receive_card { + background: white; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + position: relative; + text-decoration: none; + color: inherit; + transition: all 0.3s ease; + } + + .receive_card:active { + transform: scale(0.98); + } + + .receive_card:hover { + box-shadow: 0 4px 8px rgba(0,0,0,0.15); + } + + .receive_card_header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 2px solid #eee; + } + + .receive_card_title { + font-weight: 600; + color: #333; + } + + .receive_card_content { + display: flex; + flex-direction: column; + gap: 8px; + } + + .receive_card_item { + display: flex; + justify-content: space-between; + align-items: center; + } + + .receive_card_label { + color: #666; + font-size: 0.9em; + } + + .receive_card_value { + color: #333; + font-weight: 500; + } + + .receive_card_duration { + position: absolute; + top: 15px; + right: 15px; + color: #666; + font-size: 0.85em; + } +} + + +/* 登录页面样式 */ +.login_container { + min-height: calc(100vh - 100px); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.login_box { + background: white; + padding: 40px; + border-radius: 10px; + box-shadow: 0 4px 20px rgba(0,0,0,0.1); + width: 100%; + max-width: 400px; + text-align: center; +} + +.login_title { + color: #333; + font-size: 28px; + margin-bottom: 10px; +} + +.login_subtitle { + color: #666; + font-size: 16px; + margin-bottom: 30px; +} + +.login_form { + text-align: left; +} + +.login_input_group { + margin-bottom: 25px; +} + +.login_label { + display: block; + color: #555; + margin-bottom: 8px; + font-size: 14px; +} + +.login_input { + width: 100%; + padding: 12px 15px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 16px; + transition: all 0.3s; +} + +.login_input:focus { + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0,123,255,0.2); + outline: none; +} + +.login_button { + width: 100%; + padding: 12px; + background: linear-gradient(135deg, #0061f2 0%, #00a3ff 100%); + color: white; + border: none; + border-radius: 6px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s; +} + +.login_button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0,97,242,0.2); +} + +.login_button:active { + transform: translateY(0); +} + +/* 移动端适配 */ +@media (max-width: 991.98px) { + .login_box { + padding: 30px 20px; + } +} + +@media screen and (max-width: 768px) { + .receive_select { + width: 140px; + } + .receive_filter form { + flex-wrap: nowrap; + } +} + +/* 历史工单列表页面样式 */ +.history_container { + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.history_title { + color: #333; + font-size: 24px; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid #eee; +} + +.history_table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.history_table th { + background: #f8f9fa; + padding: 12px; + text-align: left; + font-weight: 600; + color: #495057; + border-bottom: 2px solid #dee2e6; +} + +.history_table td { + padding: 12px; + border-bottom: 1px solid #dee2e6; + color: #495057; +} + +.history_table tr:hover { + background-color: #f5f5f5; +} + +.history_link { + color: #007bff; + text-decoration: none; + transition: all 0.3s; +} + +.history_link:hover { + color: #0056b3; + text-decoration: underline; +} + +.history_pagination { + margin-top: 20px; + text-align: center; +} + +.history_page_link { + display: inline-block; + padding: 8px 16px; + margin: 0 5px; + border-radius: 4px; + background: white; + color: #007bff; + text-decoration: none; + transition: all 0.3s ease; +} + +.history_page_link:hover { + background: #007bff; + color: white; +} + +.history_page_info { + display: inline-block; + padding: 8px 16px; + color: #6c757d; +} + +/* 历史工单详情页面样式 */ +.history_detail_container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +.detail_photos { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #eee; +} + +.photo_grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 20px; + margin-top: 15px; +} + +.photo_item { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.photo_item img { + width: 100%; + height: 200px; + object-fit: cover; + transition: transform 0.3s ease; +} + +.photo_item img:hover { + transform: scale(1.05); +} + +/* 移动端适配 */ +@media (max-width: 991.98px) { + .history_container { + padding: 10px; + } + + .history_table { + display: none; + } + + .history_card { + background: white; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + text-decoration: none; + color: inherit; + transition: all 0.3s ease; + } + + .history_card:hover { + transform: translateX(5px); + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + } + + .history_card_header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #eee; + } + + .history_card_title { + font-weight: 600; + color: #333; + } + + .history_card_content { + display: flex; + flex-direction: column; + gap: 8px; + } + + .history_card_item { + display: flex; + justify-content: space-between; + align-items: center; + } + + .history_card_label { + color: #666; + font-size: 0.9em; + } + + .history_card_value { + color: #333; + font-weight: 500; + } + + .photo_grid { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } + + .photo_item img { + height: 150px; + } + .manual-card { + border-left: 4px solid #ff9800; /* 手工申报工单使用橙色边框 */ + } + + .system-card { + border-left: 4px solid #2196f3; /* 系统派单工单使用蓝色边框 */ + } +} +/* 历史工单详情页样式更新 */ +.detail_header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + padding-bottom: 15px; + border-bottom: 2px solid #eee; +} + +.detail_title { + font-size: 22px; + color: #2c3e50; + margin: 0; + +} + +.detail_status { + padding: 4px 10px; + background: #28a745; + color: white; + border-radius: 12px; + font-size: 14px; +} + +.detail_section { + margin-bottom: 30px; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; +} + +.section_title { + font-size: 18px; + color: #2c3e50; + margin-bottom: 20px; + font-weight: 500; +} + +.detail_grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; +} + +.detail_item { + display: flex; + flex-direction: column; + gap: 8px; +} + +.detail_label { + color: #6c757d; + font-size: 14px; +} + +.detail_value { + color: #2c3e50; + font-weight: 500; +} + +.detail_value.highlight { + color: #007bff; +} + +.detail_value.badge { + display: inline-block; + padding: 6px 12px; + background: #e9ecef; + border-radius: 6px; + font-size: 15px; +} + +.detail_description { + background: white; + padding: 20px; + border-radius: 8px; + margin-top: 10px; +} + +.detail_remark { + margin-top: 20px; + padding: 15px; + background: #fff3cd; + border-radius: 8px; +} + +.detail_remark h5 { + color: #856404; + margin-bottom: 10px; + font-size: 16px; +} + +.photo_grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 20px; + margin-top: 15px; +} + +.photo_item { + position: relative; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + cursor: pointer; +} + +.photo_item img { + width: 100%; + height: 200px; + object-fit: cover; + transition: transform 0.3s ease; +} + +.photo_item:hover img { + transform: scale(1.05); +} + +/* 模态框样式 */ +.modal { + display: none; + position: fixed; + z-index: 1000; + padding-top: 50px; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.9); +} + +.modal_content { + margin: auto; + display: block; + max-width: 90%; + max-height: 90vh; +} + +.modal_close { + position: absolute; + right: 35px; + top: 15px; + color: #f1f1f1; + font-size: 40px; + font-weight: bold; + cursor: pointer; +} + +/* 移动端适配 */ +@media (max-width: 768px) { + .detail_grid { + grid-template-columns: 1fr; + } + + .detail_section { + padding: 15px; + } + + .photo_grid { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } + + .photo_item img { + height: 150px; + } +} + +/* 历史工单搜索框样式 */ +.history_search { + margin: 20px 0; +} + +.history_search_form { + display: flex; + gap: 10px; + max-width: 600px; +} + +.history_search_input { + flex: 1; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.history_search_input:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0,123,255,0.1); +} + +.history_search_button { + padding: 8px 16px; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s; +} + +.history_search_button:hover { + background: #0056b3; +} + +/* 移动端适配 */ +@media screen and (max-width: 768px) { + .history_search_form { + flex-direction: column; + gap: 8px; + } +} + +/* 手工故障单样式 */ +.manual_container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +.manual_title { + margin-bottom: 30px; + color: #333; + text-align: center; +} + +.manual_form { + background: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.manual_form .form-group { + margin-bottom: 20px; +} + +.manual_form label { + display: block; + margin-bottom: 8px; + color: #555; +} + +.manual_form input, +.manual_form select, +.manual_form textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.manual_form textarea { + height: 100px; + resize: vertical; +} + +.manual_submit { + width: 100%; + padding: 12px; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; +} + +.manual_submit:hover { + background: #0056b3; +} + +@media screen and (max-width: 768px) { + .manual_form { + padding: 20px; + } +} + +.receive_detail_container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +.detail_card { + background: white; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +/* 工单信息卡片样式 */ +.detail_card:first-child { + background: #f8f9fa; + border-left: 4px solid #007bff; +} + +/* 处理信息卡片样式 */ +.detail_card:last-child { + background: white; + border-left: 4px solid #28a745; +} + +.detail_grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 10px; /* 减小行距 */ +} + +.detail_item { + padding: 5px 10px; /* 减小内边距 */ +} + +.detail_label { + color: #666; + font-size: 0.9em; + margin-right: 8px; + display: inline-block; + width: 80px; /* 固定标签宽度,使值对齐 */ +} + +.detail_value { + color: #333; + font-weight: 500; +} + +.detail_form { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + color: #666; +} + +.form-group input[type="text"], +.form-group input[type="number"], +.form-group textarea { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.form-group textarea { + height: 100px; + resize: vertical; +} + +.detail_file_input { + padding: 8px; + border: 1px dashed #ddd; + border-radius: 4px; + width: 100%; +} + +.detail_actions { + grid-column: 1 / -1; + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 20px; +} + +.detail_btn { + padding: 8px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.detail_btn_primary { + background: #007bff; + color: white; +} + +.detail_btn_secondary { + background: #6c757d; + color: white; +} + +.detail_btn:hover { + opacity: 0.9; +} + +.login_error { + color: #dc3545; + background-color: #f8d7da; + border: 1px solid #f5c6cb; + border-radius: 4px; + padding: 10px; + margin-bottom: 15px; + font-size: 14px; + text-align: center; +} + +@media (max-width: 768px) { + .detail_form { + grid-template-columns: 1fr; + } + + .detail_grid { + grid-template-columns: 1fr; + } +} + +.detail_btn_danger { + background: #dc3545; + color: white; +} + +.detail_btn_danger:hover { + background: #c82333; +} + + +.total_reward { + background: #e3f2fd; +} + +.total_reward .statistics_value { + color: #1976d2; +} \ No newline at end of file diff --git a/app/static/favicon.ico b/app/static/favicon.ico new file mode 100755 index 0000000..a35c8ef Binary files /dev/null and b/app/static/favicon.ico differ diff --git a/app/static/file/关于政企交付运营中心2025年项目运维及交付实施激励方案.pdf b/app/static/file/关于政企交付运营中心2025年项目运维及交付实施激励方案.pdf new file mode 100755 index 0000000..087dc98 Binary files /dev/null and b/app/static/file/关于政企交付运营中心2025年项目运维及交付实施激励方案.pdf differ diff --git a/app/static/js/bootstrap.bundle.min.js b/app/static/js/bootstrap.bundle.min.js new file mode 100755 index 0000000..cc0a255 --- /dev/null +++ b/app/static/js/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/app/static/js/dispatch.js b/app/static/js/dispatch.js new file mode 100755 index 0000000..6dbe0dd --- /dev/null +++ b/app/static/js/dispatch.js @@ -0,0 +1,271 @@ +function handleSelectAll() { + const checkboxes = document.querySelectorAll('input[name="device_ids"]'); + const selectAllButton = document.getElementById('selectAllBtn'); + const hasSelected = Array.from(checkboxes).some(checkbox => checkbox.checked); + const newState = !hasSelected; + + checkboxes.forEach(checkbox => { + checkbox.checked = newState; + }); + + selectAllButton.textContent = newState ? '清空' : '全选'; +} + +function handleInvertSelect() { + const checkboxes = document.querySelectorAll('input[name="device_ids"]'); + checkboxes.forEach(checkbox => { + checkbox.checked = !checkbox.checked; + }); + updateSelectAllButtonText(); +} + +function updateSelectAllButtonText() { + const checkboxes = document.querySelectorAll('input[name="device_ids"]'); + const selectAllButton = document.getElementById('selectAllBtn'); + const hasSelected = Array.from(checkboxes).some(checkbox => checkbox.checked); + selectAllButton.textContent = hasSelected ? '清空' : '全选'; +} + +function changePerPage(value) { + const currentUrl = new URL(window.location.href); + currentUrl.searchParams.set('per_page', value); + currentUrl.searchParams.set('page', '1'); + window.location.href = currentUrl.toString(); +} + +async function handleSync(type) { + if (type === 'camera') { + try { + const response = await fetch('/get_captcha', { + method: 'GET' + }); + + if (response.ok) { + const blob = await response.blob(); + document.getElementById('captchaImage').src = URL.createObjectURL(blob); + document.getElementById('captchaModal').style.display = 'block'; + } else { + alert('获取验证码失败,请重试'); + } + } catch (error) { + console.error('获取验证码失败:', error); + alert('获取验证码失败,请重试'); + } + } else if (type === 'alarm') { + // 显示确认模态框 + document.getElementById('alarmConfirmModal').style.display = 'block'; + } +} + +// 添加关闭确认模态框的函数 +function closeAlarmConfirmModal() { + document.getElementById('alarmConfirmModal').style.display = 'none'; +} + +// 添加确认同步的函数 +async function confirmAlarmSync() { + try { + // 关闭确认模态框 + closeAlarmConfirmModal(); + + // 显示加载提示 + document.getElementById('resultMessage').textContent = '正在同步数据,请稍候...'; + document.getElementById('resultModal').style.display = 'block'; + + const response = await fetch('/sync_offline_devices', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + + if (data.success) { + document.getElementById('resultMessage').textContent = + `同步成功!已为 ${data.count} 个离线设备创建工单。`; + } else { + document.getElementById('resultMessage').textContent = '同步失败:' + data.message; + } + document.getElementById('resultModal').style.display = 'block'; + } catch (error) { + console.error('同步失败:', error); + document.getElementById('resultMessage').textContent = '同步失败,请重试'; + document.getElementById('resultModal').style.display = 'block'; + } +} + +async function submitCaptcha() { + const captcha = document.getElementById('captchaInput').value; + if (!captcha) { + alert('请输入验证码'); + return; + } + + try { + const response = await fetch('/sync_camera_step1', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ captcha: captcha }) + }); + + const data = await response.json(); + + if (data.success) { + document.getElementById('captchaModal').style.display = 'none'; + // 更新模态框中的数量 + document.getElementById('updatedCount').textContent = data.updated_count || 0; + document.getElementById('confirmModal').style.display = 'block'; + } else { + if (data.message === 'INVALID_CAPTCHA') { + alert('验证码错误,请重新输入'); + handleSync('camera'); + } else { + alert('同步失败:' + data.message); + } + } + } catch (error) { + console.error('同步失败:', error); + alert('同步失败,请重试'); + } +} + +async function handleDispatch(isCamera = false) { + try { + // 不立即关闭确认模态框,而是先更新其内容 + const confirmModal = document.getElementById('confirmModal'); + const modalContent = confirmModal.querySelector('.modal-content'); + modalContent.innerHTML = ` +

正在处理

+

正在为选中设备创建工单,请稍候...

+
+ `; + + let response; + if (isCamera) { + // 如果是摄像头同步,调用第二步接口 + response = await fetch('/sync_camera_step2', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + } else { + // 普通派单 + response = await fetch('/dispatch', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + } + + const result = await response.json(); + + // 处理完成后,平滑过渡到结果模态框 + document.getElementById('confirmModal').style.display = 'none'; + document.getElementById('resultMessage').textContent = `派单成功!已为 ${result.count} 个设备创建工单。`; + document.getElementById('resultModal').style.display = 'block'; + } catch (error) { + console.error('派单失败:', error); + document.getElementById('resultMessage').textContent = '派单失败,请重试'; + document.getElementById('resultModal').style.display = 'block'; + } +} + +function cancelDispatch() { + document.getElementById('confirmModal').style.display = 'none'; + window.location.reload(); +} + +function closeCaptchaModal() { + document.getElementById('captchaModal').style.display = 'none'; + document.getElementById('captchaInput').value = ''; +} + +// 备注编辑功能 +function editRemark(deviceId, spanElement) { + const inputElement = spanElement.nextElementSibling; + spanElement.style.display = 'none'; + inputElement.style.display = 'inline-block'; + inputElement.focus(); +} + +async function saveRemark(deviceId, inputElement) { + const newRemark = inputElement.value.trim(); + const spanElement = inputElement.previousElementSibling; + + try { + const response = await fetch('/update_device_remark', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + device_id: deviceId, + remark: newRemark + }) + }); + + const data = await response.json(); + + if (data.success) { + spanElement.textContent = newRemark || '点击添加备注'; + spanElement.style.display = 'inline-block'; + inputElement.style.display = 'none'; + } else { + alert('保存失败:' + data.message); + inputElement.focus(); + } + } catch (error) { + console.error('保存备注失败:', error); + alert('保存失败,请重试'); + inputElement.focus(); + } +} +document.addEventListener('DOMContentLoaded', function() { + // 监听复选框的变化 + document.addEventListener('change', function(e) { + if (e.target.matches('input[name="device_ids"]')) { + updateSelectAllButtonText(); + } + }); + + // 添加表单提交前的检查 + document.getElementById('dispatchForm').addEventListener('submit', async function(e) { + e.preventDefault(); + + const selectedDevices = Array.from(document.querySelectorAll('input[name="device_ids"]:checked')) + .map(checkbox => checkbox.value); + + if (selectedDevices.length === 0) { + alert('请至少选择一个设备'); + return; + } + + try { + const response = await fetch('/check_existing_orders', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ device_ids: selectedDevices }) + }); + + const data = await response.json(); + + if (data.existing_devices.length > 0) { + alert(`以下设备已有在途工单,请勿重复派单:\n${data.existing_devices.join('\n')}`); + return; + } + + this.submit(); + + } catch (error) { + console.error('检查失败:', error); + alert('检查在途工单失败,请重试'); + } + }); +}); \ No newline at end of file diff --git a/app/static/js/receive.js b/app/static/js/receive.js new file mode 100755 index 0000000..77b9a0e --- /dev/null +++ b/app/static/js/receive.js @@ -0,0 +1,53 @@ +function changePerPage(value) { + const currentUrl = new URL(window.location.href); + currentUrl.searchParams.set('per_page', value); + currentUrl.searchParams.set('page', '1'); + window.location.href = currentUrl.toString(); +} + +function recallAllOrders() { + document.getElementById('confirmModal').style.display = 'block'; +} + +function closeModal() { + document.getElementById('confirmModal').style.display = 'none'; +} + +async function confirmRecall() { + try { + // 关闭确认模态框 + closeModal(); + + const response = await fetch('/recall_all_orders', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + if (data.success) { + document.getElementById('resultMessage').textContent = `成功追回 ${data.count} 个工单`; + document.getElementById('resultModal').style.display = 'block'; + } else { + document.getElementById('resultMessage').textContent = '追回失败:' + data.message; + document.getElementById('resultModal').style.display = 'block'; + } + } catch (error) { + document.getElementById('resultMessage').textContent = '操作失败,请重试'; + document.getElementById('resultModal').style.display = 'block'; + } +} + +function closeResultModal() { + document.getElementById('resultModal').style.display = 'none'; + window.location.reload(); +} + +// 添加点击模态框外部关闭功能 +window.onclick = function(event) { + const modal = document.getElementById('confirmModal'); + if (event.target == modal) { + closeModal(); + } +} \ No newline at end of file diff --git a/app/static/uploads/20250421113451_4AN-UI设计.png b/app/static/uploads/20250421113451_4AN-UI设计.png new file mode 100755 index 0000000..6d1f55e Binary files /dev/null and b/app/static/uploads/20250421113451_4AN-UI设计.png differ diff --git a/app/static/uploads/20250421113553_4AN-UI设计.png b/app/static/uploads/20250421113553_4AN-UI设计.png new file mode 100755 index 0000000..6d1f55e Binary files /dev/null and b/app/static/uploads/20250421113553_4AN-UI设计.png differ diff --git a/app/static/uploads/20250421113658_4AN-UI设计.png b/app/static/uploads/20250421113658_4AN-UI设计.png new file mode 100755 index 0000000..6d1f55e Binary files /dev/null and b/app/static/uploads/20250421113658_4AN-UI设计.png differ diff --git a/app/static/uploads/20250421151429_4AN-UI设计.png b/app/static/uploads/20250421151429_4AN-UI设计.png new file mode 100755 index 0000000..6d1f55e Binary files /dev/null and b/app/static/uploads/20250421151429_4AN-UI设计.png differ diff --git a/app/static/uploads/20250422181024_4AN-UI设计.png b/app/static/uploads/20250422181024_4AN-UI设计.png new file mode 100755 index 0000000..6d1f55e Binary files /dev/null and b/app/static/uploads/20250422181024_4AN-UI设计.png differ diff --git a/app/static/uploads/20250422181045_4AN-UI设计.png b/app/static/uploads/20250422181045_4AN-UI设计.png new file mode 100755 index 0000000..6d1f55e Binary files /dev/null and b/app/static/uploads/20250422181045_4AN-UI设计.png differ diff --git a/app/static/uploads/20250424172921_20250424160650694-53-1250891.jpg b/app/static/uploads/20250424172921_20250424160650694-53-1250891.jpg new file mode 100755 index 0000000..b742581 Binary files /dev/null and b/app/static/uploads/20250424172921_20250424160650694-53-1250891.jpg differ diff --git a/app/static/uploads/20250424172921_20250424160823715-15-1202018.jpg b/app/static/uploads/20250424172921_20250424160823715-15-1202018.jpg new file mode 100755 index 0000000..f2fc36c Binary files /dev/null and b/app/static/uploads/20250424172921_20250424160823715-15-1202018.jpg differ diff --git a/app/static/uploads/20250424172921_Screenshot_2025-04-24-16-27-11-809_com.zte.vss.ct.jpg b/app/static/uploads/20250424172921_Screenshot_2025-04-24-16-27-11-809_com.zte.vss.ct.jpg new file mode 100755 index 0000000..9cb1e41 Binary files /dev/null and b/app/static/uploads/20250424172921_Screenshot_2025-04-24-16-27-11-809_com.zte.vss.ct.jpg differ diff --git a/app/static/uploads/20250424173251_20250424165824445-59-1274337.jpg b/app/static/uploads/20250424173251_20250424165824445-59-1274337.jpg new file mode 100755 index 0000000..c73d667 Binary files /dev/null and b/app/static/uploads/20250424173251_20250424165824445-59-1274337.jpg differ diff --git a/app/static/uploads/20250424173251_20250424171213905-38-1267636.jpg b/app/static/uploads/20250424173251_20250424171213905-38-1267636.jpg new file mode 100755 index 0000000..868dae9 Binary files /dev/null and b/app/static/uploads/20250424173251_20250424171213905-38-1267636.jpg differ diff --git a/app/static/uploads/20250424173251_20250424171221972-75-1303552.jpg b/app/static/uploads/20250424173251_20250424171221972-75-1303552.jpg new file mode 100755 index 0000000..e077e6e Binary files /dev/null and b/app/static/uploads/20250424173251_20250424171221972-75-1303552.jpg differ diff --git a/app/static/uploads/20250424173251_Screenshot_2025-04-24-16-15-49-746_com.zte.vss.ct.jpg b/app/static/uploads/20250424173251_Screenshot_2025-04-24-16-15-49-746_com.zte.vss.ct.jpg new file mode 100755 index 0000000..cc31a42 Binary files /dev/null and b/app/static/uploads/20250424173251_Screenshot_2025-04-24-16-15-49-746_com.zte.vss.ct.jpg differ diff --git a/app/static/uploads/20250424173636_20250424173541588-95-1190424.jpg b/app/static/uploads/20250424173636_20250424173541588-95-1190424.jpg new file mode 100755 index 0000000..27e3c70 Binary files /dev/null and b/app/static/uploads/20250424173636_20250424173541588-95-1190424.jpg differ diff --git a/app/static/uploads/20250424173636_20250424173550262-38-1403094.jpg b/app/static/uploads/20250424173636_20250424173550262-38-1403094.jpg new file mode 100755 index 0000000..df36143 Binary files /dev/null and b/app/static/uploads/20250424173636_20250424173550262-38-1403094.jpg differ diff --git a/app/static/uploads/20250429152044_1745911116668.jpg b/app/static/uploads/20250429152044_1745911116668.jpg new file mode 100755 index 0000000..299fb66 Binary files /dev/null and b/app/static/uploads/20250429152044_1745911116668.jpg differ diff --git a/app/static/uploads/20250513112514_IMG_20250513_112417.jpg b/app/static/uploads/20250513112514_IMG_20250513_112417.jpg new file mode 100755 index 0000000..2054a9b Binary files /dev/null and b/app/static/uploads/20250513112514_IMG_20250513_112417.jpg differ diff --git a/app/static/uploads/20250513112611_IMG_20250513_112417.jpg b/app/static/uploads/20250513112611_IMG_20250513_112417.jpg new file mode 100755 index 0000000..2054a9b Binary files /dev/null and b/app/static/uploads/20250513112611_IMG_20250513_112417.jpg differ diff --git a/app/static/uploads/20250513112611_Screenshot_2025-05-13-11-23-23-656_com.zte.vss.ct.jpg b/app/static/uploads/20250513112611_Screenshot_2025-05-13-11-23-23-656_com.zte.vss.ct.jpg new file mode 100755 index 0000000..a92a19f Binary files /dev/null and b/app/static/uploads/20250513112611_Screenshot_2025-05-13-11-23-23-656_com.zte.vss.ct.jpg differ diff --git a/app/static/uploads/20250513130622_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg b/app/static/uploads/20250513130622_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg new file mode 100755 index 0000000..de795db Binary files /dev/null and b/app/static/uploads/20250513130622_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg differ diff --git a/app/static/uploads/20250513130723_IMG_20250513_114128.jpg b/app/static/uploads/20250513130723_IMG_20250513_114128.jpg new file mode 100755 index 0000000..162e2a9 Binary files /dev/null and b/app/static/uploads/20250513130723_IMG_20250513_114128.jpg differ diff --git a/app/static/uploads/20250513130723_IMG_20250513_114204.jpg b/app/static/uploads/20250513130723_IMG_20250513_114204.jpg new file mode 100755 index 0000000..6e782f2 Binary files /dev/null and b/app/static/uploads/20250513130723_IMG_20250513_114204.jpg differ diff --git a/app/static/uploads/20250513130723_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg b/app/static/uploads/20250513130723_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg new file mode 100755 index 0000000..de795db Binary files /dev/null and b/app/static/uploads/20250513130723_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg differ diff --git a/app/static/uploads/20250513130816_IMG_20250513_114143.jpg b/app/static/uploads/20250513130816_IMG_20250513_114143.jpg new file mode 100755 index 0000000..5ae0c2e Binary files /dev/null and b/app/static/uploads/20250513130816_IMG_20250513_114143.jpg differ diff --git a/app/static/uploads/20250513130816_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg b/app/static/uploads/20250513130816_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg new file mode 100755 index 0000000..de795db Binary files /dev/null and b/app/static/uploads/20250513130816_Screenshot_2025-05-13-12-55-57-084_com.zte.vss.ct.jpg differ diff --git a/app/templates/about.html b/app/templates/about.html new file mode 100755 index 0000000..5a10231 --- /dev/null +++ b/app/templates/about.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} +{% block content %} + +
+
+

关于本平台

+
+ +
+
+

平台简介

+

本平台是一个专门用于管理和处理政企重点项目运维工单的平台。平台支持工单的派发、处理、统计等功能,旨在提高运维工作效率和管理透明度。

+
+ +
+
    +
  • 工单管理:支持普通工单和手工故障单的创建、派发和处理
  • +
  • 数据统计:提供周期性的工单处理统计和运维奖励计算
  • +
  • 历史记录:记录和查询历史工单信息
  • +
  • 设备管理:支持设备信息的管理和维护
  • +
+
+ + +
+

信息

+
+

当前平台处于试行阶段,如果遇到页面错误,功能不正常等问题请截图后与管理员联系;

+

如果您有任何建议或意见,请随时与我们联系。

+

感谢您的支持和使用!

+
+
+

联系:韦矍森

+

电话:19977899008

+

部门:大化政企业务交付运营中心

+
+
+ + +
+
+{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100755 index 0000000..175e96d --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,125 @@ + + + + + + 大化重点项目工单管理平台 + + + + + + + + +
+ {% if not request.endpoint == 'login' %} + {% with messages = get_flashed_messages() %} + {% if messages %} + + {% endif %} + {% endwith %} + {% endif %} + {% block content %}{% endblock %} +
+ + + + + diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html new file mode 100755 index 0000000..5463b1d --- /dev/null +++ b/app/templates/dashboard.html @@ -0,0 +1,418 @@ +{% extends "base.html" %} +{% block content %} + +
+

设备在线率仪表盘

+ + +
+

设备在线率趋势图

+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + +
+

各类型设备总体在线率

+
+ {% for device_type, stats in overall_stats.items() %} +
+
+

{{ device_type }}

+
+
+
+
+ {{ stats.online_rate }}% +
+
+
+
+ 设备总数 + {{ stats.total }} +
+
+ 派单数 + {{ stats.work_orders }} +
+
+
+
+ {% endfor %} +
+
+ + +
+

各支局设备在线率

+
+ + + + + + + + + + + + + {% for branch, types in branch_stats.items() %} + {% for device_type, stats in types.items() %} + {% if stats.total > 0 %} + + {% if loop.first %} + + {% endif %} + + + + + + + {% endif %} + {% endfor %} + {% endfor %} + +
支局设备类型设备总数派单数在线率状态
{{ branch }}{{ device_type }}{{ stats.total }}{{ stats.work_orders }}{{ stats.online_rate }}% + {% if stats.online_rate >= 90 %} + 良好 + {% elif stats.online_rate >= 75 %} + 一般 + {% else %} + 需关注 + {% endif %} +
+
+
+ + +
+

各乡镇设备在线率

+
+ {% for town, types in town_stats.items() %} +
+
+
{{ town }}
+
+ 总设备数: {{ types['总计'].total }} + 在线率: {{ types['总计'].online_rate }}% + {% if types['总计'].online_rate >= 90 %} + 良好 + {% elif types['总计'].online_rate >= 75 %} + 一般 + {% else %} + 需关注 + {% endif %} +
+
+
+
+ + + + + + + + + + + + {% for device_type, stats in types.items() %} + {% if device_type != '总计' and stats.total > 0 %} + + + + + + + + {% endif %} + {% endfor %} + +
设备类型设备总数派单数在线率状态
{{ device_type }}{{ stats.total }}{{ stats.work_orders }}{{ stats.online_rate }}% + {% if stats.online_rate >= 90 %} + 良好 + {% elif stats.online_rate >= 75 %} + 一般 + {% else %} + 需关注 + {% endif %} +
+
+
+ {% endfor %} +
+
+
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/dispatch.html b/app/templates/dispatch.html new file mode 100755 index 0000000..d200b8d --- /dev/null +++ b/app/templates/dispatch.html @@ -0,0 +1,141 @@ +{% extends "base.html" %} +{% block content %} + + + +
+
+ + + + +
+ +
+ + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + {% for device in devices.items %} + + + + + + + + + + + {% endfor %} + +
选择设备编号所属乡镇所属支局单位名称项目类型设备类型备注
{{ device.device_id }}{{ device.town }}{{ device.branch }}{{ device.unit_name }}{{ device.project_type }}{{ device.device_type }} + {{ device.remark or '点击添加备注' }} + +
+
+ + + +
+ {% if devices.has_prev %} + 上一页 + {% endif %} + + 第 {{ devices.page }} 页,共 {{ devices.pages }} 页 + + + + {% if devices.has_next %} + 下一页 + {% endif %} +
+ + + + + + + + + + + + + +{% endblock %} diff --git a/app/templates/history.html b/app/templates/history.html new file mode 100755 index 0000000..5549e5f --- /dev/null +++ b/app/templates/history.html @@ -0,0 +1,88 @@ +{% extends "base.html" %} +{% block content %} +
+ + + + + + + + + + + + + + + + + + + {% for handling in handlings.items %} + + + + + + + + + + {% endfor %} + +
工单号所属支局故障设备处理人处理时间故障类型操作
{{ handling['order_id'] }}{{ handling['branch'] }}{{ handling['device_id'] }}{{ handling['handler'] }}{{ handling['time'].strftime('%Y-%m-%d %H:%M:%S') }}{{ handling['fault_type'] }} + 查看 +
+ + + + + +
+ {% if handlings.has_prev %} + 上一页 + {% endif %} + 第 {{ handlings.page }} 页,共 {{ handlings.pages }} 页 + {% if handlings.has_next %} + 下一页 + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/app/templates/history_detail.html b/app/templates/history_detail.html new file mode 100755 index 0000000..3075d8c --- /dev/null +++ b/app/templates/history_detail.html @@ -0,0 +1,132 @@ +{% extends "base.html" %} +{% block content %} +
+ +
+
+

工单详情 #{{ handling.order_id }}

+ {{ '申报' if order_type == 'manual' else '系统' }} +
+ +
+

基本信息

+
+
+ 所属支局 + {{ handling.branch }} +
+
+ 故障设备 + {{ handling.device_id }} +
+
+ 处理人 + {{ handling.handler }} +
+ {% if order_type == 'manual' %} +
+ 项目类型 + {{ handling.project_type }} +
+
+ 设备位置 + {{ handling.device_location }} +
+
+ 故障时间 + {{ handling.fault_time.strftime('%Y-%m-%d %H:%M:%S') }} +
+ {% else %} +
+ 处理时间 + {{ handling.restore_time.strftime('%Y-%m-%d %H:%M:%S') }} +
+ {% endif %} +
+
+ +
+

故障信息

+
+
+ 故障类型 + {{ handling.fault_type }} +
+
+ 故障对接人 + {{ handling.contact_person }} +
+
+ 联系电话 + {{ handling.contact_phone }} +
+
+ 距离支局 + {{ handling.distance_from_branch }} 公里 +
+
+
+ +
+

处理详情

+
+

{{ handling.handle_description }}

+
+ {% if handling.remark %} +
+
备注信息
+

{{ handling.remark }}

+
+ {% endif %} +
+ + {% if handling.photo_paths %} +
+

现场图片

+
+ {% for photo in handling.photo_paths.split(',') %} +
+ 现场图片 +
+ {% endfor %} +
+
+ {% endif %} +
+ + +
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100755 index 0000000..2d9612c --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% block content %} + +{% endblock %} diff --git a/app/templates/manual.html b/app/templates/manual.html new file mode 100755 index 0000000..8760663 --- /dev/null +++ b/app/templates/manual.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} +{% block content %} +
+ +
+

注意事项

+
    +
  • 本页面用于填写用户来电申报或者巡检途中发现的故障、移机,申报前请确保在途工单中没有重复故障
  • +
  • 现场照片最多可上传5张,支持jpg、jpeg、png
  • +
  • 距离支局公里数请填写支局办公室到故障地点实际距离
  • +
  • * 的字段为必填项
  • +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ +{% endblock %} \ No newline at end of file diff --git a/app/templates/receive.html b/app/templates/receive.html new file mode 100755 index 0000000..efa5d99 --- /dev/null +++ b/app/templates/receive.html @@ -0,0 +1,162 @@ +{% extends "base.html" %} +{% block content %} + + + +
+
+ 在途工单总数 + {{ total_orders }} +
+ {% if current_user.branch == '交付中心' %} +
+ {% for branch, count in branch_stats.items() %} + + {{ branch }} + {{ count }} + + {% endfor %} +
+
+ + {% if selected_branch %} + 清除筛选 + {% endif %} +
+ {% endif %} +
+ + +
+
+ + + + + + + {% if request.args.get('device_type') or request.args.get('project_type') %} + 清除筛选 + {% endif %} +
+
+ + + + + + + + + + + + + + + {% for work_order in work_orders %} + + + + + + + + + + {% endfor %} + +
工单号所属支局故障设备设备类型项目类型派单时间派单时长
{{ work_order.order_id }}{{ work_order.device.branch }}{{ work_order.device.device_id }}{{ work_order.device.device_type }}{{ work_order.device.project_type }}{{ work_order.dispatch_time }}{{ work_order.duration }}
+ + + +
+ {% if work_orders.has_prev %} + 上一页 + {% endif %} + + 第 {{ work_orders.page }} 页,共 {{ work_orders.pages }} 页 + + + + {% if work_orders.has_next %} + 下一页 + {% endif %} +
+ + + + + + + +{% endblock %} diff --git a/app/templates/receive_detail.html b/app/templates/receive_detail.html new file mode 100755 index 0000000..218aa74 --- /dev/null +++ b/app/templates/receive_detail.html @@ -0,0 +1,141 @@ +{% extends "base.html" %} +{% block content %} + +
+ +
+

工单信息

+
+
+ 工单号: + {{ work_order.order_id }} +
+
+ 所属支局: + {{ work_order.device.branch }} +
+
+ 故障设备: + {{ work_order.device.device_id }} +
+
+ 设备类型: + {{ work_order.device.device_type }} +
+
+ 项目类型: + {{ work_order.device.project_type }} +
+
+ 派单时间: + {{ work_order.dispatch_time }} +
+
+ 关联账号: + {{ work_order.device.account }} +
+
+
+ + +
+

处理信息

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

注意事项

+
    +
  • 现场照片最多可上传5张,支持jpg、jpeg、png
  • +
  • 距离支局公里数请填写支局办公室到故障地点实际距离
  • +
  • * 的字段为必填项
  • +
+
+
+ + {% if current_user.role == '统计员' %} + 追回 + {% endif %} + 返回 +
+
+
+
+ + +{% endblock %} diff --git a/app/templates/statistics.html b/app/templates/statistics.html new file mode 100755 index 0000000..a92d722 --- /dev/null +++ b/app/templates/statistics.html @@ -0,0 +1,255 @@ +{% extends "base.html" %} +{% block content %} + +
+ +
+

本周统计 + 📄 关于政企交付运营中心2025年项目运维及交付实施激励方案 +

+ +
+

普通工单

+ {% if current_user.role == '统计员' %} +
+
+ 派单总数 + {{ week_work_stats.total }} +
+
+ 已处理 + {{ week_work_stats.completed }} +
+
+ 未处理 + {{ week_work_stats.pending }} +
+
+ {% else %} +
+
+ 已处理工单 + {{ week_work_stats.completed }} +
+
+ {% endif %} +
+ + +
+

手工故障单

+
+
+ 处理完成数量 + {{ week_manual_stats }} +
+
+
+ + +
+

运维奖励(元)

+
+ {% if week_rewards is mapping %} + {% for project_type, reward in week_rewards.items() %} +
+ {{ project_type }} + {{ "%.2f"|format(reward) }} +
+ {% endfor %} + {% endif %} +
+ 总计 + {{ "%.2f"|format(week_rewards.values()|sum) if week_rewards is mapping else "%.2f"|format(week_rewards|default(0)) }} +
+
+
+
+ + +
+

本月统计 + {% if current_user.role == '统计员' %} + + {% endif %} +

+ + +
+

普通工单

+ {% if current_user.role == '统计员' %} +
+
+ 派单总数 + {{ month_work_stats.total }} +
+
+ 已处理 + {{ month_work_stats.completed }} +
+
+ 未处理 + {{ month_work_stats.pending }} +
+
+ {% else %} +
+
+ 已处理工单 + {{ month_work_stats.completed }} +
+
+ {% endif %} +
+ + +
+

手工故障单

+
+
+ 处理完成数量 + {{ month_manual_stats }} +
+
+
+ + +
+

运维奖励(元)

+
+ {% if month_rewards is mapping %} + {% for project_type, reward in month_rewards.items() %} +
+ {{ project_type }} + {{ "%.2f"|format(reward) }} +
+ {% endfor %} + {% endif %} +
+ 总计 + {{ "%.2f"|format(month_rewards.values()|sum) if month_rewards is mapping else "%.2f"|format(month_rewards|default(0)) }} +
+
+
+ {% if current_user.role == '统计员' %} +
+

本周奖励统计

+
+
+ + + + + + + + + + + + + + + {% for handler, stats in week_handler_stats.items() %} + + + + + + + + + + + {% endfor %} + +
处理人普通工单手工工单校园安防"4+N"项目综治视联网教育城域网其他预计奖励
{{ handler }}{{ stats.work_orders }}{{ stats.manual_orders }}{{ "%.2f"|format(stats.rewards['校园安防"4+N"项目']|default(0)) }}{{ "%.2f"|format(stats.rewards['综治视联网']|default(0)) }}{{ "%.2f"|format(stats.rewards['教育城域网']|default(0)) }}{{ "%.2f"|format(stats.rewards['其他']|default(0)) }}{{ "%.2f"|format(stats.rewards.values()|sum) }}
+
+
+
+ +
+

本月奖励统计

+
+
+ + + + + + + + + + + + + + + {% for handler, stats in month_handler_stats.items() %} + + + + + + + + + + + {% endfor %} + +
处理人普通工单手工工单校园安防"4+N"项目综治视联网教育城域网其他预计奖励
{{ handler }}{{ stats.work_orders }}{{ stats.manual_orders }}{{ "%.2f"|format(stats.rewards['校园安防"4+N"项目']|default(0)) }}{{ "%.2f"|format(stats.rewards['综治视联网']|default(0)) }}{{ "%.2f"|format(stats.rewards['教育城域网']|default(0)) }}{{ "%.2f"|format(stats.rewards['其他']|default(0)) }}{{ "%.2f"|format(stats.rewards.values()|sum) }}
+
+
+
+ {% endif %} + +
+
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/app/utils/__pycache__/db_utils.cpython-312.pyc b/app/utils/__pycache__/db_utils.cpython-312.pyc new file mode 100644 index 0000000..43d182c Binary files /dev/null and b/app/utils/__pycache__/db_utils.cpython-312.pyc differ diff --git a/app/utils/__pycache__/error_handlers.cpython-312.pyc b/app/utils/__pycache__/error_handlers.cpython-312.pyc new file mode 100644 index 0000000..95aeab1 Binary files /dev/null and b/app/utils/__pycache__/error_handlers.cpython-312.pyc differ diff --git a/app/utils/__pycache__/file_utils.cpython-312.pyc b/app/utils/__pycache__/file_utils.cpython-312.pyc new file mode 100644 index 0000000..0ab7c30 Binary files /dev/null and b/app/utils/__pycache__/file_utils.cpython-312.pyc differ diff --git a/app/utils/__pycache__/file_utils.cpython-313.pyc b/app/utils/__pycache__/file_utils.cpython-313.pyc new file mode 100755 index 0000000..d555636 Binary files /dev/null and b/app/utils/__pycache__/file_utils.cpython-313.pyc differ diff --git a/app/utils/__pycache__/lzmx.cpython-312.pyc b/app/utils/__pycache__/lzmx.cpython-312.pyc new file mode 100644 index 0000000..e0bc134 Binary files /dev/null and b/app/utils/__pycache__/lzmx.cpython-312.pyc differ diff --git a/app/utils/__pycache__/lzmx.cpython-313.pyc b/app/utils/__pycache__/lzmx.cpython-313.pyc new file mode 100755 index 0000000..e3e528c Binary files /dev/null and b/app/utils/__pycache__/lzmx.cpython-313.pyc differ diff --git a/app/utils/__pycache__/reward.cpython-312.pyc b/app/utils/__pycache__/reward.cpython-312.pyc new file mode 100644 index 0000000..101b72b Binary files /dev/null and b/app/utils/__pycache__/reward.cpython-312.pyc differ diff --git a/app/utils/__pycache__/reward.cpython-313.pyc b/app/utils/__pycache__/reward.cpython-313.pyc new file mode 100755 index 0000000..99c0c18 Binary files /dev/null and b/app/utils/__pycache__/reward.cpython-313.pyc differ diff --git a/app/utils/__pycache__/rewards.cpython-313.pyc b/app/utils/__pycache__/rewards.cpython-313.pyc new file mode 100755 index 0000000..51db9d8 Binary files /dev/null and b/app/utils/__pycache__/rewards.cpython-313.pyc differ diff --git a/app/utils/__pycache__/update_zte.cpython-313.pyc b/app/utils/__pycache__/update_zte.cpython-313.pyc new file mode 100755 index 0000000..fa08563 Binary files /dev/null and b/app/utils/__pycache__/update_zte.cpython-313.pyc differ diff --git a/app/utils/__pycache__/wechat.cpython-313.pyc b/app/utils/__pycache__/wechat.cpython-313.pyc new file mode 100755 index 0000000..a333037 Binary files /dev/null and b/app/utils/__pycache__/wechat.cpython-313.pyc differ diff --git a/app/utils/db_utils.py b/app/utils/db_utils.py new file mode 100755 index 0000000..2aa8339 --- /dev/null +++ b/app/utils/db_utils.py @@ -0,0 +1,136 @@ +from functools import wraps +import time +from flask import current_app +import logging +from sqlalchemy.exc import SQLAlchemyError +from scripts.models import db +from collections import defaultdict + +# 简单的内存缓存实现 +class QueryCache: + def __init__(self, max_size=100, ttl=300): # 默认缓存300秒 + self.cache = {} + self.max_size = max_size + self.ttl = ttl + self.access_times = defaultdict(int) + + def get(self, key): + if key in self.cache: + item = self.cache[key] + if time.time() - item['time'] < self.ttl: + self.access_times[key] += 1 + return item['value'] + else: + # 缓存过期,删除 + del self.cache[key] + if key in self.access_times: + del self.access_times[key] + return None + + def set(self, key, value): + # 如果缓存满了,删除最少访问的项 + if len(self.cache) >= self.max_size: + # 找出访问次数最少的键 + if self.access_times: + min_key = min(self.access_times, key=self.access_times.get) + if min_key in self.cache: + del self.cache[min_key] + del self.access_times[min_key] + + self.cache[key] = { + 'value': value, + 'time': time.time() + } + self.access_times[key] = 1 + return value + +# 创建全局缓存实例 +query_cache = QueryCache() + +# 数据库操作装饰器 - 用于缓存结果 +def cache_query(ttl=300): + """ + 缓存查询结果的装饰器 + :param ttl: 缓存有效期(秒) + """ + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + # 生成缓存键 + cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}" + + # 尝试从缓存获取 + cached_result = query_cache.get(cache_key) + if cached_result is not None: + return cached_result + + # 执行查询 + result = func(*args, **kwargs) + + # 设置缓存 + query_cache.set(cache_key, result) + return result + return wrapper + return decorator + +# 数据库操作装饰器 - 用于异常处理 +def db_operation(retries=3, retry_delay=0.5): + """ + 数据库操作的装饰器,提供异常处理和重试功能 + :param retries: 重试次数 + :param retry_delay: 重试延迟(秒) + """ + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + last_error = None + for attempt in range(retries): + try: + return func(*args, **kwargs) + except SQLAlchemyError as e: + last_error = e + current_app.logger.warning(f"数据库操作失败: {str(e)},尝试重试 ({attempt+1}/{retries})") + + # 回滚会话 + db.session.rollback() + + # 如果不是最后一次尝试,则等待后重试 + if attempt < retries - 1: + time.sleep(retry_delay) + + # 所有重试都失败 + current_app.logger.error(f"数据库操作失败(已重试{retries}次): {str(last_error)}") + raise last_error + return wrapper + return decorator + +# 辅助函数:清除特定表相关的所有缓存 +def clear_table_cache(table_name): + """ + 清除与特定表相关的所有缓存 + :param table_name: 表名 + """ + keys_to_delete = [] + for key in list(query_cache.cache.keys()): + if table_name.lower() in key.lower(): + keys_to_delete.append(key) + + for key in keys_to_delete: + if key in query_cache.cache: + del query_cache.cache[key] + if key in query_cache.access_times: + del query_cache.access_times[key] + +# 通用查询函数,包含异常处理和缓存 +@db_operation() +@cache_query() +def get_all_distinct_values(model, column_name): + """ + 获取指定模型指定列的所有不同值 + :param model: 模型类 + :param column_name: 列名 + :return: 所有不同值的列表 + """ + column = getattr(model, column_name) + results = db.session.query(column).distinct().all() + return [result[0] for result in results if result[0]] \ No newline at end of file diff --git a/app/utils/error_handlers.py b/app/utils/error_handlers.py new file mode 100755 index 0000000..b14b0b3 --- /dev/null +++ b/app/utils/error_handlers.py @@ -0,0 +1,150 @@ +from flask import render_template, jsonify, request, flash, redirect, url_for +from sqlalchemy.exc import SQLAlchemyError, IntegrityError, OperationalError +from werkzeug.exceptions import HTTPException, NotFound, Forbidden, BadRequest +import traceback + +class ErrorHandler: + """ + 全局异常处理器 + 用于注册Flask应用程序的错误处理函数 + """ + def __init__(self, app=None): + if app: + self.init_app(app) + + def init_app(self, app): + """ + 初始化应用程序的错误处理器 + :param app: Flask应用实例 + """ + # 处理SQLAlchemy错误 + app.register_error_handler(SQLAlchemyError, self.handle_db_error) + app.register_error_handler(IntegrityError, self.handle_integrity_error) + app.register_error_handler(OperationalError, self.handle_operational_error) + + # 处理HTTP错误 + app.register_error_handler(NotFound, self.handle_not_found) + app.register_error_handler(Forbidden, self.handle_forbidden) + app.register_error_handler(BadRequest, self.handle_bad_request) + + # 处理通用错误 + app.register_error_handler(Exception, self.handle_generic_error) + + # 记录初始化完成 + app.logger.info("错误处理器初始化完成") + + def handle_db_error(self, error): + """ + 处理一般数据库错误 + """ + error_details = str(error) + traceback_details = traceback.format_exc() + current_app = self._get_current_app() + current_app.logger.error(f"数据库错误: {error_details}\n{traceback_details}") + + return self._handle_error("数据库操作错误", + "抱歉,数据库操作失败,请稍后重试。", + 500) + + def handle_integrity_error(self, error): + """ + 处理数据完整性错误(如违反唯一约束) + """ + error_details = str(error) + current_app = self._get_current_app() + current_app.logger.error(f"数据完整性错误: {error_details}") + + # 尝试从错误消息中提取更友好的错误消息 + friendly_message = "数据冲突,请检查您的输入。" + if "Duplicate entry" in error_details: + friendly_message = "数据已存在,请检查您的输入。" + + return self._handle_error("数据完整性错误", friendly_message, 400) + + def handle_operational_error(self, error): + """ + 处理数据库操作错误(如连接问题) + """ + error_details = str(error) + current_app = self._get_current_app() + current_app.logger.error(f"数据库操作错误: {error_details}") + + return self._handle_error("数据库连接错误", + "抱歉,无法连接到数据库,请稍后重试。", + 503) + + def handle_not_found(self, error): + """ + 处理404错误 + """ + return self._handle_error("页面未找到", + "抱歉,您请求的页面不存在。", + 404) + + def handle_forbidden(self, error): + """ + 处理403错误 + """ + return self._handle_error("访问被拒绝", + "抱歉,您没有权限访问此页面。", + 403) + + def handle_bad_request(self, error): + """ + 处理400错误 + """ + return self._handle_error("请求错误", + "抱歉,服务器无法理解您的请求。", + 400) + + def handle_generic_error(self, error): + """ + 处理未被其他处理程序捕获的通用错误 + """ + error_details = str(error) + traceback_details = traceback.format_exc() + current_app = self._get_current_app() + current_app.logger.error(f"未处理异常: {error_details}\n{traceback_details}") + + if isinstance(error, HTTPException): + return self._handle_error("请求错误", error.description, error.code) + + return self._handle_error("服务器错误", + "抱歉,服务器遇到了未知错误,请稍后重试。", + 500) + + def _handle_error(self, title, message, status_code): + """ + 根据请求类型返回适当的错误响应 + :param title: 错误标题 + :param message: 错误消息 + :param status_code: HTTP状态码 + """ + # 根据接受的内容类型决定返回JSON还是HTML + # 检查是否是AJAX请求,is_xhr在新版Flask中已被移除 + is_ajax_request = request.headers.get('X-Requested-With') == 'XMLHttpRequest' + if is_ajax_request or request.headers.get('Accept') == 'application/json': + # 返回JSON格式的错误 + return jsonify({ + 'error': title, + 'message': message + }), status_code + else: + # 使用Flash消息并重定向到统计页面 + from flask import current_app + flash(message) + + # 根据错误类型决定重定向页面 + if status_code == 404: + return redirect(url_for('statistics')) + elif status_code == 403: + return redirect(url_for('statistics')) + else: + return redirect(url_for('statistics')) + + def _get_current_app(self): + """ + 获取当前Flask应用实例 + """ + from flask import current_app + return current_app \ No newline at end of file diff --git a/app/utils/file_utils.py b/app/utils/file_utils.py new file mode 100755 index 0000000..f526e8b --- /dev/null +++ b/app/utils/file_utils.py @@ -0,0 +1,10 @@ +from PIL import Image + +def allowed_file(filename, allowed_extensions): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions + +def compress_image(image_path, max_size=(1500, 1500), quality=85): + """压缩图片""" + img = Image.open(image_path) + img.thumbnail(max_size, Image.Resampling.LANCZOS) + img.save(image_path, quality=quality) \ No newline at end of file diff --git a/app/utils/lzmx.py b/app/utils/lzmx.py new file mode 100755 index 0000000..47b5201 --- /dev/null +++ b/app/utils/lzmx.py @@ -0,0 +1,102 @@ +# utils\lzmx.py +import requests +import json +from datetime import datetime + +# 推送系统工单处理信息到量子密信 +def send_lzmx_message(webhook_url, handler, device_id, device_type, distance_from_branch, restore_time): + headers = {'Content-Type': 'application/json'} + + message = { + "type": "text", + "textMsg": { + "content": + f"📢 故障处理通知\n" + f"故障设备:{device_id}\n" + f"处理时间:{restore_time.strftime('%Y-%m-%d %H:%M:%S')}\n" + f"故障类型:{device_type}\n" + f"距离支局:{distance_from_branch}公里\n" + f"处理人:{handler}" + } + } + + try: + response = requests.post( + webhook_url, + headers=headers, + data=json.dumps(message) + ) + response.raise_for_status() + return True + except Exception as e: + print(f"发送量子密信消息失败: {str(e)}") + return False + +# 推送手工申报工单信息到量子密信 +def send_manual_lzmx_message(webhook_url, handler, device_id, fault_type, distance_from_branch, fault_time, project_type, device_location): + headers = {'Content-Type': 'application/json'} + + message = { + "type": "text", + "textMsg": { + "content": + f"📝 故障处理通知\n" + f"项目类型:{project_type}\n" + f"故障设备:{device_id}\n" + f"设备位置:{device_location}\n" + f"故障时间:{fault_time.strftime('%Y-%m-%d %H:%M:%S')}\n" + f"故障类型:{fault_type}\n" + f"距离支局:{distance_from_branch}公里\n" + f"处理人:{handler}" + } + } + + try: + response = requests.post( + webhook_url, + headers=headers, + data=json.dumps(message) + ) + response.raise_for_status() + return True + except Exception as e: + print(f"发送量子密信消息失败: {str(e)}") + return False + +# 每周五统计本周情况推送量子密信 +def send_weekly_stats_message(webhook_url, stats): + headers = {'Content-Type': 'application/json'} + + # 格式化前三名处理人信息 + top_handlers = [] + for handler, amount in stats['top_handlers']: + top_handlers.append(f"👤 {handler}: {amount}元") + + message = { + "type": "text", + "textMsg": { + "content": + f"📊 本周工单统计报告\n" + f"—————————————————\n" + f" 本周派单:{stats['total_orders']}单\n" + f" 已处理数:{stats['completed_orders']}单\n" + f" 未处理数:{stats['pending_orders']}单\n" + f" 手工故障:{stats['manual_orders']}个\n\n" + f"🏆 前三名预计奖励:\n" + f"{chr(10).join(top_handlers)}\n" + f"—————————————————\n" + f"统计时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + } + } + + try: + response = requests.post( + webhook_url, + headers=headers, + data=json.dumps(message) + ) + response.raise_for_status() + return True + except Exception as e: + print(f"发送量子密信消息失败: {str(e)}") + return False \ No newline at end of file diff --git a/app/utils/pagination.py b/app/utils/pagination.py new file mode 100755 index 0000000..fc9b77a --- /dev/null +++ b/app/utils/pagination.py @@ -0,0 +1,11 @@ +class Pagination: + def __init__(self, items, page, per_page, total): + self.items = items + self.page = page + self.per_page = per_page + self.total = total + self.pages = (total + per_page - 1) // per_page + self.has_prev = page > 1 + self.has_next = page < self.pages + self.prev_num = page - 1 + self.next_num = page + 1 \ No newline at end of file diff --git a/app/utils/reward.py b/app/utils/reward.py new file mode 100755 index 0000000..8bab190 --- /dev/null +++ b/app/utils/reward.py @@ -0,0 +1,66 @@ +def get_reward_by_distance(project_type, distance): + """ + 根据项目类型和距离计算奖励金额 + + Args: + project_type (str): 项目类型 + distance (float): 距离(公里) + + Returns: + float: 奖励金额(元) + """ + # 2025.04.21 by v6ole + # 奖励配置说明: + # 格式为: '项目类型': [(距离上限1, 奖励金额1), (距离上限2, 奖励金额2), ...] + # 距离单位: 公里, 金额单位: 元 + # 配置按照距离从小到大排序,系统会自动匹配第一个符合条件的奖励金额 + rewards_config = { + # 校园安防"4+N"项目奖励标准: + # - 0-5公里: 20元/台 + # - 5-30公里: 40元/台 + # - 30-50公里: 50元/台 + # - 50公里以上: 60元/台 + '校园安防"4+N"项目': [ + (5, 20), + (30, 40), + (50, 50), + (float('inf'), 60) + ], + + # 综治视联网奖励标准: + # - 0-10公里: 30元/台 + # - 10-30公里: 40元/台 + # - 30-50公里: 50元/台 + # - 50公里以上: 60元/台 + '综治视联网': [ + (10, 30), + (30, 40), + (50, 50), + (float('inf'), 60) + ], + + # 教育城域网奖励标准: + # - 0-5公里: 20元/台 + # - 5-30公里: 30元/台 + # - 30-50公里: 40元/台 + # - 50公里以上: 50元/台 + '教育城域网': [ + (5, 20), + (30, 30), + (50, 40), + (float('inf'), 50) + ], + + # 其他项目统一标准: + # - 任何距离: 30元/台 + '其他': [(float('inf'), 30)] + } + + if project_type not in rewards_config: + return 0 + + for max_distance, reward in rewards_config[project_type]: + if distance <= max_distance: + return reward + + return 0 \ No newline at end of file diff --git a/app/uwsgi.ini b/app/uwsgi.ini new file mode 100755 index 0000000..83a34ba --- /dev/null +++ b/app/uwsgi.ini @@ -0,0 +1,99 @@ +[uwsgi] +# 指定 WSGI 应用的入口模块和对象 +# 格式为 module:callable,例如 app:app 表示模块名为 app.py,应用对象为 app +module = app:app + +# 指定 uWSGI 监听的 HTTP 地址和端口 +# 如果使用 Nginx 反向代理,可以注释掉此行 +http = 0.0.0.0:18019 + +# 设置 uWSGI 启动的进程数 +# 根据服务器性能和需求调整进程数 +processes = 4 + +# 设置每个进程的线程数 +# 线程数和进程数共同影响并发处理能力 +threads = 2 + +# 启用主进程 +master = true + +# 优雅地重启工作进程 +# 当收到SIGINT信号时,等待当前请求完成后再重启 +reload-mercy = 10 + +# 进程超时时间(秒),如果一个请求处理时间超过这个值,工作进程会被杀死 +harakiri = 30 + +# 接收到请求的队列最大长度,如果队列已满,新的请求将被拒绝 +listen = 100 + +# 最大并发请求数 +max-requests = 1000 + +# 每个工作进程处理完指定数量的请求后自动重启,防止内存泄漏 +max-worker-lifetime = 3600 + +# 缓冲区大小设置 +buffer-size = 32768 + +# 指定日志文件的路径 +# uWSGI 的运行日志将写入此文件 +logto = /opt/1panel/docker/compose/4an/uwsgi.log + +# 指定虚拟环境的路径 +# uWSGI 将使用此虚拟环境中的 Python 解释器和依赖库 +virtualenv = /mnt/nfs/.venv + +# 设置应用的根目录 +# uWSGI 将切换到此目录运行,确保应用的相对路径正确 +chdir = /opt/1panel/docker/compose/4an/app + +# 设置 PID 文件的路径 +# PID 文件用于管理 uWSGI 进程(如停止、重启) +pidfile = /opt/1panel/docker/compose/4an/uwsgi.pid + +# 设置 Unix Socket 文件的路径 +# 用于与 Nginx 或其他代理服务器通信 +# 如果使用 HTTP 模式,可以注释掉此行 +socket = /opt/1panel/docker/compose/4an/uwsgi.sock + +# 设置 Socket 文件的权限 +# 660 表示只有所有者和所属组有读写权限 +chmod-socket = 660 + +# 当 uWSGI 退出时,自动清理其创建的套接字和 PID 文件 +# 保持环境整洁,避免遗留文件 +vacuum = true + +# 设置日志文件的最大大小(单位为字节) +# 当日志文件达到此大小时,uWSGI 将自动轮转日志 +# 示例值为 10MB +log-maxsize = 10000000 + +# 禁用请求日志,减少IO负担 +disable-logging = true +log-slow = 1000 # 只记录处理时间超过1000ms的请求 + +# 紧急情况下尝试恢复的时间(秒) +worker-reload-mercy = 60 + +# 自动重载配置 +py-autoreload = 1 + +# 内存报告 +memory-report = true + +# 启用统计服务器 +stats = 127.0.0.1:9191 + +# 平滑重启 +touch-chain-reload = true +touch-reload = uwsgi.reload +reload-on-exception = true # 当出现异常时自动重启 + +# 启用线程支持 +enable-threads = true + +# 设置自动异常处理 +catch-exceptions = true \ No newline at end of file diff --git a/app/views/__pycache__/about.cpython-312.pyc b/app/views/__pycache__/about.cpython-312.pyc new file mode 100755 index 0000000..166383a Binary files /dev/null and b/app/views/__pycache__/about.cpython-312.pyc differ diff --git a/app/views/__pycache__/about.cpython-313.pyc b/app/views/__pycache__/about.cpython-313.pyc new file mode 100755 index 0000000..7de6146 Binary files /dev/null and b/app/views/__pycache__/about.cpython-313.pyc differ diff --git a/app/views/__pycache__/auth.cpython-312.pyc b/app/views/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000..c7659a1 Binary files /dev/null and b/app/views/__pycache__/auth.cpython-312.pyc differ diff --git a/app/views/__pycache__/auth.cpython-313.pyc b/app/views/__pycache__/auth.cpython-313.pyc new file mode 100755 index 0000000..7c47105 Binary files /dev/null and b/app/views/__pycache__/auth.cpython-313.pyc differ diff --git a/app/views/__pycache__/dashboard.cpython-312.pyc b/app/views/__pycache__/dashboard.cpython-312.pyc new file mode 100644 index 0000000..c6e1e4a Binary files /dev/null and b/app/views/__pycache__/dashboard.cpython-312.pyc differ diff --git a/app/views/__pycache__/device.cpython-312.pyc b/app/views/__pycache__/device.cpython-312.pyc new file mode 100755 index 0000000..587c68e Binary files /dev/null and b/app/views/__pycache__/device.cpython-312.pyc differ diff --git a/app/views/__pycache__/device.cpython-313.pyc b/app/views/__pycache__/device.cpython-313.pyc new file mode 100755 index 0000000..a914fbb Binary files /dev/null and b/app/views/__pycache__/device.cpython-313.pyc differ diff --git a/app/views/__pycache__/history.cpython-312.pyc b/app/views/__pycache__/history.cpython-312.pyc new file mode 100755 index 0000000..be87f05 Binary files /dev/null and b/app/views/__pycache__/history.cpython-312.pyc differ diff --git a/app/views/__pycache__/history.cpython-313.pyc b/app/views/__pycache__/history.cpython-313.pyc new file mode 100755 index 0000000..25588fa Binary files /dev/null and b/app/views/__pycache__/history.cpython-313.pyc differ diff --git a/app/views/__pycache__/manual.cpython-312.pyc b/app/views/__pycache__/manual.cpython-312.pyc new file mode 100755 index 0000000..043bb5a Binary files /dev/null and b/app/views/__pycache__/manual.cpython-312.pyc differ diff --git a/app/views/__pycache__/manual.cpython-313.pyc b/app/views/__pycache__/manual.cpython-313.pyc new file mode 100755 index 0000000..29c7f03 Binary files /dev/null and b/app/views/__pycache__/manual.cpython-313.pyc differ diff --git a/app/views/__pycache__/statistics.cpython-312.pyc b/app/views/__pycache__/statistics.cpython-312.pyc new file mode 100755 index 0000000..1168521 Binary files /dev/null and b/app/views/__pycache__/statistics.cpython-312.pyc differ diff --git a/app/views/__pycache__/statistics.cpython-313.pyc b/app/views/__pycache__/statistics.cpython-313.pyc new file mode 100755 index 0000000..9465664 Binary files /dev/null and b/app/views/__pycache__/statistics.cpython-313.pyc differ diff --git a/app/views/__pycache__/work_order.cpython-312.pyc b/app/views/__pycache__/work_order.cpython-312.pyc new file mode 100644 index 0000000..7a0d7c4 Binary files /dev/null and b/app/views/__pycache__/work_order.cpython-312.pyc differ diff --git a/app/views/__pycache__/work_order.cpython-313.pyc b/app/views/__pycache__/work_order.cpython-313.pyc new file mode 100755 index 0000000..2634d71 Binary files /dev/null and b/app/views/__pycache__/work_order.cpython-313.pyc differ diff --git a/app/views/about.py b/app/views/about.py new file mode 100755 index 0000000..16f4953 --- /dev/null +++ b/app/views/about.py @@ -0,0 +1,8 @@ +from flask import render_template +from flask_login import login_required + +def init_about_routes(app): + @app.route('/about') + @login_required + def about(): + return render_template('about.html') \ No newline at end of file diff --git a/app/views/auth.py b/app/views/auth.py new file mode 100755 index 0000000..907754e --- /dev/null +++ b/app/views/auth.py @@ -0,0 +1,26 @@ +from flask import render_template, request, redirect, url_for, flash +from flask_login import login_user, login_required, logout_user, current_user +from scripts.models import db, User + +def init_auth_routes(app): + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + phone = request.form['phone'] + user = User.query.filter_by(phone=phone).first() + if user: + login_user(user) + app.logger.info(f'用户 {user.name}({user.phone}) 登录成功') + # 无论是统计员还是装维员,都跳转到仪表盘 + return redirect(url_for('dashboard')) + else: + app.logger.warning(f'登录失败:用户不存在 (手机号: {phone})') + flash('用户不存在') + return render_template('login.html') + + @app.route('/logout') + @login_required + def logout(): + app.logger.info(f'用户 {current_user.name}({current_user.phone}) 登出') + logout_user() + return redirect(url_for('login')) \ No newline at end of file diff --git a/app/views/dashboard.py b/app/views/dashboard.py new file mode 100755 index 0000000..80dcadc --- /dev/null +++ b/app/views/dashboard.py @@ -0,0 +1,197 @@ +from flask import render_template, flash, redirect, url_for +from flask_login import login_required, current_user +from scripts.models import db, Device, WorkOrder +from collections import defaultdict +from sqlalchemy import func +from utils.db_utils import cache_query, db_operation + +def init_dashboard_routes(app): + @app.route('/dashboard') + @login_required + def dashboard(): + if current_user.role not in ['装维员', '统计员']: + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试访问仪表盘页面') + flash('您没有权限访问此页面') + return redirect(url_for('login')) + + try: + app.logger.info(f'用户 {current_user.name} 访问仪表盘页面') + + # 获取设备在线率数据 + overall_stats = get_overall_device_stats() + branch_stats = get_branch_device_stats() + town_stats = get_town_device_stats() + + return render_template('dashboard.html', + overall_stats=overall_stats, + branch_stats=branch_stats, + town_stats=town_stats) + except Exception as e: + app.logger.error(f"仪表盘页面加载失败: {str(e)}") + flash('数据加载失败,请稍后重试') + return redirect(url_for('login')) + + @cache_query(ttl=300) # 缓存5分钟 + def get_overall_device_stats(): + """获取总体设备在线率统计""" + stats = {} + + # 获取设备类型列表 + device_types = db.session.query(Device.device_type).distinct().all() + device_types = [d[0] for d in device_types if d[0]] + + for device_type in device_types: + # 获取该类型的设备总数 + total_devices = db.session.query(func.count(Device.device_id))\ + .filter(Device.device_type == device_type)\ + .scalar() or 0 + + # 获取该类型的派单数 + work_orders = db.session.query(func.count(WorkOrder.order_id))\ + .join(Device, WorkOrder.device_id == Device.device_id)\ + .filter(Device.device_type == device_type)\ + .scalar() or 0 + + # 计算在线率 + if total_devices > 0: + online_rate = ((total_devices - work_orders) / total_devices) * 100 + else: + online_rate = 0 + + stats[device_type] = { + 'total': total_devices, + 'work_orders': work_orders, + 'online_rate': round(online_rate, 2) + } + + return stats + + @cache_query(ttl=300) # 缓存5分钟 + def get_branch_device_stats(): + """获取各支局设备在线率统计""" + stats = {} + + # 获取所有支局 + branches = db.session.query(Device.branch).distinct().all() + branches = [b[0] for b in branches if b[0]] + + # 获取设备类型列表 + device_types = db.session.query(Device.device_type).distinct().all() + device_types = [d[0] for d in device_types if d[0]] + + for branch in branches: + branch_stats = {} + + for device_type in device_types: + # 获取该支局该类型的设备总数 + total_devices = db.session.query(func.count(Device.device_id))\ + .filter(Device.branch == branch, Device.device_type == device_type)\ + .scalar() or 0 + + # 获取该支局该类型的派单数 + work_orders = db.session.query(func.count(WorkOrder.order_id))\ + .join(Device, WorkOrder.device_id == Device.device_id)\ + .filter(Device.branch == branch, Device.device_type == device_type)\ + .scalar() or 0 + + # 计算在线率 + if total_devices > 0: + online_rate = ((total_devices - work_orders) / total_devices) * 100 + else: + online_rate = 0 + + branch_stats[device_type] = { + 'total': total_devices, + 'work_orders': work_orders, + 'online_rate': round(online_rate, 2) + } + + # 计算该支局总体在线率 + total_branch_devices = db.session.query(func.count(Device.device_id))\ + .filter(Device.branch == branch)\ + .scalar() or 0 + + total_branch_orders = db.session.query(func.count(WorkOrder.order_id))\ + .join(Device, WorkOrder.device_id == Device.device_id)\ + .filter(Device.branch == branch)\ + .scalar() or 0 + + if total_branch_devices > 0: + branch_online_rate = ((total_branch_devices - total_branch_orders) / total_branch_devices) * 100 + else: + branch_online_rate = 0 + + branch_stats['总计'] = { + 'total': total_branch_devices, + 'work_orders': total_branch_orders, + 'online_rate': round(branch_online_rate, 2) + } + + stats[branch] = branch_stats + + return stats + + @cache_query(ttl=300) # 缓存5分钟 + def get_town_device_stats(): + """获取各乡镇设备在线率统计""" + stats = {} + + # 获取所有乡镇 + towns = db.session.query(Device.town).distinct().all() + towns = [t[0] for t in towns if t[0]] + + # 获取设备类型列表 + device_types = db.session.query(Device.device_type).distinct().all() + device_types = [d[0] for d in device_types if d[0]] + + for town in towns: + town_stats = {} + + for device_type in device_types: + # 获取该乡镇该类型的设备总数 + total_devices = db.session.query(func.count(Device.device_id))\ + .filter(Device.town == town, Device.device_type == device_type)\ + .scalar() or 0 + + # 获取该乡镇该类型的派单数 + work_orders = db.session.query(func.count(WorkOrder.order_id))\ + .join(Device, WorkOrder.device_id == Device.device_id)\ + .filter(Device.town == town, Device.device_type == device_type)\ + .scalar() or 0 + + # 计算在线率 + if total_devices > 0: + online_rate = ((total_devices - work_orders) / total_devices) * 100 + else: + online_rate = 0 + + town_stats[device_type] = { + 'total': total_devices, + 'work_orders': work_orders, + 'online_rate': round(online_rate, 2) + } + + # 计算该乡镇总体在线率 + total_town_devices = db.session.query(func.count(Device.device_id))\ + .filter(Device.town == town)\ + .scalar() or 0 + + total_town_orders = db.session.query(func.count(WorkOrder.order_id))\ + .join(Device, WorkOrder.device_id == Device.device_id)\ + .filter(Device.town == town)\ + .scalar() or 0 + + if total_town_devices > 0: + town_online_rate = ((total_town_devices - total_town_orders) / total_town_devices) * 100 + else: + town_online_rate = 0 + + town_stats['总计'] = { + 'total': total_town_devices, + 'work_orders': total_town_orders, + 'online_rate': round(town_online_rate, 2) + } + + stats[town] = town_stats + + return stats \ No newline at end of file diff --git a/app/views/device.py b/app/views/device.py new file mode 100755 index 0000000..4058fb6 --- /dev/null +++ b/app/views/device.py @@ -0,0 +1,297 @@ +from flask import jsonify, request, send_file, session, current_app +from flask_login import login_required, current_user +from scripts.update.zte import LoginClient +from scripts.models import db, Device, WorkOrder, Channel +from datetime import datetime +from sqlalchemy import text, create_engine +import hashlib +import pymysql +import io +import os + + +def init_device_routes(app): + @app.route('/sync_offline_devices', methods=['POST']) + @login_required + def sync_offline_devices(): + if current_user.role not in ['装维员', '统计员']: + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试同步离线设备') + return jsonify({'success': False, 'message': '没有权限执行此操作'}) + + try: + app.logger.info(f'用户 {current_user.name} 开始同步离线设备') + # 从配置中获取数据库连接信息 + db_config = current_app.config['KEY110_DB'] + conn = pymysql.connect(**db_config) + + cursor = conn.cursor() + + # 获取所有教学点的状态 + cursor.execute(""" + SELECT 教学点名称, 连通状态 + FROM IP数据 + """) + + schools = cursor.fetchall() + + # 分别处理在线和离线的教学点 + offline_school_names = [school[0].strip().replace(' ', ' ') for school in schools if school[1] == '离线'] + online_school_names = [school[0].strip().replace(' ', ' ') for school in schools if school[1] == '在线'] + + # 关闭1key110数据库连接 + cursor.close() + conn.close() + + # 处理恢复在线的设备 + online_devices = Device.query.filter( + Device.device_id.in_(online_school_names), + Device.is_normal == False # 当前状态为故障的设备 + ).all() + + # 删除恢复在线设备的在途工单 + recovered_count = 0 + for device in online_devices: + # 查找并删除该设备的在途工单 + pending_orders = WorkOrder.query.filter_by( + device_id=device.device_id, + status='待处理' + ).all() + + for order in pending_orders: + db.session.delete(order) + recovered_count += 1 + + # 更新设备状态为正常 + device.is_normal = True + db.session.add(device) + + # 处理离线设备 + offline_devices = Device.query.filter( + Device.device_id.in_(offline_school_names), + Device.in_use == True, + Device.is_normal == True + ).all() + + # 生成工单号 + today = datetime.now().strftime('%Y%m%d') + last_work_order = WorkOrder.query.filter( + WorkOrder.order_id.like(f'{today}%') + ).order_by(WorkOrder.order_id.desc()).first() + + start_number = int(last_work_order.order_id[-4:]) + 1 if last_work_order else 1 + + # 创建工单 + created_count = 0 + for index, device in enumerate(offline_devices): + # 检查是否已有在途工单 + existing_order = WorkOrder.query.filter_by( + device_id=device.device_id, + status='待处理' + ).first() + + if existing_order: + continue + + current_number = start_number + index + new_number = f"{today}{str(current_number).zfill(4)}" + + work_order = WorkOrder( + order_id=new_number, + device_id=device.device_id, + dispatch_time=datetime.now(), + status='待处理', + branch=device.branch + ) + + db.session.add(work_order) + device.is_normal = False + db.session.add(device) + created_count += 1 + + db.session.commit() + + app.logger.info(f'同步完成:创建了 {created_count} 个工单,恢复了 {recovered_count} 个设备') + return jsonify({ + 'success': True, + 'count': created_count + }) + + except Exception as e: + app.logger.error(f'同步离线设备失败:{str(e)}') + return jsonify({ + 'success': False, + 'message': str(e) + }) + + @app.route('/check_existing_orders', methods=['POST']) + @login_required + def check_existing_orders(): + device_ids = request.json.get('device_ids', []) + app.logger.info(f'用户 {current_user.name} 检查设备工单状态,设备数量:{len(device_ids)}') + + # 查询设备状态 + existing_devices = Device.query.filter( + Device.device_id.in_(device_ids), + Device.is_normal == False # 检查设备是否处于故障状态 + ).with_entities(Device.device_id).all() + + # 提取设备ID列表 + existing_device_ids = [device.device_id for device in existing_devices] + app.logger.info(f'发现 {len(existing_device_ids)} 个设备存在故障工单') + + return jsonify({'existing_devices': existing_device_ids}) + + @app.route('/get_captcha', methods=['GET']) + def get_captcha(): + app.logger.info('用户请求验证码') + try: + client = LoginClient() + cid = client.get_captcha() + # 将 cid 存储在 session 中供后续使用 + session['zte_cid'] = cid + + # 读取验证码图片并返回给前端 + captcha_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), + 'static', 'captcha.png') + with open(captcha_path, "rb") as f: + app.logger.info('验证码获取成功') + return send_file( + io.BytesIO(f.read()), + mimetype='image/png' + ) + except Exception as e: + app.logger.error(f'获取验证码失败:{str(e)}') + return jsonify({'success': False, 'message': '获取验证码失败'}) + + @app.route('/sync_camera_step1', methods=['POST']) + def sync_camera_step1(): + try: + app.logger.info(f'用户 {current_user.name if current_user else "未登录"} 开始同步摄像头第一步') + data = request.get_json() + captcha = data.get('captcha') + cid = session.get('zte_cid') + + if not captcha or not cid: + app.logger.warning('同步摄像头失败:验证码或会话ID缺失') + return jsonify({'success': False, 'message': 'Invalid request'}) + + client = LoginClient() + # 登录 + app.logger.info('尝试登录中兴平台') + login_result = client.login( + account="DHDX001", + password=hashlib.sha256("Root1234#".encode()).hexdigest(), + validcode=captcha, + cid=cid + ) + + if login_result.get("result") != 0: + app.logger.warning('登录中兴平台失败:验证码错误') + return jsonify({'success': False, 'message': 'INVALID_CAPTCHA'}) + + token = login_result.get("token") + app.logger.info('登录成功,开始获取在线信息') + # 获取在线信息 + online_info = client.get_online_info(token) + + if online_info.get("result") == 0: + rows = online_info.get("rows", []) + updated_count = 0 + offline_count = 0 + + app.logger.info(f'获取到 {len(rows)} 个设备信息,开始更新数据库') + # 更新 channels 表 + for row in rows: + db.session.execute( + text(""" + INSERT INTO channels (puid, pu_name, status) + VALUES (:puid, :pu_name, :status) + ON DUPLICATE KEY UPDATE + pu_name = :pu_name, status = :status + """), + { + 'puid': row['puid'], + 'pu_name': row['pu_name'], + 'status': row['status'] + } + ) + updated_count += 1 + if row['status'] != 'online': + offline_count += 1 + + db.session.commit() + app.logger.info(f'数据库更新完成,更新了 {updated_count} 条记录,其中离线设备 {offline_count} 个') + return jsonify({ + 'success': True, + 'updated_count': updated_count, + 'offline_count': offline_count + }) + + app.logger.error('获取在线信息失败') + return jsonify({'success': False, 'message': 'Failed to get online info'}) + + except Exception as e: + app.logger.error(f'同步摄像头步骤1失败:{str(e)}') + return jsonify({'success': False, 'message': str(e)}) + + @app.route('/sync_camera_step2', methods=['POST']) + def sync_camera_step2(): + try: + app.logger.info(f'用户 {current_user.name if current_user else "未登录"} 开始同步摄像头第二步') + # 更新设备状态 + devices = Device.query.filter( + Device.puid.isnot(None), + Device.in_use == True # 添加这个条件,只检查在用设备 + ).all() + app.logger.info(f'查询到 {len(devices)} 个需要同步的设备') + offline_devices = [] + + for device in devices: + channel = db.session.query(Channel).filter_by(puid=device.puid).first() + if channel and channel.status != 'online': + device.is_normal = 0 + # 检查是否已有在途工单 + existing_order = WorkOrder.query.filter_by( + device_id=device.device_id, + status='待处理' + ).first() + + if not existing_order: # 只添加没有在途工单的设备 + offline_devices.append(device) + + db.session.commit() + app.logger.info(f'发现 {len(offline_devices)} 个离线设备需要创建工单') + + # 为离线设备创建工单 + created_count = 0 + if offline_devices: + today = datetime.now().strftime('%Y%m%d') + last_work_order = WorkOrder.query.filter( + WorkOrder.order_id.like(f'{today}%') + ).order_by(WorkOrder.order_id.desc()).first() + + start_number = int(last_work_order.order_id[-4:]) + 1 if last_work_order else 1 + + for index, device in enumerate(offline_devices): + order_id = f"{today}{str(start_number + index).zfill(4)}" + work_order = WorkOrder( + order_id=order_id, + device_id=device.device_id, + status='待处理', + dispatch_time=datetime.now(), + branch=device.branch # 添加支局信息 + ) + db.session.add(work_order) + created_count += 1 + + db.session.commit() + app.logger.info(f'成功创建 {created_count} 个工单') + + return jsonify({ + 'success': True, + 'count': created_count + }) + + except Exception as e: + app.logger.error(f'同步摄像头步骤2失败:{str(e)}') + return jsonify({'success': False, 'message': str(e)}) \ No newline at end of file diff --git a/app/views/history.py b/app/views/history.py new file mode 100755 index 0000000..c7ae213 --- /dev/null +++ b/app/views/history.py @@ -0,0 +1,133 @@ +from flask import render_template, request, flash, redirect, url_for +from flask_login import login_required, current_user +from scripts.models import db, WorkOrderHandling, ManualWorkOrder +from datetime import datetime + +def init_history_routes(app): + @app.route('/history') + @login_required + def history(): + if current_user.role not in ['装维员', '统计员']: + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试访问历史记录') + flash('您没有权限访问此页面') + return redirect(url_for('statistics')) + + app.logger.info(f'用户 {current_user.name} 访问历史记录页面') + page = request.args.get('page', 1, type=int) + per_page = 20 + search = request.args.get('search', '') + + # 查询普通工单 + if current_user.role == '统计员': + query1 = WorkOrderHandling.query + query2 = ManualWorkOrder.query + else: + # 装维员只能看到自己处理的工单 + query1 = WorkOrderHandling.query.filter_by(handler=current_user.name) + query2 = ManualWorkOrder.query.filter_by(handler=current_user.name) + + # 添加搜索条件 + if search: + query1 = query1.filter( + db.or_( + WorkOrderHandling.order_id.contains(search), + WorkOrderHandling.device_id.contains(search), + WorkOrderHandling.branch.contains(search), + WorkOrderHandling.handler.contains(search), + WorkOrderHandling.fault_type.contains(search), + WorkOrderHandling.handle_description.contains(search), + WorkOrderHandling.contact_person.contains(search), + WorkOrderHandling.remark.contains(search) + ) + ) + query2 = query2.filter( + db.or_( + ManualWorkOrder.order_id.contains(search), + ManualWorkOrder.device_id.contains(search), + ManualWorkOrder.branch.contains(search), + ManualWorkOrder.handler.contains(search), + ManualWorkOrder.fault_type.contains(search), + ManualWorkOrder.handle_description.contains(search), + ManualWorkOrder.contact_person.contains(search), + ManualWorkOrder.remark.contains(search) + ) + ) + + # 合并两种工单并按时间排序 + handlings1 = query1.all() + handlings2 = query2.all() + + # 将手工故障单转换为与普通工单相同的格式 + combined_handlings = [] + for h in handlings1: + combined_handlings.append({ + 'order_id': h.order_id, + 'branch': h.branch, + 'device_id': h.device_id, + 'handler': h.handler, + 'time': h.restore_time, + 'fault_type': h.fault_type, + 'type': 'normal' + }) + + for h in handlings2: + combined_handlings.append({ + 'order_id': h.order_id, + 'branch': h.branch, + 'device_id': h.device_id, + 'handler': h.handler, + 'time': h.create_time, + 'fault_type': h.fault_type, + 'type': 'manual' + }) + + # 按时间排序 + combined_handlings.sort(key=lambda x: x['time'], reverse=True) + + # 手动分页 + total = len(combined_handlings) + start = (page - 1) * per_page + end = start + per_page + current_page = combined_handlings[start:end] + + # 创建分页对象 + class Pagination: + def __init__(self, items, page, per_page, total): + self.items = items + self.page = page + self.per_page = per_page + self.total = total + self.pages = (total + per_page - 1) // per_page + self.has_prev = page > 1 + self.has_next = page < self.pages + self.prev_num = page - 1 + self.next_num = page + 1 + + handlings = Pagination(current_page, page, per_page, total) + + return render_template('history.html', handlings=handlings, search=search) + + @app.route('/history/') + @login_required + def history_detail(order_id): + if current_user.role not in ['装维员', '统计员']: + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试查看工单详情 {order_id}') + flash('您没有权限访问此页面') + return redirect(url_for('statistics')) + + app.logger.info(f'用户 {current_user.name} 查看工单 {order_id} 的详情') + # 根据工单号前缀判断是普通工单还是手工故障单 + if order_id.startswith('M'): + handling = ManualWorkOrder.query.get_or_404(order_id) + else: + handling = WorkOrderHandling.query.filter_by(order_id=order_id).first_or_404() + + # 装维员只能查看自己处理的工单 + if current_user.role == '装维员' and handling.handler != current_user.name: + flash('您没有权限查看此工单') + return redirect(url_for('history')) + + # 传递工单类型到模板 + order_type = 'manual' if order_id.startswith('M') else 'normal' + + return render_template('history_detail.html', handling=handling, order_type=order_type) diff --git a/app/views/manual.py b/app/views/manual.py new file mode 100755 index 0000000..0889b2d --- /dev/null +++ b/app/views/manual.py @@ -0,0 +1,85 @@ +from flask import render_template, request, jsonify, flash, redirect, url_for +from flask_login import login_required, current_user +from scripts.models import db, Device, ManualWorkOrder +from datetime import datetime +import os +from utils.file_utils import allowed_file, compress_image +from utils.lzmx import send_manual_lzmx_message +from config import Config + +def init_manual_routes(app): + @app.route('/manual', methods=['GET', 'POST']) + @login_required + def manual(): + if current_user.role not in ['装维员', '统计员']: + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试访问手工故障单页面') + flash('您没有权限访问此页面') + return redirect(url_for('statistics')) + + if request.method == 'POST': + app.logger.info(f'用户 {current_user.name} 开始创建手工故障单') + # 处理文件上传 + photo_paths = [] + if 'photo' in request.files: + files = request.files.getlist('photo') + if len(files) > 5: + flash('最多只能上传5张图片') + return redirect(url_for('manual')) + for file in files: + if file and allowed_file(file.filename, Config.ALLOWED_EXTENSIONS): # 修改这里 + filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}" + file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + file.save(file_path) + compress_image(file_path) + photo_paths.append(filename) + + # 生成工单号 + today = datetime.now().strftime('%Y%m%d') + last_order = ManualWorkOrder.query.filter( + ManualWorkOrder.order_id.like(f'M{today}%') + ).order_by(ManualWorkOrder.order_id.desc()).first() + + if last_order: + last_number = int(last_order.order_id[-4:]) + new_number = f"M{today}{str(last_number + 1).zfill(4)}" + else: + new_number = f"M{today}0001" + + # 创建手工故障单 + manual_order = ManualWorkOrder( + order_id=new_number, + project_type=request.form['project_type'], + device_id=request.form['device_id'], + device_location=request.form['device_location'], + fault_time=datetime.strptime(request.form['fault_time'], '%Y-%m-%dT%H:%M'), + handler=request.form['handler'], + fault_type=request.form['fault_type'], + handle_description=request.form['handle_description'], + photo_paths=','.join(photo_paths) if photo_paths else None, + contact_person=request.form['contact_person'], + contact_phone=request.form['contact_phone'], + distance_from_branch=request.form['distance_from_branch'], + remark=request.form.get('remark', ''), + branch=current_user.branch + ) + + db.session.add(manual_order) + db.session.commit() + + # 发送量子密信通知 + send_manual_lzmx_message( + Config.LZMX_WEBHOOK_URL, # 修改这里 + request.form['handler'], + request.form['device_id'], + request.form['fault_type'], + request.form['distance_from_branch'], + manual_order.fault_time, + request.form['project_type'], + request.form['device_location'] + ) + + app.logger.info(f'用户 {current_user.name} 创建手工故障单成功:{new_number}') + flash('手工故障单创建成功') + return redirect(url_for('history')) + + return render_template('manual.html') \ No newline at end of file diff --git a/app/views/statistics.py b/app/views/statistics.py new file mode 100755 index 0000000..4047b7c --- /dev/null +++ b/app/views/statistics.py @@ -0,0 +1,325 @@ +from flask import render_template, flash, redirect, url_for, send_file +from flask_login import login_required, current_user +from scripts.models import db, WorkOrder, WorkOrderHandling, ManualWorkOrder +from datetime import datetime, timedelta +from collections import defaultdict +from openpyxl import Workbook +from openpyxl.styles import Font, Alignment +from utils.reward import get_reward_by_distance +from scripts.models import Device +import io + +def init_statistics_routes(app): + @app.route('/statistics') + @login_required + def statistics(): + if current_user.role not in ['装维员', '统计员']: + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试访问统计页面') + flash('您没有权限访问此页面') + return redirect(url_for('login')) + + app.logger.info(f'用户 {current_user.name} 访问统计页面') + # 获取当前时间 + now = datetime.now() + + # 计算本周开始和结束时间 + week_start = now - timedelta(days=now.weekday()) + week_start = week_start.replace(hour=0, minute=0, second=0, microsecond=0) + week_end = week_start + timedelta(days=7) + + # 计算本月开始和结束时间 + month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + if now.month == 12: + month_end = now.replace(year=now.year + 1, month=1, day=1) + else: + month_end = now.replace(month=now.month + 1, day=1) + + # 统计普通工单 + def get_work_order_stats(start_time, end_time): + total = WorkOrder.query.filter( + WorkOrder.dispatch_time >= start_time, + WorkOrder.dispatch_time < end_time + ).count() + + completed = WorkOrder.query.filter( + WorkOrder.dispatch_time >= start_time, + WorkOrder.dispatch_time < end_time, + WorkOrder.status == '已处理' + ).count() + + pending = total - completed + return {'total': total, 'completed': completed, 'pending': pending} + + # 统计手工故障单 + def get_manual_order_stats(start_time, end_time): + total = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= start_time, + ManualWorkOrder.create_time < end_time + ).count() + return total + + # 计算奖励金额 + def calculate_reward(start_time, end_time): + rewards = defaultdict(float) + + # 查询所有已处理的工单 + handlings = WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= start_time, + WorkOrderHandling.restore_time < end_time + ).all() + + # 查询所有手工故障单 + manual_orders = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= start_time, + ManualWorkOrder.create_time < end_time + ).all() + + # 计算普通工单奖励 + for handling in handlings: + device = Device.query.get(handling.device_id) + if device: + distance = float(handling.distance_from_branch) + reward = get_reward_by_distance(device.project_type, distance) + rewards[device.project_type] += reward + + # 计算手工故障单奖励 + for order in manual_orders: + distance = float(order.distance_from_branch) + reward = get_reward_by_distance(order.project_type, distance) + rewards[order.project_type] += reward + + return rewards + + def get_handler_stats(start_time, end_time): + handler_stats = defaultdict(lambda: { + 'work_orders': 0, + 'manual_orders': 0, + 'rewards': defaultdict(float) + }) + + # 统计普通工单 + handlings = WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= start_time, + WorkOrderHandling.restore_time < end_time + ).all() + + for handling in handlings: + handler = handling.handler + handler_stats[handler]['work_orders'] += 1 + device = Device.query.get(handling.device_id) + if device: + distance = float(handling.distance_from_branch) + reward = get_reward_by_distance(device.project_type, distance) + handler_stats[handler]['rewards'][device.project_type] += reward + + # 统计手工故障单 + manual_orders = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= start_time, + ManualWorkOrder.create_time < end_time + ).all() + + for order in manual_orders: + handler = order.handler + handler_stats[handler]['manual_orders'] += 1 + distance = float(order.distance_from_branch) + reward = get_reward_by_distance(order.project_type, distance) + handler_stats[handler]['rewards'][order.project_type] += reward + + return handler_stats + + # 获取统计数据 + week_work_stats = get_work_order_stats(week_start, week_end) + month_work_stats = get_work_order_stats(month_start, month_end) + + week_manual_stats = get_manual_order_stats(week_start, week_end) + month_manual_stats = get_manual_order_stats(month_start, month_end) + + week_rewards = calculate_reward(week_start, week_end) + month_rewards = calculate_reward(month_start, month_end) + + week_handler_stats = get_handler_stats(week_start, week_end) + month_handler_stats = get_handler_stats(month_start, month_end) + + # 如果是装维员,只显示自己的数据 + if current_user.role == '装维员': + week_work_stats = { + 'completed': WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= week_start, + WorkOrderHandling.restore_time < week_end, + WorkOrderHandling.handler == current_user.name + ).count() + } + month_work_stats = { + 'completed': WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= month_start, + WorkOrderHandling.restore_time < month_end, + WorkOrderHandling.handler == current_user.name + ).count() + } + week_manual_stats = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= week_start, + ManualWorkOrder.create_time < week_end, + ManualWorkOrder.handler == current_user.name + ).count() + month_manual_stats = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= month_start, + ManualWorkOrder.create_time < month_end, + ManualWorkOrder.handler == current_user.name + ).count() + # 修改这里:只获取当前装维员的奖励统计 + week_rewards = calculate_reward(week_start, week_end) + month_rewards = calculate_reward(month_start, month_end) + week_handler_stats = {current_user.name: week_handler_stats.get(current_user.name, defaultdict(int))} + month_handler_stats = {current_user.name: month_handler_stats.get(current_user.name, defaultdict(int))} + + # 从 handler_stats 中提取当前用户的奖励数据 + week_rewards = week_handler_stats[current_user.name]['rewards'] + month_rewards = month_handler_stats[current_user.name]['rewards'] + + return render_template('statistics.html', + week_work_stats=week_work_stats, + month_work_stats=month_work_stats, + week_manual_stats=week_manual_stats, + month_manual_stats=month_manual_stats, + week_rewards=week_rewards, + month_rewards=month_rewards, + week_handler_stats=week_handler_stats, + month_handler_stats=month_handler_stats) + @app.route('/export_monthly_stats//') + @login_required + def export_monthly_stats(year, month): + if current_user.role != '统计员': + app.logger.warning(f'未授权访问:用户 {current_user.name} 尝试导出月度统计') + flash('您没有权限执行此操作') + return redirect(url_for('statistics')) + + app.logger.info(f'用户 {current_user.name} 导出 {year}年{month}月 统计报表') + + # 计算指定月份的开始和结束时间 + month_start = datetime(year, month, 1, 0, 0, 0) + if month == 12: + month_end = datetime(year + 1, 1, 1, 0, 0, 0) + else: + month_end = datetime(year, month + 1, 1, 0, 0, 0) + + # 检查是否有数据 + handlings = WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= month_start, + WorkOrderHandling.restore_time < month_end + ).first() + + if not handlings: + flash(f'{year}年{month}月系统无检修工单') + return redirect(url_for('statistics')) + + # 创建工作簿 + wb = Workbook() + ws = wb.active + ws.title = "运维计件奖励" + + # 设置标题 + ws.merge_cells('A1:J1') + title = ws.cell(1, 1, f"{year}年{month}月政企重点项目运维各支局检修激励汇总") + title.font = Font(size=14, bold=True) + title.alignment = Alignment(horizontal='center') + + # 设置表头 + headers = ['序号', '检修单位名称', '检修项目', '检修设备总数(台)', + '单位距支局维护人员所在地址(公里)', '检修设备每个单价(元)', + '检修项目合计数(路)', '本月检修金额(元)', '支局', '检修人员','本月个人检修合计','检修时间'] + for col, header in enumerate(headers, 1): + cell = ws.cell(2, col, header) + cell.font = Font(bold=True) + cell.alignment = Alignment(horizontal='center', wrap_text=True) + + # 获取指定月份数据 + handlings = WorkOrderHandling.query.filter( + WorkOrderHandling.restore_time >= month_start, + WorkOrderHandling.restore_time < month_end + ).all() + + # 查询本月所有手工故障单 + manual_orders = ManualWorkOrder.query.filter( + ManualWorkOrder.create_time >= month_start, + ManualWorkOrder.create_time < month_end + ).order_by(ManualWorkOrder.create_time).all() + + # 填充数据 + row = 3 + idx = 0 # 初始化序号计数器 + + # 处理普通工单 + for handling in handlings: + device = db.session.get(Device, handling.device_id) + if not device: + continue + + idx += 1 # 增加序号 + ws.cell(row, 1, idx) # 序号 + ws.cell(row, 2, device.device_id) # 检修单位名称 + ws.cell(row, 3, device.project_type) # 检修项目 + ws.cell(row, 4, 1) # 检修设备总数 + ws.cell(row, 5, handling.distance_from_branch) # 距离 + + # 计算单价 + distance = float(handling.distance_from_branch) + reward = get_reward_by_distance(device.project_type, distance) + ws.cell(row, 6, reward) # 单价 + + ws.cell(row, 7, 1) # 检修项目合计数 + ws.cell(row, 8, reward) # 本月检修金额 + ws.cell(row, 9, handling.branch) # 支局 + ws.cell(row, 10, handling.handler) # 检修人员 + ws.cell(row, 12, handling.restore_time.strftime('%Y-%m-%d %H:%M:%S')) # 检修时间 + + row += 1 + + # 处理手工故障单 + for manual_order in manual_orders: + idx += 1 # 增加序号 + ws.cell(row, 1, idx) # 序号 + ws.cell(row, 2, manual_order.device_location) # 检修单位名称 + ws.cell(row, 3, manual_order.project_type) # 检修项目 + ws.cell(row, 4, 1) # 检修设备总数 + ws.cell(row, 5, manual_order.distance_from_branch) # 距离 + + # 计算单价 + distance = float(manual_order.distance_from_branch) + reward = get_reward_by_distance(manual_order.project_type, distance) + ws.cell(row, 6, reward) # 单价 + + ws.cell(row, 7, 1) # 检修项目合计数 + ws.cell(row, 8, reward) # 本月检修金额 + ws.cell(row, 9, manual_order.branch) # 支局 + ws.cell(row, 10, manual_order.handler) # 检修人员 + ws.cell(row, 12, manual_order.create_time.strftime('%Y-%m-%d %H:%M:%S')) # 检修时间 + + row += 1 + idx += 1 + + # 添加合计行 + ws.cell(row, 7, f"合计") + ws.cell(row, 8, f"=SUM(H3:H{row-1})") + ws.cell(row, 8).font = Font(bold=True) + + # 设置列宽 + ws.column_dimensions['B'].width = 20 + ws.column_dimensions['C'].width = 15 + ws.column_dimensions['E'].width = 15 + ws.column_dimensions['F'].width = 15 + ws.column_dimensions['H'].width = 15 + + # 保存到内存中 + excel_file = io.BytesIO() + wb.save(excel_file) + excel_file.seek(0) + + # 设置文件名 + filename = f'{year}年{month}月政企重点项目运维各支局检修激励汇总.xlsx' + + return send_file( + excel_file, + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + as_attachment=True, + download_name=filename + ) diff --git a/app/views/work_order.py b/app/views/work_order.py new file mode 100755 index 0000000..4956809 --- /dev/null +++ b/app/views/work_order.py @@ -0,0 +1,331 @@ +from flask import render_template, request, redirect, url_for, flash, jsonify +from flask_login import login_required, current_user +from scripts.models import db, Device, WorkOrder, WorkOrderHandling +from datetime import datetime +import os +from utils.file_utils import allowed_file, compress_image +from utils.lzmx import send_lzmx_message +from config import Config + +def init_work_order_routes(app): + # 添加获取支局列表的辅助函数 + def get_branches(): + branches = db.session.query(Device.branch).distinct().all() + return [branch[0] for branch in branches if branch[0]] + + # 派单平台路由 + @app.route('/dispatch', methods=['GET', 'POST']) + @login_required + def dispatch(): + if current_user.role not in ['装维员', '统计员']: + flash('您没有权限访问此页面') + return redirect(url_for('statistics')) + + # 处理 POST 请求 + if request.method == 'POST': + device_ids = request.form.getlist('device_ids') + + if not device_ids: + flash('请选择至少一个设备') + return redirect(url_for('dispatch')) + + today = datetime.now().strftime('%Y%m%d') + last_work_order = WorkOrder.query.filter(WorkOrder.order_id.like(f'{today}%')).order_by(WorkOrder.order_id.desc()).first() + + # 获取起始编号 + if last_work_order: + last_number = int(last_work_order.order_id[-4:]) + start_number = last_number + 1 + else: + start_number = 1 + + # 为每个设备生成唯一的工单号 + for index, device_id in enumerate(device_ids): + device = db.session.get(Device, device_id) + if not device: + continue + + current_number = start_number + index + new_number = f"{today}{str(current_number).zfill(4)}" + + work_order = WorkOrder( + order_id=new_number, + device_id=device_id, + dispatch_time=datetime.now(), + status='待处理', + branch=device.branch + ) + + db.session.add(work_order) + device.is_normal = False + db.session.add(device) + + db.session.commit() + flash('派单成功') + return redirect(url_for('dispatch')) + + # 处理 GET 请求 + # 获取分页参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 50, type=int) # 从请求参数中获取每页显示数量 + + # 获取搜索词和筛选条件 + search = request.args.get('search', '') + selected_town = request.args.get('town', '') + selected_branch = request.args.get('branch', '') + + towns = db.session.query(Device.town).distinct().all() + towns = [town[0] for town in towns if town[0]] + + branches = db.session.query(Device.branch).distinct().all() + branches = [branch[0] for branch in branches if branch[0]] + + query = Device.query + if search: + query = query.filter( + (Device.device_id.contains(search)) | + (Device.town.contains(search)) | + (Device.branch.contains(search)) | + (Device.unit_name.contains(search)) | + (Device.project_type.contains(search)) | + (Device.device_type.contains(search)) | + (Device.remark.contains(search)) + ) + + if selected_town: + query = query.filter(Device.town == selected_town) + + if selected_branch: + query = query.filter(Device.branch == selected_branch) + + # 分页时使用从请求获取的 per_page 参数 + devices = query.filter_by(in_use=True).paginate(page=page, per_page=per_page) + + return render_template('dispatch.html', devices=devices, towns=towns, branches=branches) + + @app.route('/update_device_remark', methods=['POST']) + @login_required + def update_device_remark(): + try: + data = request.get_json() + device_id = data.get('device_id') + remark = data.get('remark', '') + + device = Device.query.get(device_id) + if not device: + return jsonify({'success': False, 'message': '设备不存在'}) + + device.remark = remark + db.session.commit() + + return jsonify({'success': True}) + except Exception as e: + return jsonify({'success': False, 'message': str(e)}) + + # 接单平台路由 + @app.route('/receive') + @login_required + def receive(): + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 50, type=int) + branch = request.args.get('branch', '') + device_type = request.args.get('device_type', '') + project_type = request.args.get('project_type', '') + + # 基础查询 + query = WorkOrder.query.filter_by(status='待处理') + + # 根据用户权限筛选工单 + if current_user.branch != '交付中心': + query = query.join(Device).filter(Device.branch == current_user.branch) + + # 获取总数统计 + total_orders = query.count() + + # 获取各支局统计 + branch_stats = {} + if current_user.branch == '交付中心': + branch_counts = db.session.query( + Device.branch, + db.func.count(WorkOrder.order_id) + ).join(WorkOrder, WorkOrder.device_id == Device.device_id + ).filter(WorkOrder.status == '待处理' + ).group_by(Device.branch).all() + + branch_stats = dict(branch_counts) + + # 应用分支筛选(仅交付中心可用) + if branch and current_user.branch == '交付中心': + query = query.join(Device).filter(Device.branch == branch) + + # 应用设备类型筛选 + if device_type: + query = query.join(Device).filter(Device.device_type == device_type) + + # 应用项目类型筛选 + if project_type: + query = query.join(Device).filter(Device.project_type == project_type) + + # 获取筛选选项 + device_types_query = db.session.query(Device.device_type).distinct().join(WorkOrder).filter(WorkOrder.status == '待处理') + project_types_query = db.session.query(Device.project_type).distinct().join(WorkOrder).filter(WorkOrder.status == '待处理') + + # 根据用户权限进一步筛选选项 + if current_user.branch != '交付中心': + device_types_query = device_types_query.filter(Device.branch == current_user.branch) + project_types_query = project_types_query.filter(Device.branch == current_user.branch) + elif branch: + device_types_query = device_types_query.filter(Device.branch == branch) + project_types_query = project_types_query.filter(Device.branch == branch) + + device_types = [dt[0] for dt in device_types_query.all() if dt[0]] + project_types = [pt[0] for pt in project_types_query.all() if pt[0]] + + # 获取分页数据 + work_orders = query.paginate(page=page, per_page=per_page) + + # 计算派单时长 + for work_order in work_orders.items: + dispatch_time = work_order.dispatch_time + now = datetime.now() + delta = now - dispatch_time + work_order.duration = f"{delta.days}天{delta.seconds // 3600}小时{(delta.seconds % 3600) // 60}分钟" + + return render_template('receive.html', + work_orders=work_orders, + total_orders=total_orders, + branch_stats=branch_stats, + show_filter=current_user.branch == '交付中心', # 只有交付中心可以看到筛选 + branches=get_branches() if current_user.branch == '交付中心' else [], # 只有交付中心可以看到支局列表 + selected_branch=branch, + device_types=device_types, + project_types=project_types + ) + + @app.route('/recall_all_orders', methods=['POST']) + @login_required + def recall_all_orders(): + try: + # 获取所有在途工单 + orders = WorkOrder.query.filter_by(status='待处理').all() + count = len(orders) + + # 恢复设备状态并删除工单 + for order in orders: + # 恢复设备状态 + device = Device.query.get(order.device_id) + if device: + device.is_normal = True + db.session.add(device) + + # 直接删除工单 + db.session.delete(order) + + db.session.commit() + + return jsonify({ + 'success': True, + 'count': count + }) + except Exception as e: + return jsonify({ + 'success': False, + 'message': str(e) + }) + + # 处理工单 + @app.route('/receive/', methods=['GET', 'POST']) + @login_required + def receive_detail(work_order_id): + if current_user.role not in ['装维员', '统计员']: + flash('您没有权限访问此页面') + return redirect(url_for('statistics')) + + work_order = WorkOrder.query.get_or_404(work_order_id) + device = Device.query.filter_by(device_id=work_order.device_id).first() + + if request.method == 'POST': + photo_paths = [] + if 'photo' in request.files: + files = request.files.getlist('photo') + if len(files) > 5: + flash('最多只能上传5张图片') + return redirect(url_for('receive_detail', work_order_id=work_order_id)) + for file in files: + if file and allowed_file(file.filename, Config.ALLOWED_EXTENSIONS): # 修改这里 + filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}" + file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + file.save(file_path) + compress_image(file_path) + photo_paths.append(filename) + + # 创建处理工单信息 + handling_info = WorkOrderHandling( + order_id=work_order.order_id, + device_id=work_order.device_id, + branch=device.branch if device else None, + restore_time=datetime.now(), + fault_type=request.form['fault_type'], + contact_person=request.form['contact_person'], + contact_phone=request.form['contact_phone'], + handler=request.form['handler'], + handle_description=request.form['handle_description'], + photo_paths=','.join(photo_paths) if photo_paths else None, + distance_from_branch=request.form['distance_from_branch'], + remark=request.form.get('remark', '') + ) + + # 更新工单状态为已处理 + work_order.status = '已处理' + + # 更新设备状态为正常 + if device: + device.is_normal = True + + # 将所有更改添加到会话 + db.session.add(handling_info) + db.session.add(work_order) + if device: + db.session.add(device) + + # 一次性提交所有更改 + db.session.commit() + + # 发送量子密信通知 + send_lzmx_message( + Config.LZMX_WEBHOOK_URL, # 修改这里 + request.form['handler'], + work_order.device_id, + request.form['fault_type'], + request.form['distance_from_branch'], + datetime.now() + ) + + flash('工单处理成功') + return redirect(url_for('receive')) + + return render_template('receive_detail.html', work_order=work_order, device=device) + + @app.route('/recall_order/') + @login_required + def recall_order(order_id): + # 检查用户权限 + if current_user.role != '统计员': + flash('没有权限执行此操作') + return redirect(url_for('receive')) + + # 获取工单 + work_order = WorkOrder.query.get_or_404(order_id) + + # 更新设备状态 + device = Device.query.get(work_order.device_id) + if device: + device.is_normal = True + db.session.add(device) + + # 删除工单 + db.session.delete(work_order) + db.session.commit() + + flash('工单已成功追回') + return redirect(url_for('receive')) \ No newline at end of file diff --git a/start_with_env.sh b/start_with_env.sh new file mode 100755 index 0000000..b5d7d6f --- /dev/null +++ b/start_with_env.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# 加载环境变量 +export $(cat /opt/1panel/docker/compose/4an/.env | grep -v '^#' | xargs) + +# 启动uwsgi +exec /mnt/nfs/.venv/bin/uwsgi --ini /opt/1panel/docker/compose/4an/app/uwsgi.ini \ No newline at end of file diff --git a/supervisor.log b/supervisor.log new file mode 100644 index 0000000..ced2a2e --- /dev/null +++ b/supervisor.log @@ -0,0 +1,10 @@ +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini diff --git a/uwsgi.log b/uwsgi.log new file mode 100644 index 0000000..a82ef77 --- /dev/null +++ b/uwsgi.log @@ -0,0 +1,2879 @@ +*** Starting uWSGI 2.0.28 (64bit) on [Fri Jul 25 18:10:12 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x7bb2910a39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +Traceback (most recent call last): + File "/opt/1panel/docker/compose/4an/app/app.py", line 13, in + from views.dashboard import init_dashboard_routes + File "/opt/1panel/docker/compose/4an/app/views/dashboard.py", line 6, in + from utils.db_utils import cache_query, db_operation +ModuleNotFoundError: No module named 'utils.db_utils' +unable to load app 0 (mountpoint='') (callable not found or import error) +*** no app loaded. going in full dynamic mode *** +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 3945870) +spawned uWSGI worker 1 (pid: 3945897, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 3945900, cores: 2) +spawned uWSGI worker 3 (pid: 3945903, cores: 2) +spawned uWSGI worker 4 (pid: 3945906, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 21 *** +spawned uWSGI http 1 (pid: 3945909) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +--- no python application found, check your startup logs for errors --- +--- no python application found, check your startup logs for errors --- +--- no python application found, check your startup logs for errors --- +--- no python application found, check your startup logs for errors --- +gateway "uWSGI http 1" has been buried (pid: 3945909) +...brutally killing workers... +worker 1 buried after 1 seconds +worker 2 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Fri Jul 25 18:12:19 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Fri Jul 25 18:12:20 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x76ead8aa39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +Traceback (most recent call last): + File "/opt/1panel/docker/compose/4an/app/app.py", line 17, in + from utils.error_handlers import ErrorHandler +ModuleNotFoundError: No module named 'utils.error_handlers' +unable to load app 0 (mountpoint='') (callable not found or import error) +*** no app loaded. going in full dynamic mode *** +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 3947431) +spawned uWSGI worker 1 (pid: 3947444, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 3947447, cores: 2) +spawned uWSGI worker 3 (pid: 3947450, cores: 2) +spawned uWSGI worker 4 (pid: 3947453, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 21 *** +spawned uWSGI http 1 (pid: 3947456) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +--- no python application found, check your startup logs for errors --- +--- no python application found, check your startup logs for errors --- +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/utils/file_utils.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/utils/file_utils.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/utils/file_utils.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/utils/file_utils.py has been modified +gateway "uWSGI http 1" has been buried (pid: 3947456) +...gracefully killing workers... +Gracefully killing worker 4 (pid: 3947453)... +Gracefully killing worker 2 (pid: 3947447)... +Gracefully killing worker 3 (pid: 3947450)... +Gracefully killing worker 1 (pid: 3947444)... +worker 1 buried after 1 seconds +worker 2 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Fri Jul 25 18:13:00 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Fri Jul 25 18:13:01 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x7190a26a39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-07-25 18:13:01,854] INFO in app: 应用启动 +[2025-07-25 18:13:01,858] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x7190a26a39e8 pid: 3947912 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 3947912) +spawned uWSGI worker 1 (pid: 3947924, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 3947927, cores: 2) +spawned uWSGI worker 3 (pid: 3947930, cores: 2) +spawned uWSGI worker 4 (pid: 3947933, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 3947936) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +[2025-07-25 18:13:03,701] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-07-25 18:13:12,301] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-07-25 18:13:13,020] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-07-25 18:13:14,467] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3991567) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 3991570) +Respawned uWSGI worker 4 (new pid: 3991573) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 4035218) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 4035221) +Respawned uWSGI worker 4 (new pid: 4035224) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 4078778) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 4078781) +Respawned uWSGI worker 4 (new pid: 4078784) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 4122363) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 4122366) +Respawned uWSGI worker 4 (new pid: 4122369) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 4166000) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 4166003) +Respawned uWSGI worker 4 (new pid: 4166006) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 15757) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 15760) +Respawned uWSGI worker 4 (new pid: 15763) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 59297) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 59300) +Respawned uWSGI worker 4 (new pid: 59303) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 102829) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 102832) +Respawned uWSGI worker 4 (new pid: 102835) +Respawned uWSGI worker 3 (new pid: 121985) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 147009) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 147012) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 165602) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 190643) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 190646) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 209199) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 234144) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 234147) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 252717) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 277758) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 277761) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 296697) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 321719) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 321722) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 340254) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 365305) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 365308) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 383831) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 408848) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 408851) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 427356) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 452351) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 452354) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 470968) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 495969) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 495972) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 514561) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 539612) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 539615) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 558206) +Respawned uWSGI worker 2 (new pid: 565441) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 583238) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 583241) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 626745) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 626748) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 670225) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 670228) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 713791) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 713794) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 757440) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 757443) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 800938) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 800941) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 844520) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 844523) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 888266) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 888269) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 931817) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 931820) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 975343) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 975346) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1019020) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1019023) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1062533) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1062536) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1106094) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1106097) +Respawned uWSGI worker 3 (new pid: 1118899) +Respawned uWSGI worker 2 (new pid: 1118902) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1149713) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1149716) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1162546) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1193379) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1193382) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1206197) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1236973) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1236976) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1249812) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1280581) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1280584) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1293334) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1324135) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1324138) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1336962) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1367817) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1367820) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1380626) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1411439) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1411442) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1424281) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1455028) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1455031) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1467812) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1498524) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1498527) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1511362) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1542127) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1542130) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1554914) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1585698) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1585701) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1598509) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1629165) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1629168) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1641987) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1673624) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1673627) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1686451) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1717245) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1717248) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1730086) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1760846) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1760849) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1773623) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1804558) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1804561) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1817383) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1848422) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1848425) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1861402) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1892186) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1892189) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1905084) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1935814) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1935817) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1948670) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1979491) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1979494) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1992295) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2022970) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2022973) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2035810) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2066605) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2066608) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2079375) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2110544) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2110547) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2123410) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2154198) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2154201) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2167025) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2197805) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2197808) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2210642) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2241413) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2241416) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2254235) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2285028) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2285031) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2297860) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2328619) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2328622) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2341386) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2372129) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2372132) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2385166) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2416051) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2416054) +Respawned uWSGI worker 2 (new pid: 2420934) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2428922) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +[2025-07-28 08:27:08,635] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +Respawned uWSGI worker 1 (new pid: 2469132) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2469135) +Respawned uWSGI worker 2 (new pid: 2469138) +[2025-07-28 08:29:41,842] INFO in device: 用户 韦矍森 开始同步离线设备 +[2025-07-28 08:29:41,867] INFO in device: 同步完成:创建了 3 个工单,恢复了 0 个设备 +[2025-07-28 08:29:43,879] INFO in device: 用户请求验证码 +[2025-07-28 08:29:43,967] INFO in device: 验证码获取成功 +[2025-07-28 08:29:52,565] INFO in device: 用户 韦矍森 开始同步摄像头第一步 +[2025-07-28 08:29:52,565] INFO in device: 尝试登录中兴平台 +[2025-07-28 08:29:52,625] WARNING in device: 登录中兴平台失败:验证码错误 +[2025-07-28 08:29:53,856] INFO in device: 用户请求验证码 +[2025-07-28 08:29:53,915] INFO in device: 验证码获取成功 +[2025-07-28 08:29:58,957] INFO in device: 用户 韦矍森 开始同步摄像头第一步 +[2025-07-28 08:29:58,958] INFO in device: 尝试登录中兴平台 +[2025-07-28 08:29:59,333] INFO in device: 登录成功,开始获取在线信息 +[2025-07-28 08:30:00,109] INFO in device: 获取到 1436 个设备信息,开始更新数据库 +[2025-07-28 08:30:00,378] INFO in device: 数据库更新完成,更新了 1436 条记录,其中离线设备 678 个 +{address space usage: 698486784 bytes/666MB} {rss usage: 90689536 bytes/86MB} [pid: 2469138|app: 0|req: 22/98] 175.178.19.237 () {64 vars in 1381 bytes} [Mon Jul 28 08:29:58 2025] POST /sync_camera_step1 => generated 58 bytes in 1425 msecs (HTTP/1.1 200) 3 headers in 85 bytes (1 switches on core 0) +[2025-07-28 08:30:01,742] INFO in device: 用户 韦矍森 开始同步摄像头第二步 +[2025-07-28 08:30:01,796] INFO in device: 查询到 1107 个需要同步的设备 +[2025-07-28 08:30:02,597] INFO in device: 发现 9 个离线设备需要创建工单 +[2025-07-28 08:30:02,614] INFO in device: 成功创建 9 个工单 +[2025-07-28 08:30:04,962] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-07-28 08:30:38,900] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2472373) +[2025-07-28 08:51:47,535] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-07-28 08:51:49,394] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-07-28 08:52:02,272] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-07-28 08:52:54,070] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-07-28 08:52:55,575] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-07-28 08:52:58,617] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2513033) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2556663) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2600238) +Respawned uWSGI worker 1 (new pid: 2626750) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2626753) +Respawned uWSGI worker 3 (new pid: 2626756) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2670263) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2670266) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2713839) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2713842) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2757368) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2757371) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2800893) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2800896) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2843913) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2843916) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2887510) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2887513) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2931110) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 2931113) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3022100) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 3022103) +Respawned uWSGI worker 2 (new pid: 3055470) +Respawned uWSGI worker 3 (new pid: 3055473) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3067353) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 3067356) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3109851) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3129130) +Respawned uWSGI worker 4 (new pid: 3129133) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3196293) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3206794) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 3206797) +Respawned uWSGI worker 3 (new pid: 3216636) +gateway "uWSGI http 1" has been buried (pid: 3947936) +...brutally killing workers... +worker 2 buried after 1 seconds +worker 1 buried after 1 seconds +worker 4 buried after 1 seconds +worker 3 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Mon Jul 28 23:34:39 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Mon Jul 28 23:35:20 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x70ac482a39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-07-28 23:35:22,509] INFO in app: 应用启动 +[2025-07-28 23:35:22,514] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 2 seconds on interpreter 0x70ac482a39e8 pid: 1256 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 1256) +spawned uWSGI worker 1 (pid: 3381, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 3389, cores: 2) +spawned uWSGI worker 3 (pid: 3395, cores: 2) +spawned uWSGI worker 4 (pid: 3401, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 3412) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +gateway "uWSGI http 1" has been buried (pid: 3412) +...brutally killing workers... +worker 1 buried after 1 seconds +worker 2 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Mon Jul 28 23:49:04 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Mon Jul 28 23:49:53 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x7f123caa39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-07-28 23:49:56,387] INFO in app: 应用启动 +[2025-07-28 23:49:56,392] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 3 seconds on interpreter 0x7f123caa39e8 pid: 2894 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 2894) +spawned uWSGI worker 1 (pid: 3993, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 3996, cores: 2) +spawned uWSGI worker 3 (pid: 3999, cores: 2) +spawned uWSGI worker 4 (pid: 4002, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 4005) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 52897) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 52900) +Respawned uWSGI worker 3 (new pid: 52903) +Respawned uWSGI worker 4 (new pid: 52906) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 95531) +Respawned uWSGI worker 2 (new pid: 95534) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 95537) +Respawned uWSGI worker 4 (new pid: 95540) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 138079) +Respawned uWSGI worker 2 (new pid: 138082) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 138085) +Respawned uWSGI worker 4 (new pid: 138088) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 180611) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 180614) +Respawned uWSGI worker 3 (new pid: 180617) +Respawned uWSGI worker 4 (new pid: 180620) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 223043) +Respawned uWSGI worker 2 (new pid: 223046) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 223049) +Respawned uWSGI worker 4 (new pid: 223052) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 265677) +Respawned uWSGI worker 2 (new pid: 265680) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 265683) +Respawned uWSGI worker 4 (new pid: 265686) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 308587) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 308590) +Respawned uWSGI worker 3 (new pid: 308593) +Respawned uWSGI worker 4 (new pid: 308596) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 351251) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 351254) +Respawned uWSGI worker 3 (new pid: 351257) +Respawned uWSGI worker 4 (new pid: 351260) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 394105) +Respawned uWSGI worker 2 (new pid: 431725) +Respawned uWSGI worker 3 (new pid: 431728) +Respawned uWSGI worker 1 (new pid: 434816) +Python auto-reloader enabled +worker 4 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 474285) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 477398) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 495834) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 519346) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 540206) +Respawned uWSGI worker 1 (new pid: 554173) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 554176) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 850968) +Respawned uWSGI worker 3 (new pid: 850971) +Respawned uWSGI worker 1 (new pid: 850974) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 850977) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 914989) +Respawned uWSGI worker 3 (new pid: 914992) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 958260) +Respawned uWSGI worker 3 (new pid: 958263) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1000717) +Respawned uWSGI worker 3 (new pid: 1000720) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1043112) +Respawned uWSGI worker 3 (new pid: 1043115) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1085471) +Respawned uWSGI worker 3 (new pid: 1085474) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1128146) +Respawned uWSGI worker 3 (new pid: 1128149) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1170458) +Respawned uWSGI worker 3 (new pid: 1170461) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1213129) +Respawned uWSGI worker 3 (new pid: 1213132) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1255423) +Respawned uWSGI worker 3 (new pid: 1255426) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1297857) +Respawned uWSGI worker 3 (new pid: 1297860) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1340174) +Respawned uWSGI worker 3 (new pid: 1340177) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1382744) +Respawned uWSGI worker 3 (new pid: 1382747) +Respawned uWSGI worker 4 (new pid: 1397291) +[2025-07-30 07:37:35,743] ERROR in error_handlers: 未处理异常: 405 Method Not Allowed: The method is not allowed for the requested URL. +Traceback (most recent call last): + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request + rv = self.dispatch_request() + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 891, in dispatch_request + self.raise_routing_exception(req) + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/app.py", line 500, in raise_routing_exception + raise request.routing_exception # type: ignore[misc] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/flask/ctx.py", line 362, in match_request + result = self.url_adapter.match(return_rule=True) # type: ignore + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/nfs/.venv/lib/python3.12/site-packages/werkzeug/routing/map.py", line 624, in match + raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None +werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL. + +Respawned uWSGI worker 1 (new pid: 1397376) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1486690) +Respawned uWSGI worker 3 (new pid: 1486693) +Respawned uWSGI worker 4 (new pid: 1486696) +Respawned uWSGI worker 1 (new pid: 1486699) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1528973) +Respawned uWSGI worker 3 (new pid: 1528976) +Respawned uWSGI worker 4 (new pid: 1528979) +Respawned uWSGI worker 1 (new pid: 1528982) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1571270) +Respawned uWSGI worker 3 (new pid: 1571273) +Respawned uWSGI worker 4 (new pid: 1571276) +Respawned uWSGI worker 1 (new pid: 1571279) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1613645) +Respawned uWSGI worker 3 (new pid: 1613648) +Respawned uWSGI worker 4 (new pid: 1613651) +Respawned uWSGI worker 1 (new pid: 1613654) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1655923) +Respawned uWSGI worker 3 (new pid: 1655926) +Respawned uWSGI worker 4 (new pid: 1655929) +Respawned uWSGI worker 1 (new pid: 1655932) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1698242) +Respawned uWSGI worker 3 (new pid: 1698245) +Respawned uWSGI worker 4 (new pid: 1698248) +Respawned uWSGI worker 1 (new pid: 1698251) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1741969) +Respawned uWSGI worker 3 (new pid: 1741972) +Respawned uWSGI worker 4 (new pid: 1741975) +Respawned uWSGI worker 1 (new pid: 1741978) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1779809) +Respawned uWSGI worker 3 (new pid: 1779812) +Respawned uWSGI worker 4 (new pid: 1779815) +Respawned uWSGI worker 1 (new pid: 1779818) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1815916) +Respawned uWSGI worker 3 (new pid: 1815919) +Respawned uWSGI worker 4 (new pid: 1815922) +Respawned uWSGI worker 1 (new pid: 1815925) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1851948) +Respawned uWSGI worker 3 (new pid: 1851951) +Respawned uWSGI worker 4 (new pid: 1851954) +Respawned uWSGI worker 1 (new pid: 1851957) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1888046) +Respawned uWSGI worker 3 (new pid: 1888049) +Respawned uWSGI worker 4 (new pid: 1888052) +Respawned uWSGI worker 1 (new pid: 1888055) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1924108) +Respawned uWSGI worker 3 (new pid: 1924111) +Respawned uWSGI worker 4 (new pid: 1924114) +Respawned uWSGI worker 1 (new pid: 1924117) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1960344) +Respawned uWSGI worker 3 (new pid: 1960347) +Respawned uWSGI worker 4 (new pid: 1960350) +Respawned uWSGI worker 1 (new pid: 1960353) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1996452) +Respawned uWSGI worker 3 (new pid: 1996455) +Respawned uWSGI worker 4 (new pid: 1996458) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2032601) +Respawned uWSGI worker 3 (new pid: 2032604) +Respawned uWSGI worker 4 (new pid: 2032607) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2068887) +Respawned uWSGI worker 3 (new pid: 2068890) +Respawned uWSGI worker 4 (new pid: 2068893) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2104827) +Respawned uWSGI worker 3 (new pid: 2104830) +Respawned uWSGI worker 4 (new pid: 2104833) +Respawned uWSGI worker 1 (new pid: 2135939) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2140910) +Respawned uWSGI worker 3 (new pid: 2140913) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2171911) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2176896) +Respawned uWSGI worker 3 (new pid: 2176899) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2207981) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2212976) +Respawned uWSGI worker 3 (new pid: 2212979) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2244126) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2249170) +Respawned uWSGI worker 3 (new pid: 2249173) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2280368) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2285462) +Respawned uWSGI worker 3 (new pid: 2285465) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2316565) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2321561) +Respawned uWSGI worker 3 (new pid: 2321564) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2352552) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2357560) +Respawned uWSGI worker 3 (new pid: 2357563) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2388604) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2393601) +Respawned uWSGI worker 3 (new pid: 2393604) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2424601) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2429648) +Respawned uWSGI worker 3 (new pid: 2429651) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2460683) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2465688) +Respawned uWSGI worker 3 (new pid: 2465691) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2496732) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2501764) +Respawned uWSGI worker 3 (new pid: 2501767) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2532780) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2537813) +Respawned uWSGI worker 3 (new pid: 2537816) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2568827) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2573902) +Respawned uWSGI worker 3 (new pid: 2573905) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2604984) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2610009) +Respawned uWSGI worker 3 (new pid: 2610012) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2641067) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2646127) +Respawned uWSGI worker 3 (new pid: 2646130) +Respawned uWSGI worker 4 (new pid: 2657733) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2677161) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2682165) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2693871) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2713188) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2718253) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2729887) +Respawned uWSGI worker 2 (new pid: 2745131) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2749275) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2754265) +worker 4 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2785364) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2790390) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2821479) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2826507) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2857572) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2862668) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2893973) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2898963) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2930125) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2935135) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2966215) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 2971233) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3002350) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3007351) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3038397) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3043401) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3074427) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3079485) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3110555) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3115558) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3146664) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3151790) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3182878) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3187945) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3218911) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3223943) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3255004) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3259993) +Respawned uWSGI worker 2 (new pid: 3274592) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3296065) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3332255) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3368361) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3404494) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3440565) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3476669) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3512736) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3548991) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3585017) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3621049) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3657336) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3693350) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3729386) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3765505) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3801603) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3837726) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3873810) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3909875) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3945971) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3982033) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 4018186) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 4054283) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 4090541) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 4126559) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 4162616) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 4826) +Respawned uWSGI worker 4 (new pid: 35704) +Respawned uWSGI worker 1 (new pid: 35707) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 35710) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 71902) +Respawned uWSGI worker 1 (new pid: 71905) +Respawned uWSGI worker 2 (new pid: 71908) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 107892) +Respawned uWSGI worker 1 (new pid: 107895) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 107898) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 143953) +Respawned uWSGI worker 1 (new pid: 143956) +Respawned uWSGI worker 2 (new pid: 143959) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 180043) +Respawned uWSGI worker 1 (new pid: 180046) +Respawned uWSGI worker 2 (new pid: 180049) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 216036) +Respawned uWSGI worker 1 (new pid: 216039) +Respawned uWSGI worker 2 (new pid: 216042) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 252007) +Respawned uWSGI worker 1 (new pid: 252010) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 252013) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 288093) +Respawned uWSGI worker 1 (new pid: 288096) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 288099) +Respawned uWSGI worker 3 (new pid: 322586) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 324184) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 358862) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 360449) +Respawned uWSGI worker 1 (new pid: 368637) +Respawned uWSGI worker 2 (new pid: 368640) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 394817) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 396467) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 404664) +Respawned uWSGI worker 2 (new pid: 404667) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 430864) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 432493) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 440662) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 440665) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 466943) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 468576) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 476797) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 476800) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 503086) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 504755) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 512968) +Respawned uWSGI worker 2 (new pid: 512971) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 539217) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 540858) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 549089) +Respawned uWSGI worker 2 (new pid: 549092) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 575330) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 576930) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 585211) +Respawned uWSGI worker 2 (new pid: 585214) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 611406) +worker 4 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 621296) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 647537) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 657382) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 683639) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 693635) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 719919) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 729760) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 756013) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 765859) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 792079) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 801987) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 828211) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 838013) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 864274) +Respawned uWSGI worker 4 (new pid: 870710) +Respawned uWSGI worker 2 (new pid: 870713) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 874163) +Python auto-reloader enabled +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 900322) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 906763) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 910191) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 917884) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 942757) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 946163) +Python auto-reloader enabled +worker 4 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 978866) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 982433) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1014965) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1018467) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1051282) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1054848) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1087489) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1091036) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1123541) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1127126) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1159597) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1163139) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1195586) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1199126) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1231671) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1235273) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1267681) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1271282) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1303802) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1307362) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1339870) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1343377) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1376122) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1379652) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1412172) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1415712) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1448183) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1451703) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1484245) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1487818) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1520364) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1523885) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1556463) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1559984) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1592651) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1596192) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1628635) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1632173) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1664662) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1668200) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1700727) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1704295) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1736892) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1740399) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1773103) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1776663) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1809207) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1812747) +Python auto-reloader enabled +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1845213) +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1848753) +Python auto-reloader enabled +Respawned uWSGI worker 4 (new pid: 1850815) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1881425) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 1887258) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1918912) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 1924711) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1956558) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 1962415) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 1994018) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 1999883) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2031463) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2037297) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2068981) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2074813) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2106242) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2112097) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2143768) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2149616) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2181713) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2187611) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2219245) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2225067) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2256626) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2262461) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2294022) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2299879) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2331512) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2337401) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2368936) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2374779) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2406359) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2412230) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2443852) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 4 (new pid: 2449702) +gateway "uWSGI http 1" has been buried (pid: 4005) +...brutally killing workers... +worker 3 buried after 1 seconds +worker 1 buried after 1 seconds +worker 2 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Tue Aug 5 06:55:35 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Tue Aug 5 06:56:04 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x7cb725aa39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-08-05 06:56:05,936] INFO in app: 应用启动 +[2025-08-05 06:56:05,939] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x7cb725aa39e8 pid: 2457037 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 2457037) +spawned uWSGI worker 1 (pid: 2457187, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 2457190, cores: 2) +spawned uWSGI worker 3 (pid: 2457193, cores: 2) +spawned uWSGI worker 4 (pid: 2457196, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 2457199) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2495606) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 2495609) +Respawned uWSGI worker 3 (new pid: 2495612) +Respawned uWSGI worker 4 (new pid: 2495615) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2532891) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 2532894) +Respawned uWSGI worker 3 (new pid: 2532897) +Respawned uWSGI worker 4 (new pid: 2532900) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2570353) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 2570356) +Respawned uWSGI worker 3 (new pid: 2570359) +Respawned uWSGI worker 4 (new pid: 2570362) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2607364) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 2607367) +Respawned uWSGI worker 3 (new pid: 2607370) +Respawned uWSGI worker 4 (new pid: 2607373) +[2025-08-05 11:37:35,492] WARNING in auth: 登录失败:用户不存在 (手机号: 199778999008) +[2025-08-05 11:37:39,193] INFO in auth: 用户 韦矍森(19977899008) 登录成功 +[2025-08-05 11:37:39,265] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +{address space usage: 698413056 bytes/666MB} {rss usage: 86958080 bytes/82MB} [pid: 2607370|app: 0|req: 10/32] 175.178.19.237 () {64 vars in 1321 bytes} [Tue Aug 5 11:37:48 2025] POST /recall_all_orders => generated 29 bytes in 1126 msecs (HTTP/1.1 200) 3 headers in 85 bytes (1 switches on core 1) +[2025-08-05 11:37:53,611] INFO in device: 用户 韦矍森 开始同步离线设备 +[2025-08-05 11:37:53,773] INFO in device: 同步完成:创建了 105 个工单,恢复了 0 个设备 +[2025-08-05 11:37:56,139] INFO in device: 用户请求验证码 +[2025-08-05 11:37:56,201] INFO in device: 验证码获取成功 +[2025-08-05 11:37:59,594] INFO in device: 用户 韦矍森 开始同步摄像头第一步 +[2025-08-05 11:37:59,594] INFO in device: 尝试登录中兴平台 +[2025-08-05 11:37:59,868] INFO in device: 登录成功,开始获取在线信息 +[2025-08-05 11:38:00,136] INFO in device: 获取到 1436 个设备信息,开始更新数据库 +[2025-08-05 11:38:00,405] INFO in device: 数据库更新完成,更新了 1436 条记录,其中离线设备 665 个 +[2025-08-05 11:38:01,573] INFO in device: 用户 韦矍森 开始同步摄像头第二步 +[2025-08-05 11:38:01,587] INFO in device: 查询到 1107 个需要同步的设备 +[2025-08-05 11:38:02,411] INFO in device: 发现 378 个离线设备需要创建工单 +[2025-08-05 11:38:02,768] INFO in device: 成功创建 378 个工单 +{address space usage: 699461632 bytes/667MB} {rss usage: 87482368 bytes/83MB} [pid: 2607370|app: 0|req: 18/60] 175.178.19.237 () {64 vars in 1377 bytes} [Tue Aug 5 11:38:01 2025] POST /sync_camera_step2 => generated 29 bytes in 1198 msecs (HTTP/1.1 200) 3 headers in 85 bytes (1 switches on core 1) +[2025-08-05 11:38:05,532] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-05 11:38:42,174] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-08-05 11:38:44,132] INFO in statistics: 用户 韦矍森 访问统计页面 +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2643794) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2679777) +Respawned uWSGI worker 1 (new pid: 2683778) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2683781) +Respawned uWSGI worker 4 (new pid: 2683784) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2720383) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2720386) +Respawned uWSGI worker 4 (new pid: 2720389) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2756406) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2756409) +Respawned uWSGI worker 4 (new pid: 2756412) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2792754) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2792757) +Respawned uWSGI worker 4 (new pid: 2792760) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2828698) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2828701) +Respawned uWSGI worker 4 (new pid: 2828704) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2864721) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2864724) +Respawned uWSGI worker 4 (new pid: 2864727) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2900775) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2900778) +Respawned uWSGI worker 4 (new pid: 2900781) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2936799) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2936802) +Respawned uWSGI worker 4 (new pid: 2936805) +Respawned uWSGI worker 2 (new pid: 2961524) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 2972788) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 2972791) +Respawned uWSGI worker 4 (new pid: 2975123) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 2997501) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3033500) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3069463) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3105523) +Respawned uWSGI worker 1 (new pid: 3110341) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3110344) +Respawned uWSGI worker 4 (new pid: 3110347) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3141571) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3146410) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3146413) +Respawned uWSGI worker 4 (new pid: 3146416) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3177634) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3182455) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3182458) +Respawned uWSGI worker 4 (new pid: 3182461) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3213927) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3218746) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3218749) +Respawned uWSGI worker 4 (new pid: 3218752) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3249939) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3254782) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3254785) +Respawned uWSGI worker 4 (new pid: 3254788) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3285981) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3290795) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3290798) +Respawned uWSGI worker 4 (new pid: 3290801) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3322082) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3326920) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3326923) +Respawned uWSGI worker 4 (new pid: 3326926) +worker 2 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 2 (new pid: 3358127) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 3362906) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 3362909) +Respawned uWSGI worker 4 (new pid: 3362912) +[2025-08-06 08:31:05,928] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +worker 2 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3398899) +Respawned uWSGI worker 4 (new pid: 3398902) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3435459) +Respawned uWSGI worker 4 (new pid: 3435462) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3471468) +Respawned uWSGI worker 4 (new pid: 3471471) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 3507862) +Respawned uWSGI worker 4 (new pid: 3507865) +gateway "uWSGI http 1" has been buried (pid: 2457199) +...brutally killing workers... +worker 2 buried after 1 seconds +worker 1 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Wed Aug 6 12:45:52 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 63499 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Wed Aug 6 12:46:37 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x7682f56a39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-08-06 12:46:40,524] INFO in app: 应用启动 +[2025-08-06 12:46:40,533] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 3 seconds on interpreter 0x7682f56a39e8 pid: 1774 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 1774) +spawned uWSGI worker 1 (pid: 5412, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 5415, cores: 2) +spawned uWSGI worker 3 (pid: 5418, cores: 2) +spawned uWSGI worker 4 (pid: 5421, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 5424) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 133849) +Respawned uWSGI worker 2 (new pid: 133852) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 133855) +Respawned uWSGI worker 4 (new pid: 133858) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 260114) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 260117) +Respawned uWSGI worker 3 (new pid: 260120) +Respawned uWSGI worker 4 (new pid: 260123) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 369062) +Respawned uWSGI worker 2 (new pid: 369065) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 369068) +Respawned uWSGI worker 4 (new pid: 369071) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 405152) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 405155) +Respawned uWSGI worker 3 (new pid: 405158) +Respawned uWSGI worker 4 (new pid: 405161) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 441225) +Respawned uWSGI worker 2 (new pid: 441228) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 441231) +Respawned uWSGI worker 4 (new pid: 441234) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 477368) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 477371) +Respawned uWSGI worker 3 (new pid: 477374) +Respawned uWSGI worker 4 (new pid: 477377) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 513406) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 513409) +Respawned uWSGI worker 3 (new pid: 513412) +Respawned uWSGI worker 4 (new pid: 513415) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 549436) +Respawned uWSGI worker 2 (new pid: 549439) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 549442) +Respawned uWSGI worker 4 (new pid: 549445) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 585632) +Respawned uWSGI worker 2 (new pid: 585635) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 585638) +Respawned uWSGI worker 4 (new pid: 585641) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 621862) +Respawned uWSGI worker 2 (new pid: 621865) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 621868) +Respawned uWSGI worker 4 (new pid: 621871) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 658051) +Respawned uWSGI worker 2 (new pid: 658054) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 658057) +Respawned uWSGI worker 4 (new pid: 658060) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 694123) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 694126) +Respawned uWSGI worker 3 (new pid: 694129) +Respawned uWSGI worker 4 (new pid: 694132) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 730251) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 730254) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 766373) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 766376) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 802561) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 802564) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 838684) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 838687) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 874787) +Respawned uWSGI worker 3 (new pid: 874790) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 915869) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 915872) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 952144) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 952147) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 988278) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 988281) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1025014) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 1025017) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1061980) +Respawned uWSGI worker 3 (new pid: 1061983) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1104349) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 1104352) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1146742) +Respawned uWSGI worker 3 (new pid: 1146743) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1184724) +Respawned uWSGI worker 3 (new pid: 1184727) +Python auto-reloader enabled +Respawned uWSGI worker 2 (new pid: 1218285) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1222695) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 3 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 3 (new pid: 1265880) +Respawned uWSGI worker 4 (new pid: 1298898) +Respawned uWSGI worker 1 (new pid: 1298901) +Respawned uWSGI worker 2 (new pid: 1298904) +Python auto-reloader enabled +[2025-08-07 16:39:57,738] INFO in auth: 用户 韦矍森(19977899008) 登录成功 +[2025-08-07 16:39:57,807] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +worker 3 lifetime reached, it was running for 3601 second(s) +worker 1 lifetime reached, it was running for 3601 second(s) +worker 2 lifetime reached, it was running for 3601 second(s) +worker 4 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1342281) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1383587) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1425263) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1461537) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1497601) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1533735) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1569740) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1606383) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1642489) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1678529) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1714489) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1750647) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1786720) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1822736) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1858931) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1894963) +Python auto-reloader enabled +worker 1 lifetime reached, it was running for 3601 second(s) +Respawned uWSGI worker 1 (new pid: 1931686) +Python auto-reloader enabled +Respawned uWSGI worker 3 (new pid: 1942531) +Respawned uWSGI worker 4 (new pid: 1942534) +Respawned uWSGI worker 2 (new pid: 1942537) +[2025-08-08 09:54:17,988] INFO in auth: 用户 韦矍森(19977899008) 登录成功 +[2025-08-08 09:54:18,087] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 09:54:30,491] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 09:54:43,337] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-08-08 10:12:48,056] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:13:00,797] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:13:04,652] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:13:13,192] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:17,587] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:13:17,935] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:20,589] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:28,698] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:28,772] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:30,637] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:30,707] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:31,778] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:13:31,854] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:24,107] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:24,195] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:26,821] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:26,904] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:28,110] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:28,193] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:29,160] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:29,239] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:17:39,394] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:17:41,674] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:17:57,666] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:18:06,121] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-08-08 10:18:20,196] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:21:25,374] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[2025-08-08 10:26:58,679] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:27:02,165] INFO in history: 用户 韦矍森 访问历史记录页面 +[2025-08-08 10:27:08,980] INFO in dashboard: 用户 韦矍森 访问仪表盘页面 +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +gateway "uWSGI http 1" has been buried (pid: 5424) +...gracefully killing workers... +Gracefully killing worker 2 (pid: 1942537)... +Gracefully killing worker 4 (pid: 1942534)... +Gracefully killing worker 3 (pid: 1942531)... +Gracefully killing worker 1 (pid: 1931686)... +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +worker 1 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +worker 2 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Fri Aug 8 10:29:34 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Fri Aug 8 10:29:36 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x7daefa6a39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-08-08 10:29:37,100] INFO in app: 应用启动 +[2025-08-08 10:29:37,103] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x7daefa6a39e8 pid: 1966896 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 1966896) +spawned uWSGI worker 1 (pid: 1966911, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 1966914, cores: 2) +spawned uWSGI worker 3 (pid: 1966917, cores: 2) +spawned uWSGI worker 4 (pid: 1966920, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 1966923) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +[uwsgi-python-reloader] module/file /opt/1panel/docker/compose/4an/app/views/work_order.py has been modified +gateway "uWSGI http 1" has been buried (pid: 1966923) +...gracefully killing workers... +Gracefully killing worker 2 (pid: 1966914)... +Gracefully killing worker 4 (pid: 1966920)... +Gracefully killing worker 3 (pid: 1966917)... +Gracefully killing worker 1 (pid: 1966911)... +worker 1 buried after 1 seconds +worker 2 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Fri Aug 8 10:30:44 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Fri Aug 8 10:30:44 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x79f998ca39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-08-08 10:30:45,926] INFO in app: 应用启动 +[2025-08-08 10:30:45,929] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x79f998ca39e8 pid: 1967797 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 1967797) +spawned uWSGI worker 1 (pid: 1967815, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 1967818, cores: 2) +spawned uWSGI worker 3 (pid: 1967821, cores: 2) +spawned uWSGI worker 4 (pid: 1967824, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 1967827) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +gateway "uWSGI http 1" has been buried (pid: 1967827) +...brutally killing workers... +worker 1 buried after 1 seconds +worker 2 buried after 1 seconds +worker 3 buried after 1 seconds +worker 4 buried after 1 seconds +binary reloading uWSGI... +chdir() to /opt/1panel/docker/compose/4an/app +closing all non-uwsgi socket fds > 2 (max_fd = 1024)... +found fd 9 mapped to socket 0 (/opt/1panel/docker/compose/4an/uwsgi.sock) +running /mnt/nfs/.venv/bin/uwsgi +[uWSGI] getting INI configuration from /opt/1panel/docker/compose/4an/app/uwsgi.ini +*** Starting uWSGI 2.0.28 (64bit) on [Fri Aug 8 10:33:50 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 7 +probably another instance of uWSGI is running on the same address (0.0.0.0:18019). +bind(): Address already in use [core/socket.c line 769] +VACUUM: pidfile removed. +*** Starting uWSGI 2.0.28 (64bit) on [Fri Aug 8 10:33:51 2025] *** +compiled with version: 13.3.0 on 04 January 2025 05:49:52 +os: Linux-6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 +nodename: ubserver +machine: x86_64 +clock source: unix +detected number of CPU cores: 12 +current working directory: /opt/1panel/docker/compose/4an/app +writing pidfile to /opt/1panel/docker/compose/4an/uwsgi.pid +detected binary path: /mnt/nfs/.venv/bin/uwsgi +!!! no internal routing support, rebuild with pcre support !!! +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +chdir() to /opt/1panel/docker/compose/4an/app +your processes number limit is 71467 +your memory page size is 4096 bytes + *** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers *** +detected max file descriptor number: 1024 +lock engine: pthread robust mutexes +thunder lock: disabled (you can enable it with --thunder-lock) +uWSGI http bound on 0.0.0.0:18019 fd 6 +uwsgi socket 0 bound to UNIX address /opt/1panel/docker/compose/4an/uwsgi.sock fd 9 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] +PEP 405 virtualenv detected: /mnt/nfs/.venv +Set PythonHome to /mnt/nfs/.venv +Python main interpreter initialized at 0x73231fea39e8 +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +python threads support enabled +your server socket listen backlog is limited to 100 connections +your mercy for graceful operations on workers is 60 seconds +mapped 703600 bytes (687 KB) for 8 cores +*** Operational MODE: preforking+threaded *** +[2025-08-08 10:33:52,612] INFO in app: 应用启动 +[2025-08-08 10:33:52,615] INFO in error_handlers: 错误处理器初始化完成 +WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x73231fea39e8 pid: 1969868 (default app) +uWSGI running as root, you can use --uid/--gid/--chroot options +*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** +*** uWSGI is running in multiple interpreter mode *** +spawned uWSGI master process (pid: 1969868) +spawned uWSGI worker 1 (pid: 1969880, cores: 2) +Python auto-reloader enabled +spawned uWSGI worker 2 (pid: 1969883, cores: 2) +spawned uWSGI worker 3 (pid: 1969886, cores: 2) +spawned uWSGI worker 4 (pid: 1969889, cores: 2) +*** Stats server enabled on 127.0.0.1:9191 fd: 22 *** +spawned uWSGI http 1 (pid: 1969892) +unable to stat() uwsgi.reload, events will be triggered as soon as the file is created +unable to stat() true, events will be triggered as soon as the file is created +[2025-08-08 10:36:13,166] ERROR in error_handlers: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"5+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +[2025-08-08 10:36:13,249] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:36:17,642] ERROR in error_handlers: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"6+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +[2025-08-08 10:36:17,709] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:36:21,772] ERROR in error_handlers: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"4+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +[2025-08-08 10:36:21,832] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:36:27,372] ERROR in error_handlers: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '摄像头', 'project_type_1': '校园安防"4+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +[2025-08-08 10:36:27,432] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:36:33,232] ERROR in error_handlers: 数据库操作错误: (pymysql.err.OperationalError) (1066, "Not unique table/alias: 'device'") +[SQL: SELECT work_order.order_id AS work_order_order_id, work_order.device_id AS work_order_device_id, work_order.dispatch_time AS work_order_dispatch_time, work_order.status AS work_order_status, work_order.branch AS work_order_branch +FROM work_order INNER JOIN device ON device.device_id = work_order.device_id INNER JOIN device ON device.device_id = work_order.device_id +WHERE work_order.status = %(status_1)s AND device.device_type = %(device_type_1)s AND device.project_type = %(project_type_1)s + LIMIT %(param_1)s, %(param_2)s] +[parameters: {'status_1': '待处理', 'device_type_1': '一键报警', 'project_type_1': '校园安防"4+N"项目', 'param_1': 0, 'param_2': 50}] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +[2025-08-08 10:36:33,291] INFO in statistics: 用户 韦矍森 访问统计页面 +[2025-08-08 10:36:35,836] INFO in statistics: 用户 韦矍森 访问统计页面 diff --git a/uwsgi.pid b/uwsgi.pid new file mode 100644 index 0000000..ea30128 --- /dev/null +++ b/uwsgi.pid @@ -0,0 +1 @@ +1969868