diff --git a/.gitignore b/.gitignore index 0a2c1cf..38490e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ out/ dist/ +release/ +.temp/ .idea/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..4464e3d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +electron_mirror=https://npmmirror.com/mirrors/electron/ diff --git a/appstore-server/Dockerfile b/appstore-server/Dockerfile new file mode 100644 index 0000000..976396c --- /dev/null +++ b/appstore-server/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple + +COPY app.py . + +ENV DB_HOST=127.0.0.1 +ENV DB_PORT=3306 +ENV DB_USER=root +ENV DB_PASSWORD=123456 +ENV DB_NAME=appstore +ENV API_PORT=3001 +ENV API_TOKEN=123456 + +EXPOSE 3001 + +CMD ["python", "app.py"] diff --git a/appstore-server/app.py b/appstore-server/app.py new file mode 100644 index 0000000..f0ee2e5 --- /dev/null +++ b/appstore-server/app.py @@ -0,0 +1,234 @@ +""" +应用商店 HTTP API 服务 +从 MySQL 读取应用模板数据,返回 JSON 给 Electron 客户端 +所有数据库连接参数通过环境变量配置 +""" + +import os +import json +from flask import Flask, jsonify, request +from flask_cors import CORS +import mysql.connector + +app = Flask(__name__) + +# 允许跨域(Electron 渲染进程直接 fetch 时需要) +CORS(app) + +# ─── 数据库配置(环境变量) ─── +DB_HOST = os.environ.get('DB_HOST', '127.0.0.1') +DB_PORT = int(os.environ.get('DB_PORT', '3306')) +DB_USER = os.environ.get('DB_USER', 'root') +DB_PASSWORD = os.environ.get('DB_PASSWORD', '123456') +DB_NAME = os.environ.get('DB_NAME', 'appstore') + +# API 监听端口 +API_PORT = int(os.environ.get('API_PORT', '3001')) + +# API 鉴权 token(可选,客户端通过 Authorization header 传递) +API_TOKEN = os.environ.get('API_TOKEN', '') + + +def get_db(): + """获取数据库连接""" + return mysql.connector.connect( + host=DB_HOST, + port=DB_PORT, + user=DB_USER, + password=DB_PASSWORD, + database=DB_NAME, + charset='utf8mb4' + ) + + +def check_token(): + """校验 API token,未配置 token 时跳过""" + if not API_TOKEN: + return True + auth = request.headers.get('Authorization', '') + return auth == f'Bearer {API_TOKEN}' + + +def fetch_template_list(): + """查询所有模板(列表摘要)""" + conn = get_db() + cursor = conn.cursor(dictionary=True) + cursor.execute( + 'SELECT id, app_key, name, description, icon, category, ' + 'image, container_name, sort_order, is_active ' + 'FROM app_templates WHERE is_active = 1 ORDER BY sort_order, id' + ) + rows = cursor.fetchall() + cursor.close() + conn.close() + return rows + + +def fetch_template_detail(app_key): + """查询单个模板的完整详情(含子表)""" + conn = get_db() + cursor = conn.cursor(dictionary=True) + + # 主表 + cursor.execute('SELECT * FROM app_templates WHERE app_key = %s AND is_active = 1', (app_key,)) + tpl = cursor.fetchone() + if not tpl: + cursor.close() + conn.close() + return None + + # 子表批量查询 + sub_tables = ['app_ports', 'app_volumes', 'app_envs', 'app_mkdirs', 'app_write_files', 'app_params'] + for table in sub_tables: + cursor.execute(f'SELECT * FROM {table} WHERE template_id = %s ORDER BY sort_order', (tpl['id'],)) + rows = cursor.fetchall() + # 序列化:去掉 template_id 和 id,JSON 友好 + cleaned = [] + for r in rows: + item = {k: v for k, v in r.items() if k not in ('id', 'template_id')} + cleaned.append(item) + # 子表名映射到 deploy 对应字段 + field_map = { + 'app_ports': 'ports', + 'app_volumes': 'volumes', + 'app_envs': 'env', + 'app_mkdirs': 'mkdirs', + 'app_write_files': 'writeFiles', + 'app_params': 'params' + } + tpl[field_map[table]] = cleaned + + # 删除不需要的主表字段 + tpl.pop('id', None) + tpl.pop('is_active', None) + tpl.pop('sort_order', None) + tpl.pop('created_at', None) + tpl.pop('updated_at', None) + + cursor.close() + conn.close() + return tpl + + +def fetch_all_templates_full(): + """一次性查询所有模板的完整详情""" + conn = get_db() + cursor = conn.cursor(dictionary=True) + + # 所有主表记录 + cursor.execute( + 'SELECT * FROM app_templates WHERE is_active = 1 ORDER BY sort_order, id' + ) + templates = cursor.fetchall() + + sub_tables = ['app_ports', 'app_volumes', 'app_envs', 'app_mkdirs', 'app_write_files', 'app_params'] + field_map = { + 'app_ports': 'ports', + 'app_volumes': 'volumes', + 'app_envs': 'env', + 'app_mkdirs': 'mkdirs', + 'app_write_files': 'writeFiles', + 'app_params': 'params' + } + + for tpl in templates: + tpl_id = tpl['id'] + for table in sub_tables: + cursor.execute( + f'SELECT * FROM {table} WHERE template_id = %s ORDER BY sort_order', + (tpl_id,) + ) + rows = cursor.fetchall() + cleaned = [] + for r in rows: + item = {k: v for k, v in r.items() if k not in ('id', 'template_id')} + cleaned.append(item) + tpl[field_map[table]] = cleaned + + # 清理主表多余字段 + tpl.pop('id', None) + tpl.pop('is_active', None) + tpl.pop('sort_order', None) + tpl.pop('created_at', None) + tpl.pop('updated_at', None) + + cursor.close() + conn.close() + return templates + + +# ─── API 路由 ─── + +@app.route('/api/apps', methods=['GET']) +def list_apps(): + """获取所有应用模板列表(完整详情)""" + if not check_token(): + return jsonify({'ok': False, 'error': '未授权'}), 401 + + try: + templates = fetch_all_templates_full() + return jsonify({'ok': True, 'data': templates, 'count': len(templates)}) + except Exception as e: + return jsonify({'ok': False, 'error': str(e)}), 500 + + +@app.route('/api/apps/', methods=['GET']) +def get_app(app_key): + """获取单个应用模板详情""" + if not check_token(): + return jsonify({'ok': False, 'error': '未授权'}), 401 + + try: + tpl = fetch_template_detail(app_key) + if not tpl: + return jsonify({'ok': False, 'error': '模板不存在'}), 404 + return jsonify({'ok': True, 'data': tpl}) + except Exception as e: + return jsonify({'ok': False, 'error': str(e)}), 500 + + +@app.route('/api/health', methods=['GET']) +def health(): + """健康检查""" + return jsonify({'ok': True, 'status': 'running'}) + + +@app.route('/api/version', methods=['GET']) +def get_version(): + """获取客户端最新版本信息""" + if not check_token(): + return jsonify({'ok': False, 'error': '未授权'}), 401 + + try: + conn = get_db() + cursor = conn.cursor(dictionary=True) + cursor.execute( + 'SELECT version, download_url, release_notes, is_latest, ' + 'created_at FROM app_version WHERE is_latest = 1 ORDER BY id DESC LIMIT 1' + ) + row = cursor.fetchone() + cursor.close() + conn.close() + + if not row: + return jsonify({'ok': False, 'error': '暂无版本信息'}), 404 + + return jsonify({ + 'ok': True, + 'data': { + 'version': row['version'], + 'downloadUrl': row['download_url'], + 'releaseNotes': row['release_notes'] or '', + 'isLatest': bool(row['is_latest']), + 'createdAt': row['created_at'].isoformat() if row['created_at'] else None, + } + }) + except Exception as e: + return jsonify({'ok': False, 'error': str(e)}), 500 + + +if __name__ == '__main__': + print(f'[AppStore API] DB: {DB_HOST}:{DB_PORT}/{DB_NAME}') + print(f'[AppStore API] Listen on :{API_PORT}') + print(f'[AppStore API] Token auth: {"on" if API_TOKEN else "off"}') + app.run(host='0.0.0.0', port=API_PORT, debug=False) diff --git a/appstore-server/deploy.sh b/appstore-server/deploy.sh new file mode 100644 index 0000000..468291e --- /dev/null +++ b/appstore-server/deploy.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# ============================================================ +# 应用商店 API 服务一键部署脚本 +# 在服务器 192.168.153.153 上执行 +# 功能:建表 → 构建镜像 → 启动容器 +# 用法:bash deploy.sh +# ============================================================ + +set -e + +echo "========== 1/4 创建数据库和表 ==========" +# 注意:MySQL 容器已在运行,3306 端口映射到宿主机 +mysql -h 127.0.0.1 -P 3306 -u root -p${MYSQL_ROOT_PASSWORD:-123456} < init.sql +echo "数据库和表创建完成" + +echo "========== 2/4 构建 API 镜像 ==========" +docker build -t appstore-api:latest . + +echo "========== 3/4 停止旧容器(如有) ==========" +docker rm -f appstore-api 2>/dev/null || true + +echo "========== 4/4 启动 API 容器 ==========" +docker run -d \ + --name appstore-api \ + --restart always \ + -p 3001:3001 \ + -e DB_HOST=127.0.0.1 \ + -e DB_PORT=3306 \ + -e DB_USER=root \ + -e DB_PASSWORD=${MYSQL_ROOT_PASSWORD:-123456} \ + -e DB_NAME=appstore \ + -e API_PORT=3001 \ + appstore-api:latest + +echo "" +echo "========== 部署完成 ==========" +echo "API 地址: http://$(hostname -I | awk '{print $1}'):3001/api/apps" +echo "健康检查: http://$(hostname -I | awk '{print $1}'):3001/api/health" diff --git a/appstore-server/init.sql b/appstore-server/init.sql new file mode 100644 index 0000000..e3af795 --- /dev/null +++ b/appstore-server/init.sql @@ -0,0 +1,290 @@ +-- ============================================================ +-- 应用商店数据库 —— 建表 + 初始数据 +-- 数据库: my_panel +-- 字符集: utf8mb4 +-- ============================================================ + +-- ─── 主表 ─── +CREATE TABLE IF NOT EXISTS app_templates ( + id INT AUTO_INCREMENT PRIMARY KEY, + app_key VARCHAR(64) NOT NULL UNIQUE COMMENT '唯一标识 (redis / mysql)', + name VARCHAR(128) NOT NULL COMMENT '显示名称', + tagline VARCHAR(256) DEFAULT NULL COMMENT '一句话描述', + description TEXT DEFAULT NULL COMMENT '详细描述', + icon VARCHAR(32) DEFAULT NULL COMMENT '图标标识 (redis / mysql)', + category VARCHAR(64) DEFAULT NULL COMMENT '分类', + image VARCHAR(256) NOT NULL COMMENT 'Docker 镜像', + container_name VARCHAR(128) NOT NULL COMMENT '容器名称', + network VARCHAR(128) DEFAULT '' COMMENT 'Docker 网络', + restart VARCHAR(32) DEFAULT 'always' COMMENT '重启策略', + hostname VARCHAR(128) DEFAULT '' COMMENT '容器主机名', + workdir VARCHAR(256) DEFAULT '' COMMENT '工作目录', + command TEXT DEFAULT NULL COMMENT '启动命令', + extra_args TEXT DEFAULT NULL COMMENT '额外 docker run 参数模板 (含 {{}} 占位符)', + sort_order INT DEFAULT 0 COMMENT '排序权重', + is_active TINYINT(1) DEFAULT 1 COMMENT '是否上架 1=是 0=否', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用模板主表'; + +-- ─── 子表:端口映射 ─── +CREATE TABLE IF NOT EXISTS app_ports ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + host_port VARCHAR(16) NOT NULL COMMENT '宿主机端口', + container_port VARCHAR(16) NOT NULL COMMENT '容器端口', + protocol VARCHAR(8) DEFAULT 'tcp' COMMENT '协议', + sort_order INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES app_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ─── 子表:挂载卷 ─── +CREATE TABLE IF NOT EXISTS app_volumes ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + host_path VARCHAR(512) NOT NULL COMMENT '宿主机路径', + container_path VARCHAR(512) NOT NULL COMMENT '容器路径', + mode VARCHAR(4) DEFAULT 'rw' COMMENT '读写模式', + sort_order INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES app_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ─── 子表:环境变量 ─── +CREATE TABLE IF NOT EXISTS app_envs ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + env_key VARCHAR(256) NOT NULL COMMENT '变量名', + env_value TEXT DEFAULT NULL COMMENT '变量值 (含 {{}} 占位符)', + sort_order INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES app_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ─── 子表:自动创建目录 ─── +CREATE TABLE IF NOT EXISTS app_mkdirs ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + dir_path VARCHAR(512) NOT NULL COMMENT '宿主机目录', + sort_order INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES app_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ─── 子表:自动写入配置文件 ─── +CREATE TABLE IF NOT EXISTS app_write_files ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + file_path VARCHAR(512) NOT NULL COMMENT '宿主机文件路径', + file_content LONGTEXT NOT NULL COMMENT '文件内容 (含 {{}} 占位符)', + sort_order INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES app_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ─── 子表:用户参数 ─── +CREATE TABLE IF NOT EXISTS app_params ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + param_key VARCHAR(64) NOT NULL COMMENT '参数键', + label VARCHAR(128) NOT NULL COMMENT '显示标签', + type VARCHAR(16) DEFAULT 'text' COMMENT 'text/password/number', + placeholder VARCHAR(256) DEFAULT NULL, + default_value VARCHAR(256) DEFAULT '' COMMENT '默认值', + required TINYINT(1) DEFAULT 0 COMMENT '是否必填', + options TEXT DEFAULT NULL COMMENT '下拉选项 JSON 数组 [{"value":"","label":""}]', + show_when VARCHAR(128) DEFAULT NULL COMMENT '条件显示 param_key:value', + sort_order INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES app_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + +-- ============================================================ +-- 初始数据:Redis 模板 +-- ============================================================ +INSERT INTO app_templates (app_key, name, tagline, description, icon, category, image, container_name, network, restart, hostname, workdir, command, sort_order, is_active) +VALUES ('redis', 'Redis', '高性能内存数据库 / 缓存', +'Redis 是开源的内存数据结构存储,用作数据库、缓存、消息队列和流引擎。支持字符串、哈希、列表、集合等多种数据结构。', +'redis', '缓存', 'redis:8.10.0', 'redis', '', 'always', 'redis', '/data', +'redis-server /etc/redis/redis.conf', 1, 1); + +SET @redis_id = LAST_INSERT_ID(); + +INSERT INTO app_ports (template_id, host_port, container_port, protocol, sort_order) VALUES +(@redis_id, '6379', '6379', 'tcp', 0); + +INSERT INTO app_volumes (template_id, host_path, container_path, mode, sort_order) VALUES +(@redis_id, '/opt/redis/data', '/data', 'rw', 0), +(@redis_id, '/opt/redis/conf/redis.conf', '/etc/redis/redis.conf', 'rw', 1), +(@redis_id, '/opt/redis/logs', '/logs', 'rw', 2); + +INSERT INTO app_mkdirs (template_id, dir_path, sort_order) VALUES +(@redis_id, '/opt/redis/data', 0), +(@redis_id, '/opt/redis/conf', 1), +(@redis_id, '/opt/redis/logs', 2); + +INSERT INTO app_write_files (template_id, file_path, file_content, sort_order) VALUES +(@redis_id, '/opt/redis/conf/redis.conf', +'# Redis 配置文件 +bind 0.0.0.0 +protected-mode no +port 6379 +tcp-backlog 511 +timeout 0 +tcp-keepalive 300 +daemonize no +supervised no +pidfile /var/run/redis_6379.pid +loglevel notice +logfile "" +databases 16 +always-show-logo yes +save 900 1 +save 300 10 +save 60 10000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir /data +appendonly no +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +# 密码(部署时替换,留空则无密码) +{{REDIS_PASSWORD_LINE}}', 0); + +INSERT INTO app_params (template_id, param_key, label, type, placeholder, default_value, required, sort_order) VALUES +(@redis_id, 'REDIS_PASSWORD', 'Redis 密码', 'password', '留空则无密码', '', 0, 0), +(@redis_id, 'PORT', '宿主机端口', 'number', '6379', '6379', 1, 1); + + +-- ============================================================ +-- 初始数据:MySQL 模板 +-- ============================================================ +INSERT INTO app_templates (app_key, name, tagline, description, icon, category, image, container_name, network, restart, hostname, workdir, command, sort_order, is_active) +VALUES ('mysql', 'MySQL', '关系型数据库', +'MySQL 是最流行的开源关系型数据库管理系统,支持事务、存储过程、触发器、视图等完整 SQL 功能。此模板基于 MySQL 8.4 LTS。', +'mysql', '数据库', 'mysql:8.4.11', 'mysql', '', 'always', 'mysql', '/', '', 2, 1); + +SET @mysql_id = LAST_INSERT_ID(); + +INSERT INTO app_ports (template_id, host_port, container_port, protocol, sort_order) VALUES +(@mysql_id, '3306', '3306', 'tcp', 0); + +INSERT INTO app_volumes (template_id, host_path, container_path, mode, sort_order) VALUES +(@mysql_id, '/opt/mysql/data', '/var/lib/mysql', 'rw', 0), +(@mysql_id, '/opt/mysql/conf/my.cnf', '/etc/my.cnf', 'rw', 1), +(@mysql_id, '/opt/mysql/log', '/var/log/mysql', 'rw', 2), +(@mysql_id, '/etc/timezone', '/etc/timezone', 'ro', 3), +(@mysql_id, '/etc/localtime', '/etc/localtime', 'ro', 4); + +INSERT INTO app_envs (template_id, env_key, env_value, sort_order) VALUES +(@mysql_id, 'MYSQL_ROOT_PASSWORD', '{{MYSQL_ROOT_PASSWORD}}', 0); + +INSERT INTO app_mkdirs (template_id, dir_path, sort_order) VALUES +(@mysql_id, '/opt/mysql/data', 0), +(@mysql_id, '/opt/mysql/conf', 1), +(@mysql_id, '/opt/mysql/log', 2); + +INSERT INTO app_write_files (template_id, file_path, file_content, sort_order) VALUES +(@mysql_id, '/opt/mysql/conf/my.cnf', +'# MySQL 配置文件 +[mysqld] +pid-file = /var/run/mysqld/mysqld.pid +socket = /var/run/mysqld/mysqld.sock +datadir = /var/lib/mysql +secure-file-priv= NULL + +# 字符集 +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# 最大连接数 +max_connections = 200 + +# Innodb +innodb_buffer_pool_size = 128M +innodb_log_file_size = 48M +innodb_flush_log_at_trx_commit = 1 + +# 日志 +log-error = /var/log/mysql/error.log +slow_query_log = 1 +slow_query_log_file = /var/log/mysql/slow.log +long_query_time = 2 + +# 端口 +port = 3306 + +[client] +default-character-set = utf8mb4 + +[mysql] +default-character-set = utf8mb4', 0); + +INSERT INTO app_params (template_id, param_key, label, type, placeholder, default_value, required, sort_order) VALUES +(@mysql_id, 'MYSQL_ROOT_PASSWORD', 'Root 密码', 'password', '设置 root 用户密码', '', 1, 0), +(@mysql_id, 'PORT', '宿主机端口', 'number', '3306', '3306', 1, 1); + + +-- ============================================================ +-- 初始数据:Halo 模板 +-- ============================================================ +INSERT INTO app_templates (app_key, name, tagline, description, icon, category, image, container_name, network, restart, hostname, workdir, command, extra_args, sort_order, is_active) +VALUES ('halo', 'Halo', '强大易用的开源建站工具', +'Halo 是一款开源的建站工具,支持博客、CMS、企业官网等多种场景。内置主题管理、插件系统、多用户、SEO 等功能。社区版基于开源仓库构建,免费使用。', +'halo', '建站', 'registry.fit2cloud.com/halo/halo:2.26', 'halo', '', 'always', 'halo', '', '', '{{HALO_DB_ARGS}}', 3, 1); + +SET @halo_id = LAST_INSERT_ID(); + +INSERT INTO app_ports (template_id, host_port, container_port, protocol, sort_order) VALUES +(@halo_id, '{{PORT}}', '8090', 'tcp', 0); + +INSERT INTO app_volumes (template_id, host_path, container_path, mode, sort_order) VALUES +(@halo_id, '/opt/halo/data', '/root/.halo2', 'rw', 0); + +INSERT INTO app_envs (template_id, env_key, env_value, sort_order) VALUES +(@halo_id, 'JVM_OPTS', '-Xmx{{JVM_MAX}} -Xms{{JVM_MIN}}', 0); + +INSERT INTO app_mkdirs (template_id, dir_path, sort_order) VALUES +(@halo_id, '/opt/halo/data', 0); + +INSERT INTO app_params (template_id, param_key, label, type, placeholder, default_value, required, options, show_when, sort_order) VALUES +(@halo_id, 'PORT', '宿主机端口', 'number', '8090', '8090', 1, NULL, NULL, 0), +(@halo_id, 'JVM_MAX', 'JVM 最大内存', 'text', '256m', '256m', 1, NULL, NULL, 1), +(@halo_id, 'JVM_MIN', 'JVM 初始内存', 'text', '256m', '256m', 1, NULL, NULL, 2), +(@halo_id, 'DB_TYPE', '数据库', 'select', 'H2 内置数据库', 'h2', 1, '[{"value":"h2","label":"H2 内置数据库(零配置)"},{"value":"mysql","label":"MySQL 外部数据库"}]', NULL, 3), +(@halo_id, 'DB_HOST', 'MySQL 地址', 'text', '127.0.0.1', '127.0.0.1', 0, NULL, 'DB_TYPE:mysql', 4), +(@halo_id, 'DB_PORT', 'MySQL 端口', 'number', '3306', '3306', 0, NULL, 'DB_TYPE:mysql', 5), +(@halo_id, 'DB_NAME', 'MySQL 库名', 'text', 'halodb', 'halodb', 0, NULL, 'DB_TYPE:mysql', 6), +(@halo_id, 'DB_USER', 'MySQL 用户名', 'text', 'root', 'root', 0, NULL, 'DB_TYPE:mysql', 7), +(@halo_id, 'DB_PASSWORD', 'MySQL 密码', 'password', '数据库密码', '', 0, NULL, 'DB_TYPE:mysql', 8); + +-- Halo 的 extra_args ({{HALO_DB_ARGS}}) 由前端 tplWithDerived 函数动态解析, +-- 根据 DB_TYPE 参数自动生成 MySQL 连接环境变量或留空(H2 模式) + + +-- ============================================================ +-- 客户端版本管理表 +-- ============================================================ +CREATE TABLE IF NOT EXISTS app_version ( + id INT AUTO_INCREMENT PRIMARY KEY, + version VARCHAR(32) NOT NULL UNIQUE COMMENT '版本号 (如 v0.0.1)', + download_url VARCHAR(512) NOT NULL COMMENT '下载地址', + release_notes TEXT DEFAULT NULL COMMENT '更新说明', + is_latest TINYINT(1) DEFAULT 0 COMMENT '是否为最新版本 1=是 0=否', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户端版本管理'; + +-- 初始版本数据:v0.0.1 +INSERT INTO app_version (version, download_url, release_notes, is_latest, created_at) +VALUES ('v0.0.1', 'https://114.66.55.63:3001/download/wfj-panel-v0.0.1.exe', +'WFJ Panel 首个版本 v0.0.1 + +功能列表: +- SSH 远程服务器连接管理 +- 服务器概览监控(CPU/内存/硬盘/网络/进程) +- 远程文件管理(上传/下载/新建/删除) +- 交互式终端(xterm.js) +- Docker 容器管理(概览/容器列表/镜像/应用商店/任务中心/配置/网络/卷) +- 应用商店一键部署(Redis/MySQL/Halo) +- 系统防火墙管理(firewalld/ufw/iptables)', 1, NOW()); diff --git a/appstore-server/requirements.txt b/appstore-server/requirements.txt new file mode 100644 index 0000000..b997cc0 --- /dev/null +++ b/appstore-server/requirements.txt @@ -0,0 +1,3 @@ +flask==3.0.3 +flask-cors==4.0.1 +mysql-connector-python==8.4.0 diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..4f35ac3 Binary files /dev/null and b/build/icon.png differ diff --git a/package-lock.json b/package-lock.json index 13167f4..b3b4dcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.1.0", "license": "MIT", "dependencies": { + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "react": "^19.2.8", "react-dom": "^19.2.8", "ssh2": "^1.17.0" @@ -16,6 +18,7 @@ "devDependencies": { "@vitejs/plugin-react": "^5.2.0", "electron": "^44.2.0", + "electron-builder": "^26.15.3", "electron-vite": "^5.0.0", "vite": "^7.3.6" } @@ -348,6 +351,86 @@ "node": ">=22.12.0" } }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@electron/get": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", @@ -369,6 +452,199 @@ "undici": "^7.24.4" } }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -811,6 +1087,19 @@ "node": ">=18" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -861,6 +1150,61 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", @@ -878,6 +1222,74 @@ "node": "^22.20 || ^24.12 || >=25" } }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmmirror.com/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -1235,6 +1647,32 @@ "win32" ] }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1280,6 +1718,29 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1287,6 +1748,40 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -1297,6 +1792,16 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -1318,6 +1823,264 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmmirror.com/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -1327,6 +2090,93 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.11.21", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", @@ -1349,6 +2199,35 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/browserslist": { "version": "4.28.9", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", @@ -1383,6 +2262,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buildcheck": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", @@ -1392,6 +2278,56 @@ "node": ">=10.0.0" } }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1402,6 +2338,49 @@ "node": ">=8" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -1423,6 +2402,144 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1430,6 +2547,13 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cpu-features": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", @@ -1444,6 +2568,53 @@ "node": ">=10.0.0" } }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1462,6 +2633,226 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron": { "version": "44.2.0", "resolved": "https://registry.npmjs.org/electron/-/electron-44.2.0.tgz", @@ -1481,6 +2872,63 @@ "node": ">= 22.12.0" } }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.422", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", @@ -1518,6 +2966,83 @@ } } }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -1531,6 +3056,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -1583,6 +3172,51 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1601,6 +3235,85 @@ } } }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1616,6 +3329,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1626,6 +3349,200 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmmirror.com/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1633,6 +3550,241 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1640,6 +3792,29 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1653,6 +3828,28 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1666,6 +3863,53 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1686,6 +3930,139 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1719,6 +4096,107 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-abi": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmmirror.com/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.28.1", + "resolved": "https://registry.npmmirror.com/undici/-/undici-6.28.1.tgz", + "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.54", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", @@ -1729,6 +4207,117 @@ "node": ">=18" } }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1749,6 +4338,52 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, "node_modules/postcss": { "version": "8.5.28", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", @@ -1778,6 +4413,53 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmmirror.com/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -1788,6 +4470,76 @@ "node": ">=0.4.0" } }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", @@ -1819,6 +4571,137 @@ "node": ">=0.10.0" } }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmmirror.com/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/rollup": { "version": "4.63.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", @@ -1865,12 +4748,39 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -1890,6 +4800,84 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1900,6 +4888,25 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/ssh2": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", @@ -1917,6 +4924,54 @@ "nan": "^2.23.0" } }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -1930,6 +4985,92 @@ "node": ">= 8.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmmirror.com/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -1947,12 +5088,63 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", @@ -1971,6 +5163,45 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/update-browserslist-db": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", @@ -2002,6 +5233,20 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", @@ -2561,12 +5806,129 @@ "@esbuild/win32-x64": "0.28.2" } }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 453b2ba..8d1ac4b 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,48 @@ "scripts": { "start": "electron-vite preview", "dev": "electron-vite dev", - "build": "electron-vite build" + "build": "electron-vite build", + "build:win": "electron-vite build && electron-builder --win --x64" + }, + "build": { + "appId": "com.wfj.panel", + "productName": "WFJ Panel", + "directories": { + "output": "release" + }, + "files": [ + "out/**/*", + "package.json" + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": ["x64"] + } + ], + "icon": "build/icon.png" + }, + "npmRebuild": false, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "WFJ Panel" + } }, "license": "MIT", "devDependencies": { "@vitejs/plugin-react": "^5.2.0", "electron": "^44.2.0", + "electron-builder": "^26.15.3", "electron-vite": "^5.0.0", "vite": "^7.3.6" }, "dependencies": { + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "react": "^19.2.8", "react-dom": "^19.2.8", "ssh2": "^1.17.0" diff --git a/src/main/index.js b/src/main/index.js index 282dba0..2389e99 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,14 +1,76 @@ -import { app, BrowserWindow, Menu, ipcMain, safeStorage } from 'electron' +import { app, BrowserWindow, Menu, ipcMain, safeStorage, dialog, shell } from 'electron' import path from 'path' import fs from 'fs' +import https from 'https' +import http from 'http' import { Client } from 'ssh2' // 隐藏 Electron 默认菜单(File / Edit 等) Menu.setApplicationMenu(null) let mainWindow = null -let activeConnection = null // 当前活动的 SSH 连接 +let activeConnection = null // 概览/文件/终端 用的 SSH 连接 let activeSession = null // { host, username },登录成功后记录 +let activeSftp = null // 缓存的 SFTP 会话,复用同一个 channel + +let dockerConnection = null // Docker 模块专用 SSH 连接(与 activeConnection 隔离) +let dockerSession = null // docker 连接的会话信息 + +let deployConnection = null // 部署任务专用 SSH 连接(第 3 条连接) +let deploySession = null // 部署连接的凭据 { host, port, username, password } + +// ---------- 后台部署任务管理 ---------- +// 每个 task: { id, appName, status, steps[], logs[], startTime, error, containerId } +// status: pending | running | done | error +const deployTasks = [] +let taskCounter = 0 + +function sendTaskUpdate(task) { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('deploy:taskUpdate', { + id: task.id, + appName: task.appName, + status: task.status, + steps: task.steps, + logs: task.logs, + startTime: task.startTime, + error: task.error, + containerId: task.containerId, + }) + } +} + +function addTaskLog(task, message) { + task.logs.push({ + time: new Date().toLocaleTimeString(), + message, + }) + sendTaskUpdate(task) +} + +function setTaskStep(task, step, message) { + // 更新步骤状态 + const stepMap = { + pull: '拉取镜像', + mkdir: '创建目录', + writefile: '写入配置', + run: '创建容器', + done: '完成', + } + const stepLabel = stepMap[step.replace('-done', '').replace('-error', '')] || step + const status = step.includes('error') ? 'error' : step.includes('done') || step === 'done' ? 'done' : 'running' + + // 找到或创建步骤 + let stepObj = task.steps.find((s) => s.name === stepLabel) + if (!stepObj) { + stepObj = { name: stepLabel, status: 'pending' } + task.steps.push(stepObj) + } + stepObj.status = status + + if (message) addTaskLog(task, message) + else sendTaskUpdate(task) +} // ---------- 凭据存储 ---------- const credFile = () => path.join(app.getPath('userData'), 'credentials.json') @@ -68,36 +130,229 @@ function createWindow() { mainWindow.on('closed', () => { mainWindow = null + if (activeShell) { + activeShell.end() + activeShell = null + } + activeSftp = null if (activeConnection) { activeConnection.end() activeConnection = null } + if (dockerConnection) { + dockerConnection.end() + dockerConnection = null + } + if (deployConnection) { + deployConnection.end() + deployConnection = null + } }) } // ---------- IPC:SSH 连接 ---------- + +// 内部函数:建立 Docker 专用 SSH 连接 +function connectDocker(host, port, username, password) { + if (dockerConnection) { + dockerConnection.end() + dockerConnection = null + } + + const client = new Client() + + client.on('ready', () => { + dockerConnection = client + dockerSession = { host, username } + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('docker:ready', { ok: true }) + } + }) + + client.on('error', () => { + if (dockerConnection === client) { + dockerConnection = null + dockerSession = null + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('docker:disconnected', { message: 'Docker SSH 连接异常' }) + } + } + }) + + client.on('close', () => { + if (dockerConnection === client) { + dockerConnection = null + dockerSession = null + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('docker:disconnected', { message: 'Docker SSH 连接已断开' }) + } + } + }) + + client.connect({ + host, + port: Number(port), + username, + password, + readyTimeout: 10000, + }) +} + +// 内部函数:获取或创建部署专用 SSH 连接 +function getDeployConnection() { + return new Promise((resolve, reject) => { + // 已有连接且仍然存活 + if (deployConnection) { + resolve(deployConnection) + return + } + if (!deploySession) { + reject(new Error('部署连接凭据不存在')) + return + } + const { host, port, username, password } = deploySession + const client = new Client() + let settled = false + + client.on('ready', () => { + if (settled) return + settled = true + deployConnection = client + resolve(client) + }) + + client.on('error', (err) => { + if (!settled) { + settled = true + reject(err) + } + if (deployConnection === client) { + deployConnection = null + } + }) + + client.on('close', () => { + if (!settled) { + settled = true + reject(new Error('部署连接被关闭')) + } + if (deployConnection === client) { + deployConnection = null + } + }) + + client.connect({ + host, + port: Number(port), + username, + password, + readyTimeout: 10000, + }) + }) +} + +// 在部署连接上执行命令(直接并发,不加串行队列) +// 每个部署任务内部是 await 顺序执行的,多个任务之间可以并行 +// SSH 单连接支持多个并发 channel,2-3 个部署任务完全够用 +function execDeploy(cmd, timeoutMs = 30000) { + return new Promise((resolve) => { + getDeployConnection().then((conn) => { + let settled = false + let stdout = '' + let stderr = '' + const timer = setTimeout(() => { + if (!settled) { + settled = true + resolve({ ok: false, message: '命令执行超时' }) + } + }, timeoutMs) + + conn.exec(cmd, (err, stream) => { + if (err) { + clearTimeout(timer) + if (!settled) { + settled = true + resolve({ ok: false, message: err.message }) + } + return + } + stream.on('data', (chunk) => (stdout += chunk.toString())) + stream.stderr.on('data', (chunk) => (stderr += chunk.toString())) + stream.on('close', (code) => { + clearTimeout(timer) + if (!settled) { + settled = true + resolve({ ok: true, code, stdout, stderr }) + } + }) + }) + }).catch((err) => { + resolve({ ok: false, message: err.message }) + }) + }) +} + ipcMain.handle('ssh:connect', (event, { host, port, username, password }) => { return new Promise((resolve) => { // 已有连接时先断开旧连接 + if (activeShell) { + activeShell.end() + activeShell = null + } + if (activeSftp) { + activeSftp = null + } if (activeConnection) { activeConnection.end() activeConnection = null } const client = new Client() + let settled = false client.on('ready', () => { + if (settled) return + settled = true activeConnection = client activeSession = { host, username } + // 保存部署连接凭据(懒创建,首次部署时才建立连接) + deploySession = { host, port: Number(port), username, password } + // 自动建立 Docker 专用连接(复用相同凭据) + connectDocker(host, port, username, password) resolve({ ok: true }) }) + // 连接阶段的错误 → resolve 给前端 + // 连接成功后的运行期错误 → 仅清理连接状态,不 crash client.on('error', (err) => { - resolve({ ok: false, message: err.message }) + if (!settled) { + settled = true + resolve({ ok: false, message: err.message }) + } + // 运行期 error:清理连接,让 UI 提示断开重连 + if (activeConnection === client) { + activeConnection = null + activeSftp = null + activeSession = null + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('ssh:disconnected', { message: err.message }) + } + } }) client.on('close', () => { - if (activeConnection === client) activeConnection = null + if (!settled) { + settled = true + resolve({ ok: false, message: '连接被关闭' }) + } + if (activeConnection === client) { + activeConnection = null + activeSftp = null + activeSession = null + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('ssh:disconnected', { message: '连接已断开' }) + } + } }) client.connect({ @@ -111,53 +366,105 @@ ipcMain.handle('ssh:connect', (event, { host, port, username, password }) => { }) ipcMain.handle('ssh:disconnect', () => { + if (activeShell) { + activeShell.end() + activeShell = null + } + activeSftp = null if (activeConnection) { activeConnection.end() activeConnection = null } activeSession = null + // 同时断开 Docker 专用连接 + if (dockerConnection) { + dockerConnection.end() + dockerConnection = null + } + dockerSession = null + // 断开部署专用连接 + if (deployConnection) { + deployConnection.end() + deployConnection = null + } + deploySession = null return { ok: true } }) ipcMain.handle('ssh:session', () => activeSession) // 在活动的 SSH 连接上执行命令 +// 使用队列串行化所有 exec 请求,避免并发打开过多 SSH channel 导致 +// "Channel open failure: open failed" 错误 +const execQueue = [] +let execRunning = false + function execSsh(cmd, timeoutMs = 10000) { return new Promise((resolve) => { - if (!activeConnection) { - resolve({ ok: false, message: 'SSH 连接不存在' }) - return + execQueue.push({ cmd, timeoutMs, resolve }) + drainExecQueue() + }) +} + +function drainExecQueue() { + if (execRunning || execQueue.length === 0) return + execRunning = true + const item = execQueue.shift() + if (item.task) { + // SFTP 任务 + item + .task() + .then((result) => item.resolve(result)) + .catch((err) => item.resolve({ ok: false, message: err.message })) + .finally(() => { + execRunning = false + drainExecQueue() + }) + } else { + // exec 任务 + runExec(item.cmd, item.timeoutMs, (result) => { + item.resolve(result) + execRunning = false + // 继续处理队列中下一个请求 + drainExecQueue() + }) + } +} + +function runExec(cmd, timeoutMs, done) { + if (!activeConnection) { + done({ ok: false, message: 'SSH 连接不存在' }) + return + } + + let settled = false + let stdout = '' + let stderr = '' + + const timer = setTimeout(() => { + if (!settled) { + settled = true + done({ ok: false, message: '命令执行超时' }) } + }, timeoutMs) - let settled = false - let stdout = '' - let stderr = '' - - const timer = setTimeout(() => { + activeConnection.exec(cmd, (err, stream) => { + if (err) { + clearTimeout(timer) if (!settled) { settled = true - resolve({ ok: false, message: '命令执行超时' }) + done({ ok: false, message: err.message }) } - }, timeoutMs) - - activeConnection.exec(cmd, (err, stream) => { - if (err) { - clearTimeout(timer) - if (!settled) { - settled = true - resolve({ ok: false, message: err.message }) - } - return + return + } + stream.on('data', (chunk) => (stdout += chunk.toString())) + stream.stderr.on('data', (chunk) => (stderr += chunk.toString())) + stream.on('close', (code) => { + clearTimeout(timer) + if (!settled) { + settled = true + done({ ok: true, code, stdout, stderr }) } - stream.on('data', (chunk) => (stdout += chunk.toString())) - stream.stderr.on('data', (chunk) => (stderr += chunk.toString())) - stream.on('close', (code) => { - clearTimeout(timer) - if (!settled) { - settled = true - resolve({ ok: true, code, stdout, stderr }) - } - }) }) }) } @@ -198,6 +505,864 @@ ipcMain.handle('cred:delete', (event, { host, username }) => { return { ok: true } }) +// ---------- IPC:SFTP 文件管理 ---------- + +// SFTP 操作也走串行队列,和 exec 共用同一个队列, +// 避免并发打开过多 SSH channel 导致 "Channel open failure" +function runSftpTask(task) { + return new Promise((resolve) => { + execQueue.push({ + resolve, + task, // async function() => result + }) + drainExecQueue() + }) +} + +// 获取 SFTP 会话(复用 activeConnection,缓存同一个 SFTP channel) +function getSftp() { + return new Promise((resolve, reject) => { + if (!activeConnection) { + reject(new Error('SSH 连接不存在')) + return + } + // 已有缓存的 sftp 会话且连接仍然有效,直接复用 + if (activeSftp) { + resolve(activeSftp) + return + } + activeConnection.sftp((err, sftp) => { + if (err) reject(err) + else { + activeSftp = sftp + resolve(sftp) + } + }) + }) +} + +// 列目录 +ipcMain.handle('sftp:list', async (event, { remotePath }) => { + return runSftpTask(async () => { + const sftp = await getSftp() + const list = await new Promise((resolve, reject) => { + sftp.readdir(remotePath, (err, items) => { + if (err) reject(err) + else resolve(items) + }) + }) + // 规范化字段 + const result = list.map((item) => ({ + filename: item.filename, + longname: item.longname || '', + attrs: { + size: item.attrs.size, + mtime: item.attrs.mtime, + atime: item.attrs.atime, + permissions: item.attrs.permissions, + uid: item.attrs.uid, + gid: item.attrs.gid, + }, + isDirectory: item.attrs.isDirectory(), + isFile: item.attrs.isFile(), + isSymlink: item.attrs.isSymbolicLink(), + })) + // 目录排前,文件排后,同类按名称排序 + result.sort((a, b) => { + if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1 + return a.filename.localeCompare(b.filename) + }) + return { ok: true, list: result } + }) +}) + +// 上传文件(本地 → 远程) +ipcMain.handle('sftp:upload', async (event, { localPath, remotePath }) => { + return runSftpTask(async () => { + const sftp = await getSftp() + const totalSize = fs.statSync(localPath).size + let uploaded = 0 + + return await new Promise((resolve) => { + const rs = fs.createReadStream(localPath) + const ws = sftp.createWriteStream(remotePath) + + rs.on('data', (chunk) => { + uploaded += chunk.length + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('sftp:progress', { + action: 'upload', + filename: path.basename(localPath), + transferred: uploaded, + total: totalSize, + }) + } + }) + + ws.on('close', () => { + resolve({ ok: true, transferred: uploaded, total: totalSize }) + }) + + ws.on('error', (err) => { + resolve({ ok: false, message: err.message }) + }) + + rs.on('error', (err) => { + resolve({ ok: false, message: err.message }) + }) + + rs.pipe(ws) + }) + }) +}) + +// 下载文件(远程 → 本地) +ipcMain.handle('sftp:download', async (event, { remotePath, localPath }) => { + return runSftpTask(async () => { + const sftp = await getSftp() + const stat = await new Promise((resolve, reject) => { + sftp.stat(remotePath, (err, stats) => { + if (err) reject(err) + else resolve(stats) + }) + }) + const totalSize = stat.size + let downloaded = 0 + + return await new Promise((resolve) => { + const rs = sftp.createReadStream(remotePath) + const ws = fs.createWriteStream(localPath) + + rs.on('data', (chunk) => { + downloaded += chunk.length + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('sftp:progress', { + action: 'download', + filename: path.basename(remotePath), + transferred: downloaded, + total: totalSize, + }) + } + }) + + ws.on('close', () => { + resolve({ ok: true, transferred: downloaded, total: totalSize }) + }) + + ws.on('error', (err) => { + resolve({ ok: false, message: err.message }) + }) + + rs.on('error', (err) => { + resolve({ ok: false, message: err.message }) + }) + + rs.pipe(ws) + }) + }) +}) + +// 打开本地文件选择对话框(用于上传,支持多选) +ipcMain.handle('sftp:selectLocalFile', async () => { + const result = await dialog.showOpenDialog(mainWindow, { + title: '选择要上传的文件', + properties: ['openFile', 'multiSelections'], + }) + if (result.canceled || result.filePaths.length === 0) return { ok: false } + return { ok: true, paths: result.filePaths } +}) + +// 打开本地保存对话框(用于下载单个文件) +ipcMain.handle('sftp:selectSavePath', async (event, { defaultName }) => { + const result = await dialog.showSaveDialog(mainWindow, { + title: '保存文件', + defaultPath: defaultName || 'download', + }) + if (result.canceled) return { ok: false } + return { ok: true, path: result.filePath } +}) + +// 打开本地目录选择对话框(用于批量下载到同一目录) +ipcMain.handle('sftp:selectDownloadDir', async () => { + const result = await dialog.showOpenDialog(mainWindow, { + title: '选择下载保存目录', + properties: ['openDirectory'], + }) + if (result.canceled || result.filePaths.length === 0) return { ok: false } + return { ok: true, path: result.filePaths[0] } +}) + +// 创建远程目录 +ipcMain.handle('sftp:mkdir', async (event, { remotePath }) => { + return runSftpTask(async () => { + const sftp = await getSftp() + await new Promise((resolve, reject) => { + sftp.mkdir(remotePath, (err) => { + if (err) reject(err) + else resolve() + }) + }) + return { ok: true } + }) +}) + +// 删除远程文件或目录 +// 统一用 exec rm -rf 命令删除,不走 SFTP,避免 channel 问题 +ipcMain.handle('sftp:delete', async (event, { remotePath, isDirectory, recursive }) => { + // 路径用单引号包裹防止特殊字符注入 + const safePath = "'" + remotePath.replace(/'/g, "'\\''") + "'" + // rm -rf 对目录递归删除,对文件直接删除 + const result = await execSsh('rm -rf ' + safePath, 30000) + if (!result.ok) return { ok: false, message: result.message || result.stderr || '删除失败' } + if (result.code !== 0) return { ok: false, message: result.stderr || '删除失败' } + return { ok: true } +}) + +// ---------- IPC:交互式终端 (Shell with PTY) ---------- + +let activeShell = null // 当前活动的 SSH Shell 流 + +// 启动交互式 Shell +ipcMain.handle('shell:start', async (event, { cols, rows }) => { + try { + if (!activeConnection) { + return { ok: false, message: 'SSH 连接不存在' } + } + // 如果已有 Shell 在运行,先关闭 + if (activeShell) { + activeShell.end() + activeShell = null + } + + const shell = await new Promise((resolve, reject) => { + activeConnection.shell( + { + cols: cols || 80, + rows: rows || 24, + term: 'xterm-256color', + }, + (err, stream) => { + if (err) reject(err) + else resolve(stream) + } + ) + }) + + activeShell = shell + + // 接收服务器输出 → 转发给渲染进程 + shell.on('data', (data) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('shell:data', data) + } + }) + + shell.stderr.on('data', (data) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('shell:data', data) + } + }) + + shell.on('close', () => { + activeShell = null + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('shell:close') + } + }) + + return { ok: true } + } catch (err) { + return { ok: false, message: err.message } + } +}) + +// 渲染进程向 Shell 写入数据(键盘输入) +ipcMain.handle('shell:write', async (event, { data }) => { + if (activeShell && !activeShell.destroyed) { + activeShell.write(data) + return { ok: true } + } + return { ok: false, message: 'Shell 未连接' } +}) + +// 调整终端窗口大小 +ipcMain.handle('shell:resize', async (event, { cols, rows }) => { + if (activeShell && !activeShell.destroyed) { + activeShell.setWindow(rows, cols, 0, 0) + return { ok: true } + } + return { ok: false, message: 'Shell 未连接' } +}) + +// 关闭 Shell +ipcMain.handle('shell:close', async () => { + if (activeShell) { + activeShell.end() + activeShell = null + } + return { ok: true } +}) + +// ---------- IPC:Docker 专用 SSH 连接 ---------- +// Docker 连接在 ssh:connect 成功后自动建立,前端无需手动连接 +// 这里只提供 disconnect 和 exec + +ipcMain.handle('docker:disconnect', () => { + if (dockerConnection) { + dockerConnection.end() + dockerConnection = null + } + dockerSession = null + return { ok: true } +}) + +// Docker 专用串行队列(与主 execQueue 隔离) +const dockerQueue = [] +let dockerRunning = false + +function execDocker(cmd, timeoutMs = 15000) { + return new Promise((resolve) => { + dockerQueue.push({ cmd, timeoutMs, resolve }) + drainDockerQueue() + }) +} + +function drainDockerQueue() { + if (dockerRunning || dockerQueue.length === 0) return + dockerRunning = true + const { cmd, timeoutMs, resolve } = dockerQueue.shift() + runDockerExec(cmd, timeoutMs, (result) => { + resolve(result) + dockerRunning = false + drainDockerQueue() + }) +} + +function runDockerExec(cmd, timeoutMs, done) { + if (!dockerConnection) { + done({ ok: false, message: 'Docker SSH 连接不存在' }) + return + } + + let settled = false + let stdout = '' + let stderr = '' + let streamRef = null + + const timer = setTimeout(() => { + if (!settled) { + settled = true + // 销毁 stream 防止 channel 泄漏 + if (streamRef) { + try { streamRef.destroy() } catch (_) {} + } + done({ ok: false, message: '命令执行超时' }) + } + }, timeoutMs) + + dockerConnection.exec(cmd, (err, stream) => { + if (err) { + clearTimeout(timer) + if (!settled) { + settled = true + done({ ok: false, message: err.message }) + } + return + } + streamRef = stream + // 如果已经超时,立即销毁新拿到的 stream + if (settled) { + try { stream.destroy() } catch (_) {} + return + } + stream.on('data', (chunk) => (stdout += chunk.toString())) + stream.stderr.on('data', (chunk) => (stderr += chunk.toString())) + stream.on('close', (code) => { + clearTimeout(timer) + if (!settled) { + settled = true + done({ ok: true, code, stdout, stderr }) + } + }) + }) +} + +ipcMain.handle('docker:exec', (event, { cmd, timeout }) => execDocker(cmd, timeout)) + +// ---------- IPC:应用商店一键部署(后台任务模式) ---------- +// 使用 deployConnection(第 3 条 SSH 连接)在后台执行部署 +// 前端通过 docker:listTasks / deploy:taskUpdate 获取进度 +// 部署步骤:1.拉取镜像 2.创建目录 3.写入配置 4.创建容器 + +ipcMain.handle('docker:deployApp', async (event, { deploy, paramValues }) => { + // 创建任务对象 + const taskId = ++taskCounter + const task = { + id: taskId, + appName: deploy.containerName || 'unknown', + status: 'pending', + steps: [], + logs: [], + startTime: new Date().toISOString(), + error: null, + containerId: null, + } + deployTasks.push(task) + + // 后台执行部署(不 await,立即返回 taskId) + runDeployTask(task, deploy, paramValues || {}) + + return { ok: true, taskId } +}) + +// 后台部署任务执行函数 +async function runDeployTask(task, deploy, paramValues) { + // ---- 参数占位符替换 ---- + function tpl(str) { + if (!str || typeof str !== 'string') return str + let result = str + for (const [key, val] of Object.entries(paramValues)) { + result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), val) + } + return result + } + + // 特殊占位符处理 + function tplWithDerived(str) { + if (!str || typeof str !== 'string') return str + let result = tpl(str) + // Redis 密码行 + const redisPwd = paramValues['REDIS_PASSWORD'] || '' + result = result.replace(/\{\{REDIS_PASSWORD_LINE\}\}/g, redisPwd ? `requirepass ${redisPwd}` : '# requirepass (未设置密码)') + // Halo 数据库参数:H2 模式为空,MySQL 模式拼装环境变量 + const dbType = paramValues['DB_TYPE'] || 'h2' + if (dbType === 'mysql') { + const dbHost = paramValues['DB_HOST'] || '127.0.0.1' + const dbPort = paramValues['DB_PORT'] || '3306' + const dbName = paramValues['DB_NAME'] || 'halodb' + const dbUser = paramValues['DB_USER'] || 'root' + const dbPwd = paramValues['DB_PASSWORD'] || '' + const r2dbcUrl = `r2dbc:pool:mysql://${dbHost}:${dbPort}/${dbName}` + const dbArgs = [ + `-e spring.r2dbc.url=${r2dbcUrl}`, + `-e spring.r2dbc.username=${dbUser}`, + `-e spring.r2dbc.password=${dbPwd}`, + `-e spring.sql.init.platform=mysql`, + ].join(' ') + result = result.replace(/\{\{HALO_DB_ARGS\}\}/g, dbArgs) + } else { + result = result.replace(/\{\{HALO_DB_ARGS\}\}/g, '') + } + return result + } + + task.status = 'running' + sendTaskUpdate(task) + + try { + // ---- Step 1: 拉取镜像 ---- + const imageName = tpl(deploy.image) + setTaskStep(task, 'pull', `正在拉取镜像 ${imageName}...`) + + // 拉取镜像可能耗时较长,超时设为 5 分钟 + const pullResult = await execDeploy(`docker pull ${imageName}`, 300000) + if (!pullResult.ok || (pullResult.code !== 0 && !pullResult.stdout.includes('Status: Image is up to date') && !pullResult.stdout.includes('Already exists'))) { + const errMsg = pullResult.stderr || pullResult.message || '未知错误' + setTaskStep(task, 'pull-error', `镜像拉取失败: ${errMsg}`) + task.status = 'error' + task.error = `镜像拉取失败: ${errMsg}` + sendTaskUpdate(task) + return + } + setTaskStep(task, 'pull-done', `镜像 ${imageName} 准备就绪`) + + // ---- Step 2: 创建宿主机目录 ---- + if (deploy.mkdirs && deploy.mkdirs.length > 0) { + for (const dir of deploy.mkdirs) { + const dirPath = tpl(dir) + setTaskStep(task, 'mkdir', `创建目录 ${dirPath}`) + const mkdirResult = await execDeploy(`mkdir -p '${dirPath}'`, 10000) + if (!mkdirResult.ok || mkdirResult.code !== 0) { + setTaskStep(task, 'mkdir-error', `创建目录失败: ${dirPath}`) + task.status = 'error' + task.error = `创建目录失败: ${dirPath}` + sendTaskUpdate(task) + return + } + } + setTaskStep(task, 'mkdir-done', '目录创建完成') + } + + // ---- Step 3: 写入配置文件 ---- + if (deploy.writeFiles && deploy.writeFiles.length > 0) { + for (const file of deploy.writeFiles) { + const filePath = tpl(file.path) + const content = tplWithDerived(file.content) + setTaskStep(task, 'writefile', `写入配置文件 ${filePath}`) + // 用 heredoc 方式写入文件,避免转义问题 + const writeCmd = `cat > '${filePath}' << 'APPSTORE_EOF'\n${content}\nAPPSTORE_EOF` + const writeResult = await execDeploy(writeCmd, 10000) + if (!writeResult.ok || writeResult.code !== 0) { + setTaskStep(task, 'writefile-error', `写入配置文件失败: ${filePath}`) + task.status = 'error' + task.error = `写入配置文件失败: ${filePath}` + sendTaskUpdate(task) + return + } + } + setTaskStep(task, 'writefile-done', '配置文件写入完成') + } + + // ---- Step 4: 构建 docker run 命令 ---- + const parts = ['docker', 'run', '-d'] + + const containerName = tpl(deploy.containerName) + if (containerName) parts.push('--name', containerName) + + if (deploy.restart) parts.push('--restart', deploy.restart) + if (deploy.hostname) parts.push('--hostname', deploy.hostname) + if (deploy.workdir) parts.push('-w', deploy.workdir) + if (deploy.network) parts.push('--network', deploy.network) + + // 端口映射 + for (const port of (deploy.ports || [])) { + const hostPort = tpl(port.host) + if (!port.container) continue + let p = hostPort ? `${hostPort}:` : '' + p += port.container + if (port.protocol && port.protocol !== 'tcp') p += '/' + port.protocol + parts.push('-p', p) + } + + // 挂载卷 + for (const vol of (deploy.volumes || [])) { + if (!vol.container) continue + const hostPath = tpl(vol.host) + let v = hostPath ? `${hostPath}:` : '' + v += vol.container + if (vol.mode && vol.mode !== 'rw') v += ':' + vol.mode + parts.push('-v', v) + } + + // 环境变量(值含空格时用引号包裹,防止 docker 解析错误) + for (const env of (deploy.env || [])) { + if (!env.key) continue + const envValue = tpl(env.value) + const pair = `${env.key}=${envValue}` + if (envValue.includes(' ')) { + parts.push('-e', `'${pair}'`) + } else { + parts.push('-e', pair) + } + } + + // 额外参数(如 Halo 数据库配置,在镜像名之前插入) + // extraArgs 中的值(DB URL、用户名、密码等)不含空格,可直接 split + if (deploy.extraArgs) { + const extraArgs = tplWithDerived(deploy.extraArgs) + if (extraArgs && extraArgs.trim()) { + parts.push(...extraArgs.trim().split(/\s+/).filter(Boolean)) + } + } + + // 启动命令 + const command = tpl(deploy.command) + parts.push(imageName) + if (command) { + parts.push(...command.split(/\s+/).filter(Boolean)) + } + + const runCmd = parts.join(' ') + setTaskStep(task, 'run', `正在创建容器...`) + addTaskLog(task, `执行命令: ${runCmd}`) + + const runResult = await execDeploy(runCmd, 60000) + + if (!runResult.ok || runResult.code !== 0) { + const errMsg = runResult.stderr || runResult.message || '创建容器失败' + setTaskStep(task, 'run-error', `容器创建失败: ${errMsg}`) + task.status = 'error' + task.error = `容器创建失败: ${errMsg}` + sendTaskUpdate(task) + return + } + + const containerId = (runResult.stdout || '').trim().split('\n')[0] + task.containerId = containerId + setTaskStep(task, 'run-done', `容器创建成功!ID: ${containerId.slice(0, 12)}`) + setTaskStep(task, 'done', '部署完成') + task.status = 'done' + sendTaskUpdate(task) + + } catch (err) { + setTaskStep(task, 'run-error', `部署异常: ${err.message}`) + task.status = 'error' + task.error = `部署异常: ${err.message}` + sendTaskUpdate(task) + } +} + +// ---------- IPC:检查 SSH 连接数 ---------- +// 检查服务器当前的 SSH 连接数,用于部署前警告 +ipcMain.handle('docker:checkConnections', async () => { + // 用 docker 连接执行命令检查当前 SSH 连接数 + const r = await execDocker("ss -tn state established '( sport = :22 )' 2>/dev/null | tail -n +2 | wc -l || netstat -tn 2>/dev/null | grep ':22 ' | grep ESTABLISHED | wc -l", 10000) + if (!r.ok) { + return { ok: false, message: r.message || '检查失败' } + } + const count = parseInt((r.stdout || '').trim()) || 0 + return { ok: true, count } +}) + +// ---------- IPC:列出所有部署任务 ---------- +ipcMain.handle('docker:listTasks', async () => { + return { ok: true, tasks: deployTasks.map((t) => ({ + id: t.id, + appName: t.appName, + status: t.status, + steps: t.steps, + logs: t.logs, + startTime: t.startTime, + error: t.error, + containerId: t.containerId, + }))} +}) + +// ---------- 在线应用商店 ---------- +// API 地址持久化在 Electron userData 目录 +const appstoreConfigPath = path.join(app.getPath('userData'), 'appstore-config.json') +// 在线模板缓存路径 +const appstoreCachePath = path.join(app.getPath('userData'), 'appstore-cache.json') + +// 默认 API 地址(可被用户覆盖) +let appstoreApiUrl = 'http://114.66.55.63:3001/api/apps' +// API 鉴权 token +let appstoreApiToken = 'akaxedx' + +// 从配置文件加载 API 地址和 token +try { + const configRaw = fs.readFileSync(appstoreConfigPath, 'utf-8') + const config = JSON.parse(configRaw) + if (config.apiUrl) appstoreApiUrl = config.apiUrl + if (config.apiToken !== undefined) appstoreApiToken = config.apiToken +} catch (_) { + // 首次运行,使用默认值 +} + +/** + * 发起 HTTP GET 请求(支持 http / https),附带 Authorization token + */ +function httpGet(urlStr, timeoutMs = 10000) { + return new Promise((resolve) => { + const lib = urlStr.startsWith('https') ? https : http + const headers = {} + if (appstoreApiToken) { + headers['Authorization'] = `Bearer ${appstoreApiToken}` + } + const req = lib.get(urlStr, { timeout: timeoutMs, headers }, (res) => { + let data = '' + res.on('data', (chunk) => { data += chunk }) + res.on('end', () => { + resolve({ ok: res.statusCode === 200, statusCode: res.statusCode, body: data }) + }) + }) + req.on('error', (err) => { + resolve({ ok: false, error: err.message }) + }) + req.on('timeout', () => { + req.destroy() + resolve({ ok: false, error: '请求超时' }) + }) + }) +} + +// ---------- IPC:获取/设置 API 地址 ---------- +ipcMain.handle('appstore:getApiUrl', async () => { + return { ok: true, apiUrl: appstoreApiUrl } +}) + +ipcMain.handle('appstore:setApiUrl', async (_e, url) => { + appstoreApiUrl = url + try { + fs.writeFileSync(appstoreConfigPath, JSON.stringify({ apiUrl: url }), 'utf-8') + return { ok: true } + } catch (err) { + return { ok: false, error: err.message } + } +}) + +// ---------- IPC:同步在线模板 ---------- +// 从远程 API 获取模板列表,写入本地缓存文件 +ipcMain.handle('appstore:sync', async () => { + const res = await httpGet(appstoreApiUrl, 15000) + if (!res.ok) { + return { ok: false, error: res.error || `HTTP ${res.statusCode}` } + } + try { + const parsed = JSON.parse(res.body) + if (!parsed.ok || !Array.isArray(parsed.data)) { + return { ok: false, error: parsed.error || '返回数据格式错误' } + } + // 转换数据库格式 → 前端 deploy 格式 + const templates = parsed.data.map(mapDbTemplateToFront) + // 写入缓存 + fs.writeFileSync(appstoreCachePath, JSON.stringify({ + templates, + syncedAt: new Date().toISOString(), + }), 'utf-8') + return { ok: true, templates, syncedAt: new Date().toISOString() } + } catch (err) { + return { ok: false, error: 'JSON 解析失败: ' + err.message } + } +}) + +// ---------- IPC:获取本地缓存模板 ---------- +ipcMain.handle('appstore:getLocal', async () => { + try { + const raw = fs.readFileSync(appstoreCachePath, 'utf-8') + const cache = JSON.parse(raw) + return { ok: true, templates: cache.templates || [], syncedAt: cache.syncedAt || null } + } catch (_) { + return { ok: true, templates: [], syncedAt: null } + } +}) + +// ---------- IPC:检查客户端版本更新 ---------- +// 客户端当前版本号 +const CLIENT_VERSION = 'v0.0.1' + +ipcMain.handle('app:checkUpdate', async () => { + // 版本检查接口地址(固定,基于 appstore API 同一服务器) + const versionApiUrl = appstoreApiUrl.replace('/api/apps', '/api/version') + const res = await httpGet(versionApiUrl, 10000) + if (!res.ok) { + return { ok: false, error: res.error || `HTTP ${res.statusCode}` } + } + try { + const parsed = JSON.parse(res.body) + if (!parsed.ok || !parsed.data) { + return { ok: false, error: parsed.error || '返回数据格式错误' } + } + const latest = parsed.data + const hasUpdate = latest.version !== CLIENT_VERSION + return { + ok: true, + currentVersion: CLIENT_VERSION, + latestVersion: latest.version, + downloadUrl: latest.downloadUrl, + releaseNotes: latest.releaseNotes || '', + hasUpdate, + } + } catch (err) { + return { ok: false, error: 'JSON 解析失败: ' + err.message } + } +}) + +// ---------- IPC:获取当前版本号 ---------- +ipcMain.handle('app:getVersion', async () => { + return { ok: true, version: CLIENT_VERSION } +}) + +// ---------- IPC:打开下载地址(系统默认浏览器) ---------- +ipcMain.handle('app:openUrl', async (_e, url) => { + try { + shell.openExternal(url) + return { ok: true } + } catch (err) { + return { ok: false, error: err.message } + } +}) + +// ---------- IPC:检查已安装应用 ---------- +// 通过 docker 连接执行 docker ps -a 获取已有容器列表 +// 返回 { ok, installed: ['redis', 'mysql', ...] } +ipcMain.handle('appstore:checkInstalled', async () => { + if (!dockerConnection) { + return { ok: false, error: 'Docker 未连接' } + } + const r = await execDocker('docker ps -a --format "{{.Names}}" 2>/dev/null', 10000) + if (!r.ok) { + return { ok: false, error: r.message || '检查失败' } + } + const names = (r.stdout || '').trim().split('\n').map((s) => s.trim()).filter(Boolean) + return { ok: true, installed: names } +}) + +/** + * 将数据库行转换为前端 deploy 格式 + * 数据库字段名 → 前端字段名映射 + */ +function mapDbTemplateToFront(row) { + const tpl = { + id: row.app_key, + name: row.name, + icon: row.icon || '', + tagline: row.tagline || '', + image: row.image, + description: row.description || '', + category: row.category || '', + deploy: { + image: row.image, + containerName: row.container_name, + ports: (row.ports || []).map((p) => ({ + host: p.host_port, + container: p.container_port, + protocol: p.protocol || 'tcp', + })), + volumes: (row.volumes || []).map((v) => ({ + host: v.host_path, + container: v.container_path, + mode: v.mode || 'rw', + })), + env: (row.env || []).map((e) => ({ + key: e.env_key, + value: e.env_value || '', + })), + network: row.network || '', + restart: row.restart || 'always', + hostname: row.hostname || '', + workdir: row.workdir || '', + command: row.command || '', + mkdirs: (row.mkdirs || []).map((m) => m.dir_path), + writeFiles: (row.writeFiles || []).map((f) => ({ + path: f.file_path, + content: f.file_content, + })), + params: (row.params || []).map((p) => { + const param = { + key: p.param_key, + label: p.label, + type: p.type || 'text', + placeholder: p.placeholder || '', + default: p.default_value || '', + required: !!p.required, + } + // select 类型:从 DB options 列读取下拉选项 (JSON 字符串) + if (param.type === 'select' && p.options) { + try { + param.options = JSON.parse(p.options) + } catch { + param.options = [] + } + } + // 条件渲染:从 DB show_when 列读取,格式 "param_key:value" + if (p.show_when) { + const [whenParam, whenValue] = p.show_when.split(':') + if (whenParam && whenValue !== undefined) { + param.showWhen = { param: whenParam, equals: whenValue } + } + } + return param + }), + // 额外 docker run 参数(含 {{}} 占位符,由 tplWithDerived 动态解析) + extraArgs: row.extra_args || undefined, + }, + } + // 清理 undefined 字段 + if (!tpl.deploy.extraArgs) delete tpl.deploy.extraArgs + return tpl +} + // ---------- 应用生命周期 ---------- app.whenReady().then(() => { createWindow() diff --git a/src/preload/index.js b/src/preload/index.js index cdcdc56..95a17a4 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -6,8 +6,85 @@ contextBridge.exposeInMainWorld('panel', { disconnect: () => ipcRenderer.invoke('ssh:disconnect'), getSession: () => ipcRenderer.invoke('ssh:session'), exec: (cmd, timeout) => ipcRenderer.invoke('ssh:exec', { cmd, timeout }), + onDisconnected: (callback) => { + const handler = (_event, data) => callback(data) + ipcRenderer.on('ssh:disconnected', handler) + return () => ipcRenderer.removeListener('ssh:disconnected', handler) + }, // 凭据(密码加密保存在本机) saveCredential: (cred) => ipcRenderer.invoke('cred:save', cred), listCredentials: () => ipcRenderer.invoke('cred:list'), deleteCredential: (key) => ipcRenderer.invoke('cred:delete', key), + // SFTP 文件管理 + sftpList: (remotePath) => ipcRenderer.invoke('sftp:list', { remotePath }), + sftpUpload: (localPath, remotePath) => ipcRenderer.invoke('sftp:upload', { localPath, remotePath }), + sftpDownload: (remotePath, localPath) => ipcRenderer.invoke('sftp:download', { remotePath, localPath }), + sftpSelectLocalFile: () => ipcRenderer.invoke('sftp:selectLocalFile'), + sftpSelectSavePath: (defaultName) => ipcRenderer.invoke('sftp:selectSavePath', { defaultName }), + sftpSelectDownloadDir: () => ipcRenderer.invoke('sftp:selectDownloadDir'), + sftpMkdir: (remotePath) => ipcRenderer.invoke('sftp:mkdir', { remotePath }), + sftpDelete: (remotePath, isDirectory, recursive) => ipcRenderer.invoke('sftp:delete', { remotePath, isDirectory, recursive }), + // SFTP 进度监听(渲染进程注册回调,主进程通过 webContents.send 推送进度) + onSftpProgress: (callback) => { + const handler = (_event, data) => callback(data) + ipcRenderer.on('sftp:progress', handler) + return () => ipcRenderer.removeListener('sftp:progress', handler) + }, + // 交互式终端 (Shell with PTY) + shellStart: (cols, rows) => ipcRenderer.invoke('shell:start', { cols, rows }), + shellWrite: (data) => ipcRenderer.invoke('shell:write', { data }), + shellResize: (cols, rows) => ipcRenderer.invoke('shell:resize', { cols, rows }), + shellClose: () => ipcRenderer.invoke('shell:close'), + onShellData: (callback) => { + const handler = (_event, data) => callback(data) + ipcRenderer.on('shell:data', handler) + return () => ipcRenderer.removeListener('shell:data', handler) + }, + onShellClose: (callback) => { + const handler = () => callback() + ipcRenderer.on('shell:close', handler) + return () => ipcRenderer.removeListener('shell:close', handler) + }, + // Docker 专用 SSH 连接(主 SSH 连接成功后自动建立) + dockerDisconnect: () => ipcRenderer.invoke('docker:disconnect'), + dockerExec: (cmd, timeout) => ipcRenderer.invoke('docker:exec', { cmd, timeout }), + dockerDeployApp: (deploy, paramValues) => + ipcRenderer.invoke('docker:deployApp', { deploy, paramValues }), + // 部署前检查 SSH 连接数 + dockerCheckConnections: () => ipcRenderer.invoke('docker:checkConnections'), + // 列出所有部署任务 + dockerListTasks: () => ipcRenderer.invoke('docker:listTasks'), + // 监听部署任务更新(后台任务模式) + onDeployTaskUpdate: (callback) => { + const handler = (_event, data) => callback(data) + ipcRenderer.on('deploy:taskUpdate', handler) + return () => ipcRenderer.removeListener('deploy:taskUpdate', handler) + }, + onDockerReady: (callback) => { + const handler = () => callback() + ipcRenderer.on('docker:ready', handler) + return () => ipcRenderer.removeListener('docker:ready', handler) + }, + onDockerDisconnected: (callback) => { + const handler = (_event, data) => callback(data) + ipcRenderer.on('docker:disconnected', handler) + return () => ipcRenderer.removeListener('docker:disconnected', handler) + }, + // ───── 在线应用商店 ───── + // 从远程 API 同步模板到本地缓存 + appstoreSync: () => ipcRenderer.invoke('appstore:sync'), + // 获取本地缓存的在线模板 + appstoreGetLocal: () => ipcRenderer.invoke('appstore:getLocal'), + // 获取/设置 API 地址 + appstoreGetApiUrl: () => ipcRenderer.invoke('appstore:getApiUrl'), + appstoreSetApiUrl: (url) => ipcRenderer.invoke('appstore:setApiUrl', url), + // 检查服务器上已安装的应用(docker ps -a 容器名列表) + appstoreCheckInstalled: () => ipcRenderer.invoke('appstore:checkInstalled'), + // ───── 客户端版本管理 ───── + // 检查是否有新版本 + appCheckUpdate: () => ipcRenderer.invoke('app:checkUpdate'), + // 获取当前版本号 + appGetVersion: () => ipcRenderer.invoke('app:getVersion'), + // 用系统浏览器打开 URL + appOpenUrl: (url) => ipcRenderer.invoke('app:openUrl', url), }) diff --git a/src/renderer/src/App.jsx b/src/renderer/src/App.jsx index 86b2baa..b59eef7 100644 --- a/src/renderer/src/App.jsx +++ b/src/renderer/src/App.jsx @@ -1,21 +1,46 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import Login from './pages/Login' import Panel from './pages/Panel' export default function App() { const [session, setSession] = useState(null) + const [dockerReady, setDockerReady] = useState(false) // 登录成功后切换到面板页 const handleConnected = (session) => setSession(session) // 断开连接,回到登录页 const handleLogout = async () => { + await window.panel.dockerDisconnect() await window.panel.disconnect() + setDockerReady(false) setSession(null) } + // 监听主 SSH 连接断开 + useEffect(() => { + if (!session) return + const cleanup = window.panel.onDisconnected((data) => { + alert(`SSH 连接已断开${data?.message ? ':' + data.message : ''}`) + setDockerReady(false) + setSession(null) + }) + return cleanup + }, [session]) + + // 监听 Docker 连接就绪 / 断开 + useEffect(() => { + if (!session) return + const readyCleanup = window.panel.onDockerReady(() => setDockerReady(true)) + const discCleanup = window.panel.onDockerDisconnected(() => setDockerReady(false)) + return () => { + readyCleanup() + discCleanup() + } + }, [session]) + return session ? ( - + ) : ( ) diff --git a/src/renderer/src/pages/Login.jsx b/src/renderer/src/pages/Login.jsx index 5ddea94..a9da9ff 100644 --- a/src/renderer/src/pages/Login.jsx +++ b/src/renderer/src/pages/Login.jsx @@ -11,9 +11,20 @@ export default function Login({ onConnected }) { const [status, setStatus] = useState('') const [saved, setSaved] = useState([]) - // 加载已保存的服务器列表 + // 加载已保存的服务器列表,并自动填充最近一次的登录信息 useEffect(() => { - window.panel.listCredentials().then(setSaved) + window.panel.listCredentials().then((list) => { + setSaved(list) + // 如果有保存的服务器,自动填充第一条(最近保存的) + if (list.length > 0) { + const last = list[0] + setHost(last.host) + setPort(String(last.port)) + setUsername(last.username) + setPassword(last.password) + setRemember(true) + } + }) }, []) // 点击已保存的服务器,自动填充表单 @@ -60,7 +71,7 @@ export default function Login({ onConnected }) { return (
-

SSH Panel

+

WFJ Panel

连接到你的服务器

diff --git a/src/renderer/src/pages/Panel.css b/src/renderer/src/pages/Panel.css index b219072..afe98ae 100644 --- a/src/renderer/src/pages/Panel.css +++ b/src/renderer/src/pages/Panel.css @@ -78,3 +78,124 @@ main .hint { font-size: 15px; margin-top: 140px; } + +/* ---- 更新按钮 ---- */ +.btn-update { + background: rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(255, 255, 255, 0.15); +} + +.btn-update:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.2); +} + +.btn-update:disabled { + opacity: 0.6; + cursor: default; +} + +/* ---- 更新弹窗 ---- */ +.update-dialog { + background: var(--card-bg); + border-radius: var(--radius-card); + box-shadow: var(--shadow-overlay); + padding: 28px 32px; + max-width: 480px; + width: 90%; +} + +.update-dialog-header h3 { + font-size: 17px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 20px; +} + +.update-dialog-body { + margin-bottom: 20px; +} + +.update-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + text-align: center; +} + +.update-icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + border-radius: 50%; +} + +.update-icon-error { + background: rgba(239, 68, 68, 0.1); + color: var(--status-critical); +} + +.update-icon-available { + background: rgba(91, 110, 245, 0.1); + color: var(--brand); +} + +.update-icon-latest { + background: rgba(34, 197, 94, 0.1); + color: var(--status-good); +} + +.update-message { + font-size: 16px; + font-weight: 600; + color: var(--ink-strong); +} + +.update-detail { + font-size: 13px; + color: var(--ink-muted); +} + +.update-version-info { + display: flex; + align-items: center; + gap: 10px; + font-size: 15px; +} + +.update-version-old { + color: var(--ink-muted); + text-decoration: line-through; +} + +.update-version-new { + color: var(--brand); + font-weight: 700; +} + +.update-notes { + font-family: inherit; + font-size: 13px; + color: var(--ink-secondary); + background: var(--surface-dim); + border: 1px solid var(--hairline); + border-radius: var(--radius-sm); + padding: 12px 16px; + text-align: left; + white-space: pre-wrap; + word-break: break-word; + line-height: 1.6; + max-height: 200px; + overflow: auto; + width: 100%; + margin: 0; +} + +.update-dialog-footer { + display: flex; + gap: 12px; + justify-content: flex-end; +} diff --git a/src/renderer/src/pages/Panel.jsx b/src/renderer/src/pages/Panel.jsx index 6eaa9f0..a5e00ef 100644 --- a/src/renderer/src/pages/Panel.jsx +++ b/src/renderer/src/pages/Panel.jsx @@ -1,16 +1,53 @@ -import { useState } from 'react' +import { useState, useEffect, useCallback } from 'react' import Overview from './overview/Overview' +import FileManager from './files/FileManager' +import ContainerPanel from './container/ContainerPanel' +import TerminalPanel from './terminal/TerminalPanel' +import Firewall from './firewall/Firewall' import './Panel.css' -const NAV_ITEMS = ['概览', '容器', '终端'] +const NAV_ITEMS = ['概览', '文件', '容器', '终端', '防火墙'] -export default function Panel({ session, onLogout }) { +export default function Panel({ session, dockerReady, onLogout }) { const [active, setActive] = useState('概览') + const [currentVersion, setCurrentVersion] = useState('v0.0.1') + const [updateInfo, setUpdateInfo] = useState(null) // null | { checking | result } + const [showUpdateDialog, setShowUpdateDialog] = useState(false) + + // 获取当前版本号 + useEffect(() => { + window.panel.appGetVersion().then((r) => { + if (r.ok) setCurrentVersion(r.version) + }) + }, []) + + // 检查更新 + const handleCheckUpdate = useCallback(async () => { + setUpdateInfo({ checking: true }) + try { + const r = await window.panel.appCheckUpdate() + if (r.ok) { + setUpdateInfo({ result: r }) + setShowUpdateDialog(true) + } else { + setUpdateInfo({ result: r }) + setShowUpdateDialog(true) + } + } catch (err) { + setUpdateInfo({ result: { ok: false, error: err.message } }) + setShowUpdateDialog(true) + } + }, []) + + // 打开下载页 + const handleDownload = useCallback((url) => { + window.panel.appOpenUrl(url) + }, []) return ( <>
- SSH Panel + WFJ Panel
{active === '概览' && } - {active === '容器' &&

容器功能待开发

} - {active === '终端' &&

终端功能待开发

} + {active === '文件' && } + {active === '容器' && } + {active === '终端' && } + {active === '防火墙' && }
+ + {/* 更新检查弹窗 */} + {showUpdateDialog && updateInfo?.result && ( +
setShowUpdateDialog(false)}> +
e.stopPropagation()}> +
+

版本更新

+
+
+ {!updateInfo.result.ok ? ( +
+
+ + + + + +
+

检查更新失败

+ {updateInfo.result.error || '未知错误'} +
+ ) : updateInfo.result.hasUpdate ? ( +
+
+ + + + +
+

发现新版本!

+
+ {updateInfo.result.currentVersion} + + + + + {updateInfo.result.latestVersion} +
+ {updateInfo.result.releaseNotes && ( +
{updateInfo.result.releaseNotes}
+ )} +
+ ) : ( +
+
+ + + + +
+

已是最新版本

+ 当前版本: {updateInfo.result.currentVersion} +
+ )} +
+
+ + {updateInfo.result.ok && updateInfo.result.hasUpdate && ( + + )} +
+
+
+ )} ) } diff --git a/src/renderer/src/pages/container/AppStore.css b/src/renderer/src/pages/container/AppStore.css new file mode 100644 index 0000000..3426d4d --- /dev/null +++ b/src/renderer/src/pages/container/AppStore.css @@ -0,0 +1,855 @@ +/* ============================================================ + 应用商店 + ============================================================ */ +.appstore-list { + display: flex; + flex-direction: column; + gap: 20px; +} + +.appstore-header h3 { + font-size: 17px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 6px; +} + +.appstore-header p { + font-size: 13px; + color: var(--ink-muted); +} + +/* ---- 应用卡片网格 ---- */ +.appstore-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 16px; +} + +.app-card { + display: flex; + align-items: center; + gap: 16px; + padding: 20px; + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + cursor: pointer; + transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease; +} + +.app-card:hover { + border-color: var(--brand); + box-shadow: var(--shadow-card-hover); + transform: translateY(-2px); +} + +/* ---- 应用图标 ---- */ +.app-icon-box, +.app-icon-large { + display: flex; + align-items: center; + justify-content: center; + border-radius: 14px; + flex-shrink: 0; +} + +.app-icon-box { + width: 56px; + height: 56px; +} + +.app-icon-large { + width: 64px; + height: 64px; +} + +.app-icon-redis { + background: rgba(220, 50, 47, 0.08); + color: #dc352f; +} + +.app-icon-mysql { + background: rgba(0, 110, 160, 0.08); + color: #006e9f; +} + +.app-icon-halo { + background: rgba(99, 102, 241, 0.08); + color: #6366f1; +} + +/* ---- 应用卡片信息 ---- */ +.app-card-info { + flex: 1; + min-width: 0; +} + +.app-card-name { + font-size: 16px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 4px; +} + +.app-card-tagline { + font-size: 13px; + color: var(--ink-secondary); + margin-bottom: 8px; +} + +.app-card-meta { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.app-card-tag { + font-size: 11px; + font-weight: 500; + padding: 2px 8px; + background: var(--surface-dim); + border-radius: 10px; + color: var(--ink-muted); +} + +.app-card-image { + font-size: 11px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--brand); +} + +/* ============================================================ + 应用详情页 + ============================================================ */ +.appstore-detail { + display: flex; + flex-direction: column; + gap: 20px; +} + +.appstore-back { + align-self: flex-start; +} + +/* ---- 详情头部 ---- */ +.app-detail-header { + display: flex; + align-items: center; + gap: 20px; + padding: 20px; + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); +} + +.app-detail-info h2 { + font-size: 20px; + font-weight: 700; + color: var(--ink-strong); + margin-bottom: 4px; +} + +.app-tagline { + font-size: 14px; + color: var(--ink-secondary); + margin-bottom: 8px; +} + +.app-category { + font-size: 12px; + font-weight: 500; + padding: 3px 10px; + background: var(--brand-soft); + color: var(--brand); + border-radius: 10px; +} + +.app-description { + font-size: 14px; + color: var(--ink-secondary); + line-height: 1.7; + padding: 0 4px; +} + +/* ---- 配置预览 ---- */ +.app-config-preview { + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + padding: 16px 20px; +} + +.app-config-preview h4 { + font-size: 14px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 12px; +} + +.config-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 10px; + margin-bottom: 16px; +} + +.config-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; +} + +.config-key { + font-size: 12px; + color: var(--ink-muted); + font-weight: 500; + flex-shrink: 0; +} + +.config-val { + font-size: 13px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink-strong); + word-break: break-all; +} + +.config-section { + margin-bottom: 12px; +} + +.config-section:last-child { + margin-bottom: 0; +} + +.config-section-title { + display: block; + font-size: 12px; + font-weight: 600; + color: var(--ink-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 6px; +} + +.config-rows { + display: flex; + flex-direction: column; + gap: 4px; +} + +.config-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + background: var(--surface-dim); + border-radius: var(--radius-sm); + font-size: 12px; + font-family: 'Consolas', 'Monaco', monospace; +} + +.row-host { + color: var(--ink-strong); + font-weight: 500; + max-width: 260px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.row-arrow { + color: var(--ink-muted); + font-size: 11px; + flex-shrink: 0; +} + +.row-container { + color: var(--brand); + font-weight: 500; +} + +.row-proto { + font-size: 11px; + color: var(--ink-muted); + background: var(--card-bg); + padding: 1px 6px; + border-radius: 3px; + flex-shrink: 0; +} + +.row-command { + color: var(--ink-strong); + word-break: break-all; + line-height: 1.5; +} + +/* ---- 参数表单 ---- */ +.app-params-form { + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + padding: 16px 20px; +} + +.app-params-form h4 { + font-size: 14px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 12px; +} + +.param-row { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 10px; +} + +.param-row:last-child { + margin-bottom: 0; +} + +.param-label { + font-size: 13px; + font-weight: 500; + color: var(--ink-secondary); + width: 100px; + flex-shrink: 0; + text-align: right; +} + +.param-required { + color: var(--status-critical); + margin-left: 2px; +} + +.param-input { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink); + background: var(--card-bg); + box-sizing: border-box; +} + +.param-input:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; +} + +/* ---- 部署按钮区 ---- */ +.app-deploy-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + padding-bottom: 10px; +} + +/* ============================================================ + 部署进度弹窗 + ============================================================ */ +.deploy-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1300; +} + +.deploy-dialog { + background: var(--card-bg); + border-radius: var(--radius-card); + box-shadow: var(--shadow-overlay); + width: 90%; + max-width: 560px; + max-height: 80vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.deploy-header { + padding: 16px 24px; + border-bottom: 1px solid var(--hairline); + flex-shrink: 0; +} + +.deploy-header h3 { + font-size: 16px; + font-weight: 600; + color: var(--ink-strong); + margin: 0; +} + +.deploy-body { + padding: 20px 24px; + overflow-y: auto; +} + +/* ---- 步骤指示器 ---- */ +.deploy-steps { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; + padding: 0 8px; +} + +.deploy-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + flex: 1; +} + +.step-indicator { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid var(--hairline-strong); + background: var(--card-bg); + color: var(--ink-muted); + transition: all 0.3s ease; +} + +.deploy-step.done .step-indicator { + background: var(--status-good); + border-color: var(--status-good); + color: #fff; +} + +.deploy-step.current .step-indicator { + border-color: var(--brand); + background: var(--brand-soft); +} + +.deploy-step.error .step-indicator { + background: var(--status-critical); + border-color: var(--status-critical); + color: #fff; +} + +.step-spinner { + width: 14px; + height: 14px; + border: 2px solid var(--brand-soft); + border-top-color: var(--brand); + border-radius: 50%; + animation: step-spin 0.8s linear infinite; +} + +@keyframes step-spin { + to { transform: rotate(360deg); } +} + +.step-pending { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--hairline-strong); +} + +.step-label { + font-size: 12px; + color: var(--ink-secondary); + font-weight: 500; +} + +.deploy-step.done .step-label { + color: var(--status-good); +} + +.deploy-step.current .step-label { + color: var(--brand); +} + +.deploy-step.error .step-label { + color: var(--status-critical); +} + +/* ---- 日志区 ---- */ +.deploy-log { + background: #1e1e2e; + border-radius: var(--radius-sm); + padding: 12px 16px; + max-height: 200px; + overflow-y: auto; + min-height: 80px; +} + +.deploy-log-line { + display: flex; + gap: 10px; + font-size: 12px; + font-family: 'Consolas', 'Monaco', monospace; + color: #cdd6f4; + line-height: 1.6; + margin-bottom: 2px; +} + +.deploy-log-line.log-error .log-text { + color: #f38ba8; +} + +.log-time { + color: #585b70; + flex-shrink: 0; +} + +.log-text { + word-break: break-all; +} + +/* ---- 底部 ---- */ +.deploy-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 16px; +} + +/* ============================================================ + SSH 连接数警告弹窗 + ============================================================ */ +.conn-warning-body { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 12px 0; + text-align: center; +} + +.conn-warning-icon { + color: var(--status-warning, #ed8936); +} + +.conn-warning-text { + font-size: 14px; + color: var(--ink-secondary); + line-height: 1.8; + margin: 0; +} + +.conn-warning-text strong { + color: var(--status-critical); + font-size: 18px; +} + +/* ============================================================ + 部署已提交提示弹窗 + ============================================================ */ +.deploy-submitted { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 16px 0; + text-align: center; +} + +.deploy-submitted-icon { + color: var(--status-good); +} + +.deploy-submitted p { + font-size: 15px; + color: var(--ink-strong); + margin: 0; +} + +.deploy-submitted span { + font-size: 13px; + color: var(--ink-muted); +} + +.deploy-submitted-error { + color: var(--status-critical); + font-size: 13px; + margin-top: 4px; +} + +.deploy-submitted-error-icon { + color: var(--status-critical); +} + +/* ============================================================ + 在线商店:顶部操作栏 + ============================================================ */ +.appstore-header-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.appstore-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.sync-msg { + font-size: 12px; + padding: 4px 10px; + border-radius: 12px; + white-space: nowrap; +} + +.sync-msg-ok { + color: var(--status-good); + background: rgba(34, 197, 94, 0.08); +} + +.sync-msg-error { + color: var(--status-critical); + background: rgba(239, 68, 68, 0.08); +} + +.sync-info { + color: var(--ink-muted); + background: var(--surface-dim); +} + +/* ============================================================ + 已安装应用标注 + ============================================================ */ +.app-card-installed { + opacity: 0.75; + border-color: var(--status-good); +} + +.app-card-installed:hover { + opacity: 1; +} + +.app-card-installed-badge { + display: inline-block; + font-size: 10px; + font-weight: 600; + padding: 1px 7px; + border-radius: 8px; + background: rgba(34, 197, 94, 0.12); + color: var(--status-good); + margin-left: 6px; + vertical-align: middle; +} + +.app-installed-badge { + display: inline-block; + font-size: 12px; + font-weight: 600; + padding: 2px 10px; + border-radius: 10px; + background: rgba(34, 197, 94, 0.12); + color: var(--status-good); + margin-left: 8px; + vertical-align: middle; +} + +/* 来源标签(内置/在线) */ +.app-source-tag { + font-size: 11px; + font-weight: 500; + padding: 2px 8px; + border-radius: 10px; + margin-left: 8px; + vertical-align: middle; +} + +.app-card-source { + font-size: 10px; + font-weight: 600; + padding: 1px 7px; + border-radius: 8px; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.app-card-source-builtin { + background: rgba(99, 102, 241, 0.1); + color: #6366f1; +} + +.app-card-source-online { + background: rgba(16, 185, 129, 0.1); + color: #10b981; +} + +/* 已安装提示 */ +.app-installed-notice { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background: rgba(34, 197, 94, 0.06); + border: 1px solid rgba(34, 197, 94, 0.2); + border-radius: var(--radius-card); + color: var(--status-good); +} + +.app-installed-notice svg { + flex-shrink: 0; +} + +.app-installed-notice span { + font-size: 14px; + line-height: 1.6; +} + +/* ============================================================ + API 设置弹窗 + ============================================================ */ +.api-settings-form { + display: flex; + flex-direction: column; + gap: 8px; +} + +.api-settings-form label { + font-size: 13px; + font-weight: 500; + color: var(--ink-secondary); +} + +.api-settings-hint { + font-size: 12px; + color: var(--ink-muted); + margin-top: 4px; +} + +/* ============================================================ + 按钮通用样式补充 + ============================================================ */ +.btn-sm { + padding: 4px 10px; + font-size: 12px; + display: inline-flex; + align-items: center; + gap: 4px; +} + +/* ============================================================ + select 下拉框 + ============================================================ */ +.param-select { + cursor: pointer; + appearance: auto; +} + +/* ============================================================ + 参数提示信息 + ============================================================ */ +.param-hint { + font-size: 12px; + padding: 8px 12px; + border-radius: var(--radius-sm); + margin-top: 8px; + line-height: 1.6; +} + +.param-hint-warn { + background: rgba(245, 158, 11, 0.08); + color: #d97706; + border: 1px solid rgba(245, 158, 11, 0.2); +} + +.param-hint-ok { + background: rgba(34, 197, 94, 0.06); + color: var(--status-good); + border: 1px solid rgba(34, 197, 94, 0.2); +} + +/* ============================================================ + Docker 配置页 + ============================================================ */ +.docker-config-page { + display: flex; + flex-direction: column; + gap: 20px; +} + +.docker-config-card { + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + padding: 20px; +} + +.docker-config-card h4 { + font-size: 15px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 12px; +} + +.mirror-textarea { + width: 100%; + min-height: 120px; + padding: 10px 14px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink); + background: var(--surface-dim); + resize: vertical; + box-sizing: border-box; + line-height: 1.6; +} + +.mirror-textarea:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; +} + +.mirror-hint { + font-size: 12px; + color: var(--ink-muted); + margin-top: 6px; + line-height: 1.6; +} + +.docker-config-actions { + display: flex; + gap: 10px; + margin-top: 14px; +} + +.docker-config-status { + font-size: 13px; + padding: 8px 14px; + border-radius: var(--radius-sm); + margin-top: 10px; +} + +.docker-config-status-ok { + color: var(--status-good); + background: rgba(34, 197, 94, 0.06); +} + +.docker-config-status-error { + color: var(--status-critical); + background: rgba(239, 68, 68, 0.06); +} diff --git a/src/renderer/src/pages/container/AppStore.jsx b/src/renderer/src/pages/container/AppStore.jsx new file mode 100644 index 0000000..7758fab --- /dev/null +++ b/src/renderer/src/pages/container/AppStore.jsx @@ -0,0 +1,582 @@ +import { useState, useEffect, useCallback, useRef } from 'react' +import './AppStore.css' +import { APP_TEMPLATES } from './appTemplates' + +// ---- 应用图标 ---- +function AppIcon({ type }) { + const icons = { + redis: ( + + + + + + ), + mysql: ( + + + + + + ), + halo: ( + + + + + + ), + default: ( + + + + + ), + } + return icons[type] || icons.default +} + +// SSH 连接数警告弹窗 +function ConnectionWarning({ count, onConfirm, onCancel }) { + return ( +
+
e.stopPropagation()}> +
+

SSH 连接数警告

+
+
+
+
+ + + + + +
+

+ 当前服务器已有 {count} 个 SSH 连接,部署任务将再建立 1 条连接。 +
+ 连接数过多可能导致服务器拒绝新连接,是否继续部署? +

+
+
+ + +
+
+
+
+ ) +} + +// 部署已提交提示 +function DeploySubmitted({ appName, error, onClose, onViewTasks }) { + return ( +
+
e.stopPropagation()}> +
+

{error ? '部署提交失败' : '部署任务已提交'}

+
+
+
+
+ {error ? ( + + + + + + ) : ( + + + + + )} +
+ {error ? ( + <> +

{appName} 部署任务提交失败。

+ {error} + + ) : ( + <> +

{appName} 部署任务已在后台开始执行。

+ 可前往「任务中心」实时查看部署进度。 + + )} +
+
+ + {!error && ( + + )} +
+
+
+
+ ) +} + +// ---- 主组件 ---- +export default function AppStore({ onDeployed, onViewTasks }) { + const [selectedApp, setSelectedApp] = useState(null) // 选中的应用模板 + const [paramValues, setParamValues] = useState({}) + const [deploying, setDeploying] = useState(false) // 正在检查连接数/提交中 + const [connWarning, setConnWarning] = useState(null) // { count } 或 null + const [deploySubmitted, setDeploySubmitted] = useState(null) // { appName } 或 null + + // 在线商店状态 + const [onlineTemplates, setOnlineTemplates] = useState([]) + const [syncedAt, setSyncedAt] = useState(null) + const [syncing, setSyncing] = useState(false) + const [syncMsg, setSyncMsg] = useState(null) // { type: 'ok'|'error', text } + const [installedApps, setInstalledApps] = useState([]) // 已安装容器名列表 + + const syncMsgTimer = useRef(null) + + // 初始化:加载本地缓存 + 获取 API 地址 + 检查已安装 + useEffect(() => { + // 加载本地缓存在线模板 + window.panel.appstoreGetLocal().then((r) => { + if (r.ok && r.templates) { + setOnlineTemplates(r.templates) + setSyncedAt(r.syncedAt) + } + }) + + // 检查已安装应用 + refreshInstalled() + }, []) + + // 刷新已安装列表 + const refreshInstalled = useCallback(async () => { + try { + const r = await window.panel.appstoreCheckInstalled() + if (r.ok) { + setInstalledApps(r.installed || []) + } + } catch { + // Docker 未连接时忽略 + } + }, []) + + // 合并内置模板和在线模板(去重:按 containerName 去重,内置优先) + const allTemplates = (() => { + const seen = new Set() + const result = [] + // 内置模板优先 + for (const t of APP_TEMPLATES) { + const cn = t.deploy.containerName + if (!seen.has(cn)) { + seen.add(cn) + result.push({ ...t, source: 'builtin' }) + } + } + // 在线模板补充 + for (const t of onlineTemplates) { + const cn = t.deploy?.containerName + if (cn && !seen.has(cn)) { + seen.add(cn) + result.push({ ...t, source: 'online' }) + } + } + return result + })() + + // 判断某个模板是否已安装 + const isInstalled = useCallback((template) => { + const cn = template.deploy?.containerName + if (!cn) return false + return installedApps.includes(cn) + }, [installedApps]) + + // 同步在线商店 + const handleSync = useCallback(async () => { + setSyncing(true) + setSyncMsg(null) + try { + const r = await window.panel.appstoreSync() + if (r.ok) { + setOnlineTemplates(r.templates) + setSyncedAt(r.syncedAt) + setSyncMsg({ type: 'ok', text: `同步成功,共 ${r.templates.length} 个在线模板` }) + // 同步后刷新已安装状态 + refreshInstalled() + } else { + setSyncMsg({ type: 'error', text: r.error || '同步失败' }) + } + } catch (err) { + setSyncMsg({ type: 'error', text: err.message || '同步异常' }) + } + setSyncing(false) + // 5 秒后清除提示 + if (syncMsgTimer.current) clearTimeout(syncMsgTimer.current) + syncMsgTimer.current = setTimeout(() => setSyncMsg(null), 5000) + }, [refreshInstalled]) + + + + // 选中应用时初始化参数 + useEffect(() => { + if (!selectedApp) return + const defaults = {} + for (const param of selectedApp.deploy.params || []) { + defaults[param.key] = param.default || '' + } + // Halo + MySQL 联动:如果 MySQL 容器已安装,自动填充默认值 + if (selectedApp.id === 'halo' && installedApps.includes('mysql')) { + defaults['DB_TYPE'] = 'mysql' + defaults['DB_HOST'] = '127.0.0.1' + defaults['DB_PORT'] = '3306' + defaults['DB_NAME'] = 'halodb' + defaults['DB_USER'] = 'root' + } + setParamValues(defaults) + }, [selectedApp, installedApps]) + + // 执行部署(先检查连接数) + const handleDeploy = useCallback(async () => { + setDeploying(true) + + // 检查 SSH 连接数 + let connCount = 0 + try { + const r = await window.panel.dockerCheckConnections() + if (r.ok) { + connCount = r.count + } + } catch { + // 检查失败不阻塞部署 + } + + // 连接数 >= 8 时弹窗警告(服务器 SSH MaxStartups 通常 10:30:100) + if (connCount >= 8) { + setConnWarning({ count: connCount }) + setDeploying(false) + return + } + + // 直接提交部署 + await submitDeploy() + }, [selectedApp, paramValues]) + + // 确认继续部署(从警告弹窗过来) + const confirmDeploy = async () => { + setConnWarning(null) + await submitDeploy() + } + + // 提交部署到后台 + const submitDeploy = async () => { + setDeploying(true) + + const result = await window.panel.dockerDeployApp(selectedApp.deploy, paramValues) + + setDeploying(false) + + if (result.ok) { + // 部署任务已提交到后台 + setDeploySubmitted({ appName: selectedApp.name }) + if (onDeployed) onDeployed() + } else { + // 提交失败 + setDeploySubmitted({ + appName: selectedApp.name, + error: result.message || '部署任务提交失败', + }) + } + } + + const closeSubmitted = () => { + setDeploySubmitted(null) + setSelectedApp(null) + // 部署后刷新已安装状态 + refreshInstalled() + } + + // ---- 渲染 ---- + + // 连接数警告弹窗 + if (connWarning) { + return ( + setConnWarning(null)} + /> + ) + } + + // 部署已提交提示 + if (deploySubmitted) { + return ( + { + closeSubmitted() + if (onViewTasks) onViewTasks() + }} + /> + ) + } + + // 应用详情 + 参数表单 + if (selectedApp) { + const app = selectedApp + const d = app.deploy + const installed = isInstalled(app) + + return ( +
+ {/* 返回按钮 */} + + + {/* 应用信息 */} +
+
+ +
+
+

+ {app.name} + {installed && 已安装} +

+

{app.tagline}

+ {app.category} + {app.source === 'builtin' ? '内置' : '在线'} +
+
+ +

{app.description}

+ + {/* 配置预览 */} +
+

部署配置预览

+
+
+ 镜像 + {d.image} +
+
+ 容器名 + {d.containerName} +
+
+ 重启策略 + {d.restart} +
+ {d.hostname && ( +
+ 主机名 + {d.hostname} +
+ )} +
+ + {/* 端口映射 */} + {d.ports.length > 0 && ( +
+ 端口映射 +
+ {d.ports.map((p, i) => ( +
+ {p.host} + : + {p.container} + {p.protocol} +
+ ))} +
+
+ )} + + {/* 挂载卷 */} + {d.volumes.length > 0 && ( +
+ 挂载卷 +
+ {d.volumes.map((v, i) => ( +
+ {v.host} + : + {v.container} + {v.mode} +
+ ))} +
+
+ )} + + {/* 环境变量 */} + {d.env && d.env.length > 0 && ( +
+ 环境变量 +
+ {d.env.map((e, i) => ( +
+ {e.key} + = + {e.value.includes('{{') ? '(部署时输入)' : e.value} +
+ ))} +
+
+ )} + + {/* 启动命令 */} + {d.command && ( +
+ 启动命令 +
+
+ {d.command} +
+
+
+ )} +
+ + {/* 参数输入表单 */} + {installed ? ( +
+ + + + + 该应用已安装,无法重复部署。如需重新部署,请先在容器列表中删除同名容器。 +
+ ) : ( +
+

部署参数

+ {(d.params || []).filter((param) => { + // showWhen 条件渲染 + if (param.showWhen) { + return paramValues[param.showWhen.param] === param.showWhen.equals + } + return true + }).map((param) => ( +
+ + {param.type === 'select' ? ( + + ) : ( + setParamValues((prev) => ({ ...prev, [param.key]: e.target.value }))} + /> + )} +
+ ))} + {/* Halo MySQL 联动提示 */} + {selectedApp.id === 'halo' && paramValues['DB_TYPE'] === 'mysql' && !installedApps.includes('mysql') && ( +
+ 检测到服务器未安装 MySQL 容器。请先在应用商店部署 MySQL,或切换为 H2 数据库。 +
+ )} + {selectedApp.id === 'halo' && paramValues['DB_TYPE'] === 'mysql' && installedApps.includes('mysql') && ( +
+ 已检测到 MySQL 容器,已自动填充连接参数。请确认密码正确。 +
+ )} +
+ )} + + {/* 部署按钮 */} +
+ + +
+
+ ) + } + + // 应用商店列表 + return ( +
+
+
+
+

应用商店

+

选择应用一键部署,自动拉取镜像、创建目录、写入配置文件并启动容器

+
+
+ {syncMsg && ( + {syncMsg.text} + )} + {syncedAt && !syncMsg && !syncing && ( + 上次同步: {new Date(syncedAt).toLocaleString('zh-CN')} + )} + +
+
+
+
+ {allTemplates.map((app) => { + const installed = isInstalled(app) + return ( +
setSelectedApp(app)} + > +
+ +
+
+
+ {app.name} + {installed && 已安装} +
+
{app.tagline}
+
+ {app.category} + {app.image} + + {app.source === 'builtin' ? '内置' : '在线'} + +
+
+
+ ) + })} +
+ +
+ ) +} diff --git a/src/renderer/src/pages/container/ContainerCreate.css b/src/renderer/src/pages/container/ContainerCreate.css new file mode 100644 index 0000000..1e6ee65 --- /dev/null +++ b/src/renderer/src/pages/container/ContainerCreate.css @@ -0,0 +1,584 @@ +/* ============================================================ + 创建容器弹窗 + ============================================================ */ +.cc-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1200; +} + +.cc-dialog { + background: var(--card-bg); + border-radius: var(--radius-card); + box-shadow: var(--shadow-overlay); + width: 92%; + max-width: 760px; + max-height: 88vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* ---- 头部 ---- */ +.cc-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 24px; + border-bottom: 1px solid var(--hairline); + flex-shrink: 0; +} + +.cc-header h2 { + font-size: 17px; + font-weight: 600; + color: var(--ink-strong); + margin: 0; +} + +.cc-close { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: none; + border-radius: var(--radius-sm); + background: transparent; + color: var(--ink-muted); + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease; +} + +.cc-close:hover { + background: var(--surface-dim); + color: var(--ink-strong); +} + +/* ---- 模式切换 ---- */ +.cc-mode-switch { + display: flex; + gap: 0; + padding: 0 24px; + border-bottom: 1px solid var(--hairline); + flex-shrink: 0; +} + +.cc-mode-btn { + padding: 10px 20px; + font-size: 14px; + font-weight: 500; + font-family: inherit; + color: var(--ink-secondary); + background: transparent; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease; + margin-bottom: -1px; +} + +.cc-mode-btn:hover { + color: var(--ink-strong); +} + +.cc-mode-btn.active { + color: var(--brand); + border-bottom-color: var(--brand); +} + +/* ---- 内容区 ---- */ +.cc-body { + flex: 1; + overflow-y: auto; + padding: 20px 24px; +} + +/* ---- 通用 section ---- */ +.cc-section { + margin-bottom: 20px; +} + +.cc-section-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 600; + color: var(--ink-strong); + margin-bottom: 10px; +} + +.cc-loading-hint { + font-size: 12px; + font-weight: 400; + color: var(--ink-muted); +} + +/* ---- 命令模式 ---- */ +.cc-cmd-input { + width: 100%; + padding: 12px 14px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-card); + font-size: 13px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink); + background: var(--surface-dim); + line-height: 1.6; + resize: vertical; + min-height: 80px; + box-sizing: border-box; +} + +.cc-cmd-input:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; + background: var(--card-bg); +} + +.cc-cmd-input::placeholder { + color: var(--ink-muted); + font-family: 'Consolas', 'Monaco', monospace; +} + +.cc-cmd-error { + margin-top: 8px; + padding: 8px 12px; + background: rgba(239, 68, 68, 0.08); + color: var(--status-critical); + font-size: 13px; + border-radius: var(--radius-sm); +} + +.cc-cmd-hint { + margin-top: 8px; + font-size: 13px; + color: var(--ink-muted); +} + +/* ---- 解析预览 ---- */ +.cc-parsed-preview, +.cc-preview-section { + margin-top: 16px; + background: var(--surface-dim); + border: 1px solid var(--hairline); + border-radius: var(--radius-card); + padding: 14px 16px; +} + +.cc-parsed-title, +.cc-preview-title { + font-size: 13px; + font-weight: 600; + color: var(--ink-secondary); + margin-bottom: 10px; +} + +/* ---- 镜像选择 ---- */ +.cc-image-select { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: inherit; + color: var(--ink); + background: var(--card-bg); + cursor: pointer; + box-sizing: border-box; + margin-bottom: 10px; +} + +.cc-image-select:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; +} + +.cc-image-manual { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink); + background: var(--card-bg); + box-sizing: border-box; +} + +.cc-image-manual:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; +} + +/* ---- 分区导航 ---- */ +.cc-section-nav { + display: flex; + gap: 4px; + margin-bottom: 16px; + border-bottom: 1px solid var(--hairline); + padding-bottom: 0; +} + +.cc-nav-btn { + padding: 8px 14px; + font-size: 13px; + font-weight: 500; + font-family: inherit; + color: var(--ink-secondary); + background: transparent; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease; + margin-bottom: -1px; +} + +.cc-nav-btn:hover { + color: var(--ink-strong); +} + +.cc-nav-btn.active { + color: var(--brand); + border-bottom-color: var(--brand); +} + +/* ---- 表单 ---- */ +.cc-form-section { + display: flex; + flex-direction: column; + gap: 12px; +} + +.cc-form-row { + display: flex; + align-items: center; + gap: 12px; +} + +.cc-form-label { + font-size: 13px; + font-weight: 500; + color: var(--ink-secondary); + width: 90px; + flex-shrink: 0; + text-align: right; +} + +.cc-form-input { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: inherit; + color: var(--ink); + background: var(--card-bg); + box-sizing: border-box; +} + +.cc-form-input:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; +} + +/* ---- 子标题(端口/卷/环境变量) ---- */ +.cc-section-subtitle { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +.cc-section-subtitle > span { + font-size: 14px; + font-weight: 600; + color: var(--ink-strong); +} + +.cc-add-btn { + padding: 4px 12px; + font-size: 13px; + font-family: inherit; + font-weight: 500; + color: var(--brand); + background: var(--brand-soft); + border: 1px solid rgba(91, 110, 245, 0.2); + border-radius: var(--radius-sm); + cursor: pointer; + transition: background 0.15s ease; +} + +.cc-add-btn:hover { + background: var(--brand-soft-hover); +} + +.cc-empty-hint { + padding: 16px; + text-align: center; + font-size: 13px; + color: var(--ink-muted); + background: var(--surface-dim); + border-radius: var(--radius-sm); +} + +/* ---- 行布局(端口/卷/环境变量) ---- */ +.cc-port-row, +.cc-volume-row, +.cc-env-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.cc-port-row input, +.cc-volume-row input, +.cc-env-row input { + flex: 1; + min-width: 0; + padding: 7px 10px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink); + background: var(--card-bg); + box-sizing: border-box; +} + +.cc-port-row input:focus, +.cc-volume-row input:focus, +.cc-env-row input:focus { + outline: 2px solid var(--brand); + outline-offset: 1px; +} + +.cc-port-row select, +.cc-volume-row select { + padding: 7px 10px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-sm); + font-size: 13px; + font-family: inherit; + color: var(--ink); + background: var(--card-bg); + cursor: pointer; + flex-shrink: 0; + width: 80px; +} + +.cc-arrow { + font-size: 14px; + color: var(--ink-muted); + flex-shrink: 0; + font-family: 'Consolas', 'Monaco', monospace; +} + +.cc-remove-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + border-radius: var(--radius-sm); + background: transparent; + color: var(--ink-muted); + cursor: pointer; + flex-shrink: 0; + transition: background 0.15s ease, color 0.15s ease; +} + +.cc-remove-btn:hover { + background: rgba(239, 68, 68, 0.1); + color: var(--status-critical); +} + +/* ---- 底部 ---- */ +.cc-footer { + border-top: 1px solid var(--hairline); + padding: 14px 24px; + flex-shrink: 0; + background: var(--surface-dim); +} + +.cc-create-error { + padding: 8px 12px; + margin-bottom: 10px; + background: rgba(239, 68, 68, 0.08); + color: var(--status-critical); + font-size: 13px; + border-radius: var(--radius-sm); +} + +.cc-cmd-preview { + display: flex; + align-items: flex-start; + gap: 10px; + margin-bottom: 12px; +} + +.cc-cmd-preview-label { + font-size: 12px; + font-weight: 600; + color: var(--ink-muted); + flex-shrink: 0; + padding-top: 2px; +} + +.cc-cmd-preview-code { + flex: 1; + font-size: 12px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink); + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-sm); + padding: 8px 12px; + word-break: break-all; + line-height: 1.5; + max-height: 60px; + overflow-y: auto; +} + +.cc-footer-actions { + display: flex; + gap: 12px; + justify-content: flex-end; +} + +/* ============================================================ + 可视化配置展示 + ============================================================ */ +.cc-parsed-config { + display: flex; + flex-direction: column; + gap: 14px; +} + +.cc-parsed-group { + display: flex; + flex-direction: column; + gap: 6px; +} + +.cc-parsed-group-title { + font-size: 12px; + font-weight: 600; + color: var(--ink-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.cc-parsed-items { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.cc-parsed-item { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-sm); +} + +.cc-parsed-label { + font-size: 12px; + color: var(--ink-muted); + font-weight: 500; +} + +.cc-parsed-value { + font-size: 12px; + font-family: 'Consolas', 'Monaco', monospace; + color: var(--ink-strong); + font-weight: 500; +} + +.cc-value-image { + color: var(--brand); + font-weight: 600; +} + +/* ---- 端口/卷/环境变量 表格行 ---- */ +.cc-parsed-table { + display: flex; + flex-direction: column; + gap: 4px; +} + +.cc-parsed-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + background: var(--card-bg); + border: 1px solid var(--hairline); + border-radius: var(--radius-sm); + font-size: 12px; + font-family: 'Consolas', 'Monaco', monospace; +} + +.cc-port-host, +.cc-vol-host { + color: var(--ink-strong); + font-weight: 500; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cc-arrow-icon { + color: var(--ink-muted); + font-size: 11px; +} + +.cc-port-container, +.cc-vol-container { + color: var(--brand); + font-weight: 500; +} + +.cc-port-proto { + font-size: 11px; + color: var(--ink-muted); + background: var(--surface-dim); + padding: 1px 6px; + border-radius: 3px; +} + +.cc-vol-mode { + font-size: 11px; + color: var(--ink-muted); + background: var(--surface-dim); + padding: 1px 6px; + border-radius: 3px; +} + +.cc-env-key { + color: var(--ink-strong); + font-weight: 600; +} + +.cc-eq { + color: var(--ink-muted); +} + +.cc-env-value { + color: var(--ink-secondary); + word-break: break-all; +} diff --git a/src/renderer/src/pages/container/ContainerCreate.jsx b/src/renderer/src/pages/container/ContainerCreate.jsx new file mode 100644 index 0000000..1336ae6 --- /dev/null +++ b/src/renderer/src/pages/container/ContainerCreate.jsx @@ -0,0 +1,685 @@ +import { useState, useEffect, useCallback, useRef } from 'react' +import './ContainerCreate.css' +import { parseDockerRun, buildDockerRun } from './cmdParser' + +// ---- 默认配置 ---- +const DEFAULT_CONFIG = { + image: '', + name: '', + ports: [], + volumes: [], + env: [], + network: '', + restart: '', + hostname: '', + workdir: '', + user: '', + memory: '', + cpu: '', + command: '', + extraArgs: [], +} + +export default function ContainerCreate({ onClose, onCreated }) { + const [mode, setMode] = useState('image') // 'image' | 'command' + const [images, setImages] = useState([]) + const [networks, setNetworks] = useState([]) + const [imgLoading, setImgLoading] = useState(true) + const [config, setConfig] = useState(DEFAULT_CONFIG) + const [cmdInput, setCmdInput] = useState('') + const [cmdError, setCmdError] = useState(null) + const [creating, setCreating] = useState(false) + const [createError, setCreateError] = useState(null) + const [activeSection, setActiveSection] = useState('basic') + const dialogRef = useRef(null) + + // ---- 获取镜像列表和网络列表 ---- + useEffect(() => { + // 并行获取 + setImgLoading(true) + window.panel.dockerExec('docker images --format "{{json .}}"', 15000).then((r) => { + if (r.ok && r.code === 0) { + const list = (r.stdout || '').trim().split('\n').filter(Boolean).map((line) => { + try { return JSON.parse(line) } catch { return null } + }).filter(Boolean) + setImages(list) + } + setImgLoading(false) + }) + + window.panel.dockerExec('docker network ls --format "{{.Name}}"', 10000).then((r) => { + if (r.ok && r.code === 0) { + const list = (r.stdout || '').trim().split('\n').filter(Boolean) + setNetworks(list) + } + }) + }, []) + + // ---- 命令模式:解析输入并同步配置 ---- + useEffect(() => { + if (mode !== 'command') return + if (!cmdInput.trim()) { + setConfig(DEFAULT_CONFIG) + setCmdError(null) + return + } + // 只在包含 docker run 或 docker create 时才解析 + if (!/^docker\s+run\s/i.test(cmdInput.trim()) && !/^docker\s+create\s/i.test(cmdInput.trim())) { + setCmdError('命令必须以 docker run 或 docker create 开头') + return + } + try { + const parsed = parseDockerRun(cmdInput) + setConfig(parsed) + setCmdError(null) + } catch (err) { + setCmdError('解析失败: ' + err.message) + } + }, [cmdInput, mode]) + + // ---- ESC 关闭 ---- + useEffect(() => { + const onKey = (e) => { + if (e.key === 'Escape') onClose() + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [onClose]) + + // ---- 配置更新辅助 ---- + const updateConfig = useCallback((key, value) => { + setConfig((prev) => ({ ...prev, [key]: value })) + }, []) + + const updateArrayItem = useCallback((key, index, field, value) => { + setConfig((prev) => { + const arr = [...prev[key]] + arr[index] = { ...arr[index], [field]: value } + return { ...prev, [key]: arr } + }) + }, []) + + const addArrayItem = useCallback((key, template) => { + setConfig((prev) => ({ ...prev, [key]: [...prev[key], template] })) + }, []) + + const removeArrayItem = useCallback((key, index) => { + setConfig((prev) => { + const arr = [...prev[key]] + arr.splice(index, 1) + return { ...prev, [key]: arr } + }) + }, []) + + // ---- 创建容器 ---- + const handleCreate = useCallback(async () => { + setCreating(true) + setCreateError(null) + + let cmd + if (mode === 'command') { + // 命令模式:直接使用输入的命令,但确保 -d + const raw = cmdInput.trim() + if (!/^docker\s+(run|create)\s/i.test(raw)) { + setCreateError('命令必须以 docker run 或 docker create 开头') + setCreating(false) + return + } + // 如果没有 -d 或 --detach,加上 -d + if (!/\s-d(\s|$)/.test(raw) && !/--detach(\s|$)/.test(raw)) { + cmd = raw.replace(/^(docker\s+(run|create))\s/i, '$1 -d ') + } else { + cmd = raw + } + } else { + // 镜像模式:从配置构建命令 + if (!config.image) { + setCreateError('请选择或输入镜像名称') + setCreating(false) + return + } + cmd = buildDockerRun(config) + } + + const r = await window.panel.dockerExec(cmd, 60000) + + if (r.ok && r.code === 0) { + onCreated() + } else { + setCreateError(r.stderr || r.message || '创建容器失败') + } + setCreating(false) + }, [mode, cmdInput, config, onCreated]) + + // ---- 可视化预览命令 ---- + const previewCmd = mode === 'command' ? cmdInput : buildDockerRun(config) + + // ---- 获取镜像名 ---- + const getImageName = (img) => { + const repo = img.Repository || img.repository || '' + const tag = img.Tag || img.tag || '' + if (!repo || repo === '') return '' + return tag && tag !== '' ? `${repo}:${tag}` : repo + } + + const sections = [ + { id: 'basic', label: '基本配置' }, + { id: 'ports', label: '端口映射' }, + { id: 'volumes', label: '挂载卷' }, + { id: 'env', label: '环境变量' }, + { id: 'advanced', label: '高级配置' }, + ] + + return ( +
+
e.stopPropagation()} + > + {/* 头部 */} +
+

创建容器

+ +
+ + {/* 模式切换 */} +
+ + +
+ + {/* 内容区 */} +
+ {mode === 'command' ? ( + /* ---- 命令创建模式 ---- */ +
+
+
+ 输入 docker run 命令 +
+