feat: WFJ Panel v0.1.0 - 完整功能版

- SSH 服务器连接管理 + 登录页自动预填
- 服务器概览监控(CPU/内存/硬盘/网络/进程,进程卡片支持排序和端口显示)
- 远程文件管理(浏览/上传/下载/新建/删除,实时进度条)
- 交互式终端(xterm.js + ssh2 PTY)
- Docker 容器管理(三连接架构,概览/容器列表/镜像/应用商店/任务中心/配置/网络/卷)
- 应用商店一键部署(Redis/MySQL/Halo,在线模板同步,数据驱动 options/showWhen/extraArgs)
- Docker 镜像源配置(daemon.json 编辑 + 自动重启)
- Docker 强制停止修复(三步强制停止 + 轮询确认)
- 系统防火墙管理(firewalld/ufw/iptables 自动检测 + GUI 管理)
- 客户端版本检查(/api/version 接口 + 更新弹窗)
- 品牌名统一为 WFJ Panel
- electron-builder 打包配置(NSIS 安装包,国内镜像)
- .npmrc 配置 Electron 国内镜像
This commit is contained in:
2026-09-08 01:25:54 +08:00
parent e929d4842e
commit 90bc4bd398
46 changed files with 14841 additions and 57 deletions
+20
View File
@@ -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"]
+234
View File
@@ -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/<app_key>', 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)
+38
View File
@@ -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"
+290
View File
@@ -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());
+3
View File
@@ -0,0 +1,3 @@
flask==3.0.3
flask-cors==4.0.1
mysql-connector-python==8.4.0