已安装应用界面增加导出应用安装包的功能
This commit is contained in:
parent
c8bf0ea7dd
commit
77cb098f80
@ -787,7 +787,7 @@
|
|||||||
"Package '{0}' uninstalled successfully": "扩展包 '{0}' 卸载成功",
|
"Package '{0}' uninstalled successfully": "扩展包 '{0}' 卸载成功",
|
||||||
"Package '{0}' installed successfully": "扩展包 '{0}' 安装成功",
|
"Package '{0}' installed successfully": "扩展包 '{0}' 安装成功",
|
||||||
|
|
||||||
"App Installer": "应用扩展安装",
|
"App Installer": "应用安装",
|
||||||
"Upload and install applications to your local Jingrow environment": "上传并安装应用或扩展到您的本地 Jingrow 环境",
|
"Upload and install applications to your local Jingrow environment": "上传并安装应用或扩展到您的本地 Jingrow 环境",
|
||||||
"Upload App Package": "上传应用或扩展包",
|
"Upload App Package": "上传应用或扩展包",
|
||||||
"Uploading...": "上传中...",
|
"Uploading...": "上传中...",
|
||||||
|
|||||||
@ -51,6 +51,7 @@ import { t } from '@/shared/i18n'
|
|||||||
const installedApps = ref<any[]>([])
|
const installedApps = ref<any[]>([])
|
||||||
const loadingApps = ref(false)
|
const loadingApps = ref(false)
|
||||||
const uninstalling = ref(false)
|
const uninstalling = ref(false)
|
||||||
|
const exporting = ref<string | null>(null)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@ -77,12 +78,20 @@ const columns: DataTableColumns = [
|
|||||||
{
|
{
|
||||||
title: t('Actions'),
|
title: t('Actions'),
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 100,
|
width: 280,
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
return h('div', { class: 'action-buttons' }, [
|
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, {
|
h(NButton, {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
style: 'margin-left: 8px;',
|
||||||
disabled: !row.uninstallable,
|
disabled: !row.uninstallable,
|
||||||
onClick: () => uninstallApp(row)
|
onClick: () => uninstallApp(row)
|
||||||
}, { default: () => row.uninstallable ? t('Uninstall') : t('System App') })
|
}, { 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) => {
|
const uninstallApp = async (app: any) => {
|
||||||
// 检查是否为系统应用
|
// 检查是否为系统应用
|
||||||
if (!app.uninstallable) {
|
if (!app.uninstallable) {
|
||||||
|
|||||||
@ -9,15 +9,20 @@ import logging
|
|||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
import re
|
||||||
import requests
|
|
||||||
import json
|
import json
|
||||||
import shutil
|
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.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.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.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
|
from jingrow.config import Config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -1133,3 +1138,29 @@ async def create_app(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"发布失败: {str(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