1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +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/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"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"}
func (b *BaseApi) SyncApp(c *gin.Context) {
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() {
global.LOG.Infof("sync app list start ...")
if err := appService.SyncAppListFromRemote(); err != nil {

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@ ErrRepoNotValid: "Remote repository verification failed"
ErrNameIsExist: "Name is already exist"
ErrDemoEnvironment: "Demo server, prohibit this operation!"
ErrCmdTimeout: "Command execution timed out"
ErrInitUser: "The user initialization has been completed by the system. Please do not repeat the operation!"
#app
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 }}'
ErrFileParse: 'Application docker-compose file parsing failed!'
ErrInstallDirNotFound: 'installation directory does not exist'
AppStoreIsUpToDate: 'The app store is already up to date'
#file
ErrFileCanNotRead: "File can not read"
@ -46,8 +46,6 @@ ErrDomainIsExist: "Domain is already exist"
ErrAliasIsExist: "Alias is already exist"
ErrAppDelete: 'Other Website use this App'
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
ErrUsernameIsExist: 'Username is already exist'
ErrUsernameIsNotExist: 'Username is not exist'
#ssl
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
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrPortRules: "Invalid port specification"
#runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity"

View File

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

View File

@ -112,6 +112,7 @@ export namespace App {
message: string;
icon: string;
canUpdate: boolean;
path: string;
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:
'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',
toFolder: 'Open the installation directory',
},
website: {
website: 'Website',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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