Anyexyz's picture
Update main.py
8b502bb verified
import zipfile
import yaml
from pywebio.input import *
from pywebio.output import *
from pywebio.platform import config
from pywebio.platform.tornado import start_server
from pathlib import Path
import shutil
import logging
import os
import io
import re
# 环境变量
APPS_DIR = Path("apps")
DEFAULT_LOGO = Path("default_logo.png")
# 初始化logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 校验key是否为英文字符串
def is_valid_key(key):
return bool(re.match(r'^[a-zA-Z]+$', key))
# 校验基本信息
def check_base_info(data):
required_fields = [
"name", "key", "tags", "shortDescZh", "shortDescEn",
"type", "crossVersionUpdate", "website", "github", "document"
]
for field in required_fields:
if not data[field]:
return (field, f"{field} 不能为空")
if len(data["shortDescZh"]) > 30:
return ("shortDescZh", "中文描述不能超过30个字")
if not is_valid_key(data["key"]):
return ("key", "key 必须是纯英文字符串")
return None
# 保存文件
def save_file(path, content, mode='w', encoding=None):
try:
if 'b' in mode: # 二进制模式
with open(path, mode) as f:
f.write(content)
else: # 文本模式
with open(path, mode, encoding=encoding or 'utf-8') as f:
f.write(content)
logging.info(f"File saved successfully: {path}")
except IOError as e:
logging.error(f"Error saving file {path}: {e}")
raise
# 复制文件
def copy_file(src, dst):
try:
shutil.copy(src, dst)
logging.info(f"File copied successfully from {src} to {dst}")
except IOError as e:
logging.error(f"Error copying file from {src} to {dst}: {e}")
raise
# 创建目录
def create_directory(path):
try:
path.mkdir(parents=True, exist_ok=True)
logging.info(f"Directory created: {path}")
except OSError as e:
logging.error(f"Error creating directory {path}: {e}")
raise
# 创建版本
def create_version(app_dir, existing_versions):
while True:
version = input("请输入应用的版本 (不要以v开头)")
if version in existing_versions:
put_error(f"版本 {version} 已存在,请输入一个新的版本号")
else:
break
version_dir = app_dir / version
create_directory(version_dir)
version_info = input_group("版本信息", [
textarea("请编写docker-compose.yml", name="docker_compose", code={"mode": "yaml", "theme": ""}),
textarea("请编写data.yml", name="data", code={"mode": "yaml", "theme": ""}),
])
save_file(version_dir / "data.yml", version_info["data"])
save_file(version_dir / "docker-compose.yml", version_info["docker_compose"])
put_success(f"已成功创建版本 {version}")
return version
# 压缩文件夹
def zip_folder(folder_path, output_path):
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, _, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, folder_path)
zipf.write(file_path, arcname)
# 主函数
def main():
base_info = input_group(
"自助创建 1Panel 应用",
[
input("1. 请输入应用名称* ", name="name", type=TEXT),
input("2. 请输入应用的key* (仅限英文,用于创建文件夹)", name="key", type=TEXT),
checkbox("3. 选择应用标签*(可以有多个)", inline=True, options=[
{"label": "建站", "value": "WebSite"},
{"label": "Web 服务器", "value": "Server"},
{"label": "运行环境", "value": "Runtime"},
{"label": "数据库", "value": "Database"},
{"label": "工具", "value": "Tool"},
{"label": "CI/CD", "value": "CI/CD"},
{"label": "本地", "value": "Local"},
], name="tags"),
input("4. 请输入应用中文描述*(不要超过30个字)", name="shortDescZh", type=TEXT),
input("5. 请输入应用英文描述*", name="shortDescEn", type=TEXT),
select("6. 选择应用类型*", options=[
{"label": "工具类应用,如 phpMyAdmin redis-commander jenkins", "value": "tool"},
{"label": "支持一键部署的站点类应用类型,如 wordpress halo", "value": "website"},
{"label": "服务类型的运行时应用,如 mysql openresty redis", "value": "runtime"},
], name="type"),
select("7. 是否可跨大版本升级*", options=[
{"label": "是", "value": True},
{"label": "否", "value": False},
], name="crossVersionUpdate"),
slider("8. 应用安装数量限制,(0 代表无限制)*", name="limit", min=0, max=100, step=1, value=0),
input("9. 官网地址*", name="website", type=URL),
input("10. Github 地址*", name="github", type=URL),
input("11. 文档地址*", name="document", type=URL),
file_upload("上传应用Logo图片(最好是 180 * 180 px)(可选): ", name="logo", accept=[".png", ".jpg", ".jpeg"], max_size="5M"),
],
validate=check_base_info,
)
app_dir = APPS_DIR / base_info["key"]
create_directory(app_dir)
app_info = {
"additionalProperties": {
"key": base_info["key"],
"name": base_info["name"],
"tags": base_info["tags"],
"shortDescZh": base_info["shortDescZh"],
"shortDescEn": base_info["shortDescEn"],
"type": base_info["type"],
"crossVersionUpdate": base_info["crossVersionUpdate"],
"limit": base_info["limit"],
"website": base_info["website"],
"github": base_info["github"],
"document": base_info["document"],
}
}
save_file(app_dir / "data.yml", yaml.dump(app_info, allow_unicode=True))
if base_info["logo"]:
_, file_extension = os.path.splitext(base_info["logo"]["filename"])
logo_filename = f"logo{file_extension.lower()}"
save_file(app_dir / logo_filename, base_info["logo"]["content"], mode='wb')
else:
copy_file(DEFAULT_LOGO, app_dir / "logo.png")
put_success("已成功创建基本信息")
readme = textarea("请编写README", code={"mode": "markdown", "theme": ""})
save_file(app_dir / "README.md", readme)
put_success("已成功创建README")
versions = []
while True:
version = create_version(app_dir, versions)
versions.append(version)
if not actions("是否继续创建新版本?", [
{"label": "是", "value": "yes"},
{"label": "否", "value": "no"},
]) == "yes":
break
# 压缩应用文件夹
zip_buffer = io.BytesIO()
zip_folder(app_dir, zip_buffer)
zip_buffer.seek(0)
# 美化下载按钮
put_button(
f"下载 {base_info['name']} 应用文件",
onclick=lambda: put_file(f"{base_info['key']}.zip", zip_buffer.getvalue()),
color="success",
outline=True
)
if __name__ == "__main__":
config(title="自助创建 1Panel 应用")
start_server(main, debug=False, port=8080, cdn=False)