已安装应用界面增加导出应用安装包的功能
This commit is contained in:
parent
c8bf0ea7dd
commit
77cb098f80
@ -787,7 +787,7 @@
|
||||
"Package '{0}' uninstalled successfully": "扩展包 '{0}' 卸载成功",
|
||||
"Package '{0}' installed successfully": "扩展包 '{0}' 安装成功",
|
||||
|
||||
"App Installer": "应用扩展安装",
|
||||
"App Installer": "应用安装",
|
||||
"Upload and install applications to your local Jingrow environment": "上传并安装应用或扩展到您的本地 Jingrow 环境",
|
||||
"Upload App Package": "上传应用或扩展包",
|
||||
"Uploading...": "上传中...",
|
||||
|
||||
@ -51,6 +51,7 @@ import { t } from '@/shared/i18n'
|
||||
const installedApps = ref<any[]>([])
|
||||
const loadingApps = ref(false)
|
||||
const uninstalling = ref(false)
|
||||
const exporting = ref<string | null>(null)
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
@ -77,12 +78,20 @@ const columns: DataTableColumns = [
|
||||
{
|
||||
title: t('Actions'),
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
width: 280,
|
||||
render: (row: any) => {
|
||||
return h('div', { class: 'action-buttons' }, [
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'default',
|
||||
loading: exporting.value === row.name,
|
||||
disabled: row.name === 'jingrow',
|
||||
onClick: () => exportApp(row)
|
||||
}, { default: () => t('Export Package') }),
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
style: 'margin-left: 8px;',
|
||||
disabled: !row.uninstallable,
|
||||
onClick: () => uninstallApp(row)
|
||||
}, { default: () => row.uninstallable ? t('Uninstall') : t('System App') })
|
||||
@ -118,6 +127,38 @@ const refreshApps = () => {
|
||||
}
|
||||
|
||||
|
||||
const exportApp = async (app: any) => {
|
||||
// 检查是否为系统应用
|
||||
if (app.name === 'jingrow') {
|
||||
message.warning(t('System App') + ' ' + t('cannot be exported'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
exporting.value = app.name
|
||||
|
||||
const response = await axios.post(`/jingrow/export-app-package/${app.name}`, {}, {
|
||||
headers: get_session_api_headers()
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
message.success(t('App package exported successfully: {0}').replace('{0}', response.data.filename || app.name))
|
||||
// 自动下载文件
|
||||
if (response.data.filename) {
|
||||
const fileUrl = `/files/${response.data.filename}`
|
||||
window.open(fileUrl, '_blank')
|
||||
}
|
||||
} else {
|
||||
message.error(response.data.error || t('Failed to export app package'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Export app error:', error)
|
||||
message.error(error.response?.data?.error || t('Failed to export app package'))
|
||||
} finally {
|
||||
exporting.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const uninstallApp = async (app: any) => {
|
||||
// 检查是否为系统应用
|
||||
if (!app.uninstallable) {
|
||||
|
||||
@ -9,15 +9,20 @@ import logging
|
||||
import tempfile
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
import requests
|
||||
import re
|
||||
import json
|
||||
import shutil
|
||||
import re
|
||||
import subprocess
|
||||
import unicodedata
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import requests
|
||||
|
||||
from jingrow.utils.app_installer import install_app, get_installed_apps as get_apps, get_app_directories, ensure_package_and_module
|
||||
from jingrow.utils.jingrow_api import get_jingrow_api_headers
|
||||
from jingrow.utils.auth import get_jingrow_cloud_url, get_jingrow_cloud_api_headers, get_jingrow_cloud_api_url, get_jingrow_api_headers
|
||||
from jingrow.utils.export_app_package import export_app_package_from_local
|
||||
from jingrow.config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -1133,3 +1138,29 @@ async def create_app(
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"发布失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/jingrow/export-app-package/{app_name}")
|
||||
async def export_app_package(app_name: str):
|
||||
"""导出应用安装包 - 直接打包本地应用源码"""
|
||||
try:
|
||||
logger.info(f"开始导出应用安装包: {app_name}")
|
||||
|
||||
# 获取应用目录
|
||||
current = Path(__file__).resolve()
|
||||
root = current.parents[4]
|
||||
apps_dir = root / "apps"
|
||||
|
||||
# 调用辅助函数
|
||||
result = export_app_package_from_local(app_name, apps_dir)
|
||||
|
||||
if not result.get("success"):
|
||||
raise HTTPException(status_code=400, detail=result.get("error", "导出失败"))
|
||||
|
||||
return result
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"导出应用安装包失败: {str(e)}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"导出失败: {str(e)}")
|
||||
|
||||
68
apps/jingrow/jingrow/utils/export_app_package.py
Normal file
68
apps/jingrow/jingrow/utils/export_app_package.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""
|
||||
导出应用安装包功能
|
||||
"""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
def export_app_package_from_local(app_name: str, apps_dir: Path):
|
||||
"""从本地应用目录打包应用安装包"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
root = Path(__file__).resolve().parents[4]
|
||||
|
||||
# 使用 tmp 作为临时打包目录
|
||||
tmp_dir = root / "tmp"
|
||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# files 目录用于存放最终导出文件
|
||||
files_dir = root / "apps" / "jingrow" / "frontend" / "public" / "files"
|
||||
files_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
app_source_dir = apps_dir / app_name
|
||||
if not app_source_dir.exists():
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"本地应用目录不存在: {app_source_dir}"
|
||||
}
|
||||
|
||||
# 创建临时打包目录
|
||||
final_dir = tmp_dir / app_name
|
||||
if final_dir.exists():
|
||||
shutil.rmtree(final_dir)
|
||||
final_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 复制整个应用(包含后端和前端)
|
||||
source = app_source_dir
|
||||
|
||||
for item in source.iterdir():
|
||||
if item.name in ['__pycache__', '.git', '.DS_Store']:
|
||||
continue
|
||||
dst = final_dir / item.name
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dst, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(item, dst)
|
||||
|
||||
# 打包
|
||||
final_filename = f"{app_name}-{timestamp}.tar.gz"
|
||||
tmp_tar = tmp_dir / final_filename
|
||||
|
||||
subprocess.check_output(
|
||||
["tar", "czf", str(tmp_tar), app_name],
|
||||
cwd=str(tmp_dir)
|
||||
)
|
||||
|
||||
# 移动到 files 目录
|
||||
final_tar = files_dir / final_filename
|
||||
shutil.move(str(tmp_tar), str(final_tar))
|
||||
|
||||
# 清理临时目录
|
||||
shutil.rmtree(final_dir, ignore_errors=True)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"filename": final_filename,
|
||||
"file_path": str(final_tar),
|
||||
"message": f"应用安装包已导出到: {final_tar}"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user