Spaces:
Running
Running
File size: 7,505 Bytes
8b502bb 66e6dd3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
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) |