1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 16:29:17 +08:00

feat: 已安装应用增加打开安装目录功能 (#1100)

This commit is contained in:
zhengkunwang223 2023-05-22 17:10:51 +08:00 committed by GitHub
parent 7c56ed7b16
commit ce19107c95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 82 additions and 34 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -39,6 +40,15 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"}
func (b *BaseApi) SyncApp(c *gin.Context) { func (b *BaseApi) SyncApp(c *gin.Context) {
go appService.SyncAppListFromLocal() go appService.SyncAppListFromLocal()
res, err := appService.GetAppUpdate()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if !res.CanUpdate {
helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsUpToDate"))
return
}
go func() { go func() {
global.LOG.Infof("sync app list start ...") global.LOG.Infof("sync app list start ...")
if err := appService.SyncAppListFromRemote(); err != nil { if err := appService.SyncAppListFromRemote(); err != nil {

View File

@ -56,6 +56,7 @@ type AppInstalledDTO struct {
AppName string `json:"appName"` AppName string `json:"appName"`
Icon string `json:"icon"` Icon string `json:"icon"`
CanUpdate bool `json:"canUpdate"` CanUpdate bool `json:"canUpdate"`
Path string `json:"path"`
} }
type DatabaseConn struct { type DatabaseConn struct {

View File

@ -385,7 +385,6 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
res.CanUpdate = true res.CanUpdate = true
return res, err return res, err
} }
res.CanUpdate = true
return res, nil return res, nil
} }
@ -665,7 +664,6 @@ func (a AppService) SyncAppListFromRemote() error {
return err return err
} }
if !updateRes.CanUpdate { if !updateRes.CanUpdate {
//global.LOG.Infof("The latest version is [%s] The app store is already up to date", updateRes.Version)
return nil return nil
} }
var ( var (
@ -836,6 +834,11 @@ func (a AppService) SyncAppListFromRemote() error {
return err return err
} }
} }
if len(deleteDetails) > 0 {
if err := appDetailRepo.BatchDelete(ctx, addDetails); err != nil {
return err
}
}
for _, u := range updateDetails { for _, u := range updateDetails {
if err := appDetailRepo.Update(ctx, u); err != nil { if err := appDetailRepo.Update(ctx, u); err != nil {
return err return err

View File

@ -646,6 +646,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
} }
installDTO := response.AppInstalledDTO{ installDTO := response.AppInstalledDTO{
AppInstall: installed, AppInstall: installed,
Path: installed.GetPath(),
} }
app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId)) app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId))
if err != nil { if err != nil {

View File

@ -46,6 +46,13 @@ func GetErrMsg(key string, maps map[string]interface{}) string {
return content return content
} }
func GetMsgByKey(key string) string {
content := ginI18n.MustGetMessage(&i18n.LocalizeConfig{
MessageID: key,
})
return content
}
//go:embed lang/* //go:embed lang/*
var fs embed.FS var fs embed.FS

View File

@ -17,7 +17,6 @@ ErrRepoNotValid: "Remote repository verification failed"
ErrNameIsExist: "Name is already exist" ErrNameIsExist: "Name is already exist"
ErrDemoEnvironment: "Demo server, prohibit this operation!" ErrDemoEnvironment: "Demo server, prohibit this operation!"
ErrCmdTimeout: "Command execution timed out" ErrCmdTimeout: "Command execution timed out"
ErrInitUser: "The user initialization has been completed by the system. Please do not repeat the operation!"
#app #app
ErrPortInUsed: "{{ .detail }} port already in use" ErrPortInUsed: "{{ .detail }} port already in use"
@ -31,6 +30,7 @@ ErrUpdateBuWebsite: 'The application was updated successfully, but the modificat
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}' Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
ErrFileParse: 'Application docker-compose file parsing failed!' ErrFileParse: 'Application docker-compose file parsing failed!'
ErrInstallDirNotFound: 'installation directory does not exist' ErrInstallDirNotFound: 'installation directory does not exist'
AppStoreIsUpToDate: 'The app store is already up to date'
#file #file
ErrFileCanNotRead: "File can not read" ErrFileCanNotRead: "File can not read"
@ -46,8 +46,6 @@ ErrDomainIsExist: "Domain is already exist"
ErrAliasIsExist: "Alias is already exist" ErrAliasIsExist: "Alias is already exist"
ErrAppDelete: 'Other Website use this App' ErrAppDelete: 'Other Website use this App'
ErrGroupIsUsed: 'The group is in use and cannot be deleted' ErrGroupIsUsed: 'The group is in use and cannot be deleted'
ErrUsernameIsExist: 'Username is already exist'
ErrUsernameIsNotExist: 'Username is not exist'
#ssl #ssl
ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed" ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed"
@ -66,7 +64,6 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container #container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted" ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted" ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrPortRules: "Invalid port specification"
#runtime #runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity" ErrDirNotFound: "The build folder does not exist! Please check file integrity"

View File

@ -17,7 +17,6 @@ ErrRepoNotValid: "远程仓库校验失败!"
ErrNameIsExist: "名称已存在" ErrNameIsExist: "名称已存在"
ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrDemoEnvironment: "演示服务器,禁止此操作!"
ErrCmdTimeout: "命令执行超时!" ErrCmdTimeout: "命令执行超时!"
ErrInitUser: "系统已完成用户初始化,请勿重复操作!"
#app #app
ErrPortInUsed: "{{ .detail }} 端口已被占用!" ErrPortInUsed: "{{ .detail }} 端口已被占用!"
@ -31,6 +30,7 @@ ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}' Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
ErrFileParse: '应用 docker-compose 文件解析失败!' ErrFileParse: '应用 docker-compose 文件解析失败!'
ErrInstallDirNotFound: '安装目录不存在' ErrInstallDirNotFound: '安装目录不存在'
AppStoreIsUpToDate: '应用商店已经是最新版本'
#file #file
ErrFileCanNotRead: "此文件不支持预览" ErrFileCanNotRead: "此文件不支持预览"
@ -46,8 +46,6 @@ ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代号已存在" ErrAliasIsExist: "代号已存在"
ErrAppDelete: '其他网站使用此应用,无法删除' ErrAppDelete: '其他网站使用此应用,无法删除'
ErrGroupIsUsed: '分组正在使用中,无法删除' ErrGroupIsUsed: '分组正在使用中,无法删除'
ErrUsernameIsExist: '用户名已存在'
ErrUsernameIsNotExist: '用户不存在'
#ssl #ssl
ErrSSLCannotDelete: "证书正在被网站使用,无法删除" ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
@ -66,7 +64,6 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container #container
ErrInUsed: "{{ .detail }} 正被使用,无法删除" ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除" ErrObjectInUsed: "该对象正被使用,无法删除"
ErrPortRules: "无效的端口规格"
#runtime #runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!" ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"

View File

@ -112,6 +112,7 @@ export namespace App {
message: string; message: string;
icon: string; icon: string;
canUpdate: boolean; canUpdate: boolean;
path: string;
app: App; app: App;
} }

View File

@ -0,0 +1,15 @@
import router from '@/routers';
export function canEditPort(app: any): boolean {
if (app.key == 'openresty') {
return false;
}
if (app.type == 'php') {
return false;
}
return true;
}
export function toFolder(folder: string) {
router.push({ path: '/hosts/files', query: { path: folder } });
}

View File

@ -1113,6 +1113,7 @@ const message = {
appInstallWarn: appInstallWarn:
'The application does not release the external access port by default, you can choose to release it in the advanced settings', 'The application does not release the external access port by default, you can choose to release it in the advanced settings',
upgradeStart: 'Start upgrading! Please refresh the page later', upgradeStart: 'Start upgrading! Please refresh the page later',
toFolder: 'Open the installation directory',
}, },
website: { website: {
website: 'Website', website: 'Website',

View File

@ -1106,6 +1106,7 @@ const message = {
allowPortHelper: '允许外部端口访问会放开防火墙端口php运行环境请勿放开', allowPortHelper: '允许外部端口访问会放开防火墙端口php运行环境请勿放开',
appInstallWarn: '应用默认不放开外部访问端口可以在高级设置中选择放开', appInstallWarn: '应用默认不放开外部访问端口可以在高级设置中选择放开',
upgradeStart: '开始升级请稍后刷新页面', upgradeStart: '开始升级请稍后刷新页面',
toFolder: '打开安装目录',
}, },
website: { website: {
website: '网站', website: '网站',

View File

@ -154,8 +154,12 @@ const getAppDetail = (key: string) => {
const sync = () => { const sync = () => {
loading.value = true; loading.value = true;
SyncApp() SyncApp()
.then(() => { .then((res) => {
MsgSuccess(i18n.global.t('app.syncStart')); if (res.message != '') {
MsgSuccess(res.message);
} else {
MsgSuccess(i18n.global.t('app.syncStart'));
}
canUpdate.value = false; canUpdate.value = false;
search(req); search(req);
}) })

View File

@ -111,12 +111,11 @@ const props = withDefaults(defineProps<OperateProps>(), {
// id: 0, // id: 0,
appKey: '', appKey: '',
}); });
let app = ref<any>({}); const app = ref<any>({});
let appDetail = ref<any>({}); const appDetail = ref<any>({});
let version = ref(''); const version = ref('');
let loadingDetail = ref(false); const loadingDetail = ref(false);
let loadingApp = ref(false); const loadingApp = ref(false);
// let appKey = ref();
const installRef = ref(); const installRef = ref();
const getApp = async () => { const getApp = async () => {
@ -150,6 +149,7 @@ const openInstall = () => {
let params = { let params = {
params: appDetail.value.params, params: appDetail.value.params,
appDetailId: appDetail.value.id, appDetailId: appDetail.value.id,
app: app.value,
}; };
if (app.value.type === 'php') { if (app.value.type === 'php') {
router.push({ path: '/websites/runtime/php' }); router.push({ path: '/websites/runtime/php' });

View File

@ -62,7 +62,7 @@
</el-input> </el-input>
<span class="input-help">{{ $t('container.limitHelper') }}</span> <span class="input-help">{{ $t('container.limitHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item prop="allowPort"> <el-form-item prop="allowPort" v-if="canEditPort(installData.app)">
<el-checkbox v-model="req.allowPort" :label="$t('app.allowPort')" size="large" /> <el-checkbox v-model="req.allowPort" :label="$t('app.allowPort')" size="large" />
<span class="input-help">{{ $t('app.allowPortHelper') }}</span> <span class="input-help">{{ $t('app.allowPortHelper') }}</span>
</el-form-item> </el-form-item>
@ -86,6 +86,7 @@
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import { InstallApp } from '@/api/modules/app'; import { InstallApp } from '@/api/modules/app';
import { Rules, checkNumberRange } from '@/global/form-rules'; import { Rules, checkNumberRange } from '@/global/form-rules';
import { canEditPort } from '@/global/business';
import { FormInstance, FormRules } from 'element-plus'; import { FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -97,10 +98,12 @@ const router = useRouter();
interface InstallRrops { interface InstallRrops {
appDetailId: number; appDetailId: number;
params?: App.AppParams; params?: App.AppParams;
app: any;
} }
const installData = ref<InstallRrops>({ const installData = ref<InstallRrops>({
appDetailId: 0, appDetailId: 0,
app: {},
}); });
const open = ref(false); const open = ref(false);
const rules = ref<FormRules>({ const rules = ref<FormRules>({
@ -112,9 +115,7 @@ const rules = ref<FormRules>({
}); });
const loading = ref(false); const loading = ref(false);
const paramForm = ref<FormInstance>(); const paramForm = ref<FormInstance>();
const form = ref<{ [key: string]: any }>({}); const form = ref<{ [key: string]: any }>({});
const initData = () => ({ const initData = () => ({
appDetailId: 0, appDetailId: 0,
params: form.value, params: form.value,
@ -126,7 +127,6 @@ const initData = () => ({
containerName: '', containerName: '',
allowPort: false, allowPort: false,
}); });
const req = reactive(initData()); const req = reactive(initData());
const handleClose = () => { const handleClose = () => {

View File

@ -2,7 +2,7 @@
<el-drawer :close-on-click-modal="false" v-model="open" size="40%"> <el-drawer :close-on-click-modal="false" v-model="open" size="40%">
<template #header> <template #header>
<Header :header="$t('app.param')" :back="handleClose"> <Header :header="$t('app.param')" :back="handleClose">
<template #buttons v-if="canEdit"> <template #buttons>
<el-button type="primary" plain @click="editParam" :disabled="loading"> <el-button type="primary" plain @click="editParam" :disabled="loading">
{{ edit ? $t('app.detail') : $t('commons.button.edit') }} {{ edit ? $t('app.detail') : $t('commons.button.edit') }}
</el-button> </el-button>
@ -75,7 +75,7 @@
</el-input> </el-input>
<span class="input-help">{{ $t('container.limitHelper') }}</span> <span class="input-help">{{ $t('container.limitHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item prop="allowPort"> <el-form-item prop="allowPort" v-if="canEditPort(paramData.app)">
<el-checkbox v-model="paramModel.allowPort" :label="$t('app.allowPort')" size="large" /> <el-checkbox v-model="paramModel.allowPort" :label="$t('app.allowPort')" size="large" />
<span class="input-help">{{ $t('app.allowPortHelper') }}</span> <span class="input-help">{{ $t('app.allowPortHelper') }}</span>
</el-form-item> </el-form-item>
@ -103,12 +103,15 @@ import { FormInstance } from 'element-plus';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang'; import i18n from '@/lang';
import { canEditPort } from '@/global/business';
interface ParamProps { interface ParamProps {
id: Number; id: Number;
app: any;
} }
const paramData = ref<ParamProps>({ const paramData = ref<ParamProps>({
id: 0, id: 0,
app: {},
}); });
interface EditForm extends App.InstallParams { interface EditForm extends App.InstallParams {
@ -127,13 +130,12 @@ const rules = reactive({
params: {}, params: {},
}); });
const submitModel = ref<any>({}); const submitModel = ref<any>({});
const canEdit = ref(false);
const acceptParams = async (props: ParamProps) => { const acceptParams = async (props: ParamProps) => {
canEdit.value = true;
submitModel.value.installId = props.id; submitModel.value.installId = props.id;
params.value = []; params.value = [];
paramData.value.id = props.id; paramData.value.id = props.id;
paramData.value.app = props.app;
edit.value = false; edit.value = false;
await get(); await get();
open.value = true; open.value = true;
@ -156,9 +158,6 @@ const get = async () => {
const configParams = res.data.params || []; const configParams = res.data.params || [];
if (configParams && configParams.length > 0) { if (configParams && configParams.length > 0) {
configParams.forEach((d) => { configParams.forEach((d) => {
if (d.edit) {
canEdit.value = true;
}
let value = d.value; let value = d.value;
if (d.type === 'number') { if (d.type === 'number') {
value = Number(value); value = Number(value);

View File

@ -97,11 +97,21 @@
:content="installed.message" :content="installed.message"
> >
<template #reference> <template #reference>
<el-button link type="primary">详情</el-button> <el-button link type="primary">
{{ $t('app.detail') }}
</el-button>
</template> </template>
</el-popover> </el-popover>
</span> </span>
<el-tooltip effect="dark" :content="$t('app.toFolder')" placement="top">
<el-button type="primary" link @click="toFolder(installed.path)">
<el-icon>
<FolderOpened />
</el-icon>
</el-button>
</el-tooltip>
<el-button <el-button
class="h-button" class="h-button"
type="primary" type="primary"
@ -132,7 +142,7 @@
plain plain
round round
size="small" size="small"
:disabled="installed.status === 'Upgrading'" :disabled="installed.status === 'Running'"
@click="openOperate(installed, 'upgrade')" @click="openOperate(installed, 'upgrade')"
v-if="mode === 'upgrade'" v-if="mode === 'upgrade'"
> >
@ -210,6 +220,7 @@ import Status from '@/components/status/index.vue';
import { getAge } from '@/utils/util'; import { getAge } from '@/utils/util';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { toFolder } from '@/global/business';
const data = ref<any>(); const data = ref<any>();
const loading = ref(false); const loading = ref(false);
@ -392,7 +403,7 @@ const buttons = [
{ {
label: i18n.global.t('app.params'), label: i18n.global.t('app.params'),
click: (row: any) => { click: (row: any) => {
openParam(row.id); openParam(row);
}, },
disabled: (row: any) => { disabled: (row: any) => {
return row.status === 'DownloadErr' || row.status === 'Upgrading'; return row.status === 'DownloadErr' || row.status === 'Upgrading';
@ -418,8 +429,8 @@ const openUploads = (key: string, name: string) => {
uploadRef.value.acceptParams(params); uploadRef.value.acceptParams(params);
}; };
const openParam = (installId: number) => { const openParam = (row: any) => {
appParamRef.value.acceptParams({ id: installId }); appParamRef.value.acceptParams({ app: row.app, id: row.id });
}; };
const isAppErr = (row: any) => { const isAppErr = (row: any) => {