feat: 增加已安装应用操作

This commit is contained in:
zhengkunwang223 2022-09-26 18:20:21 +08:00 committed by zhengkunwang223
parent f7263934f9
commit 4830839c91
11 changed files with 147 additions and 8 deletions

View File

@ -68,7 +68,7 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := appService.Install(req.AppDetailId, req.Params); err != nil {
if err := appService.Install(req.Name, req.AppDetailId, req.Params); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@ -92,3 +92,17 @@ func (b *BaseApi) PageInstalled(c *gin.Context) {
Total: total,
})
}
func (b *BaseApi) InstallOperate(c *gin.Context) {
var req dto.AppInstallOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := appService.Operate(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -66,6 +66,7 @@ type AppRequest struct {
type AppInstallRequest struct {
AppDetailId uint `json:"appDetailId" validate:"required"`
Params map[string]interface{} `json:"params"`
Name string `json:"name" validate:"required"`
}
type AppInstalled struct {
@ -79,3 +80,16 @@ type AppInstalled struct {
type AppInstalledRequest struct {
PageInfo
}
type AppOperate string
var (
Up AppOperate = "up"
Down AppOperate = "down"
Restart AppOperate = "restart"
)
type AppInstallOperate struct {
InstallId uint `json:"installId" validate:"required"`
Operate AppOperate `json:"operate" validate:"required"`
}

View File

@ -2,6 +2,7 @@ package model
type AppInstall struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null"`
ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
Version string `json:"version" gorm:"type:varchar(256);not null"`
AppId uint `json:"appId" gorm:"type:integer;not null"`

View File

@ -13,7 +13,7 @@ func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
db = opt(db)
}
var install []model.AppInstall
err := db.Find(&install).Error
err := db.Preload("App").Find(&install).Error
return install, err
}

View File

@ -3,6 +3,7 @@ package service
import (
"encoding/base64"
"encoding/json"
"errors"
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/app/repo"
@ -147,7 +148,51 @@ func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO,
return appDetailDTO, nil
}
func (a AppService) Install(appDetailId uint, params map[string]interface{}) error {
func (a AppService) Operate(req dto.AppInstallOperate) error {
appInstall, err := appInstallRepo.GetBy(commonRepo.WithByID(req.InstallId))
if err != nil {
return err
}
if len(appInstall) == 0 {
return errors.New("not found")
}
install := appInstall[0]
dockerComposePath := path.Join(global.CONF.System.AppDir, install.App.Key, install.ContainerName, "docker-compose.yml")
switch req.Operate {
case dto.Up:
out, err := compose.Up(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
case dto.Down:
out, err := compose.Down(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
case dto.Restart:
out, err := compose.Restart(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
default:
return errors.New("operate not support")
}
return nil
}
func handleErr(install model.AppInstall, err error, out string) error {
reErr := err
install.Message = err.Error()
if out != "" {
install.Message = out
reErr = errors.New(out)
}
_ = appInstallRepo.Save(install)
return reErr
}
func (a AppService) Install(name string, appDetailId uint, params map[string]interface{}) error {
appDetail, err := appDetailRepo.GetAppDetail(commonRepo.WithByID(appDetailId))
if err != nil {
return err
@ -159,6 +204,7 @@ func (a AppService) Install(appDetailId uint, params map[string]interface{}) err
}
containerName := constant.ContainerPrefix + app.Key + "-" + common.RandStr(6)
appInstall := model.AppInstall{
Name: name,
AppId: appDetail.AppId,
AppDetailId: appDetail.ID,
Version: appDetail.Version,

View File

@ -21,5 +21,6 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.POST("/installed", baseApi.PageInstalled)
appRouter.POST("/installed/op", baseApi.InstallOperate)
}
}

View File

@ -62,6 +62,7 @@ export namespace App {
}
export interface AppInstalled extends CommonModel {
name: string;
containerName: string;
version: string;
appId: string;
@ -75,4 +76,9 @@ export namespace App {
ready: number;
icon: string;
}
export interface AppInstalledOp {
installId: number;
operate: string;
}
}

View File

@ -25,3 +25,7 @@ export const InstallApp = (install: App.AppInstall) => {
export const GetAppInstalled = (info: ReqPage) => {
return http.post<ResPage<App.AppInstalled>>('apps/installed', info);
};
export const InstalledOp = (op: App.AppInstalledOp) => {
return http.post<any>('apps/installed/op', op);
};

View File

@ -61,6 +61,7 @@ export default {
createSuccess: '新建成功',
updateSuccess: '更新成功',
uploadSuccess: '上传成功',
operate: '操作',
},
login: {
captchaHelper: '请输入验证码',
@ -403,5 +404,7 @@ export default {
restart: '重启',
up: '启动',
down: '停止',
name: '名称',
description: '描述',
},
};

View File

@ -1,6 +1,9 @@
<template>
<el-dialog v-model="open" :title="$t('app.install')" width="30%">
<el-form ref="paramForm" label-position="left" :model="form" label-width="150px" :rules="rules">
<el-form-item :label="$t('app.name')" prop="name">
<el-input v-model="req.name"></el-input>
</el-form-item>
<div v-for="(f, index) in installData.params?.formFields" :key="index">
<el-form-item :label="f.labelZh" :prop="f.envKey">
<el-input
@ -44,12 +47,15 @@ const installData = ref<InstallRrops>({
});
let open = ref(false);
let form = reactive<{ [key: string]: any }>({});
let rules = reactive<FormRules>({});
let rules = reactive<FormRules>({
name: [Rules.requiredInput],
});
let loading = false;
const paramForm = ref<FormInstance>();
const req = reactive({
appDetailId: 0,
params: {},
name: '',
});
const em = defineEmits(['close']);
const handleClose = () => {

View File

@ -1,5 +1,7 @@
<template>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" v-loading="loading">
<el-table-column :label="$t('app.name')" prop="name"></el-table-column>
<el-table-column :label="$t('app.description')" prop="description"></el-table-column>
<el-table-column :label="$t('app.appName')" prop="appName"></el-table-column>
<el-table-column :label="$t('app.version')" prop="version"></el-table-column>
<el-table-column :label="$t('app.container')">
@ -9,7 +11,18 @@
</el-table-column>
<el-table-column :label="$t('app.status')">
<template #default="{ row }">
<el-tag>{{ row.status }}</el-tag>
<el-popover
v-if="row.status === 'Error'"
placement="top-start"
:width="400"
trigger="hover"
:content="row.message"
>
<template #reference>
<el-tag type="error">{{ row.status }}</el-tag>
</template>
</el-popover>
<el-tag v-else>{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column
@ -23,14 +36,15 @@
</template>
<script lang="ts" setup>
import { GetAppInstalled } from '@/api/modules/app';
import { GetAppInstalled, InstalledOp } from '@/api/modules/app';
import { onMounted, reactive, ref } from 'vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util';
import i18n from '@/lang';
import { ElMessageBox } from 'element-plus';
let data = ref<any>();
let loading = ref(false);
const paginationConfig = reactive({
currentPage: 1,
pageSize: 20,
@ -49,15 +63,45 @@ const search = () => {
});
};
const operate = async (row: any, op: string) => {
const req = {
installId: row.id,
operate: op,
};
ElMessageBox.confirm(i18n.global.t(`${'app.' + op}`) + '?', i18n.global.t('commons.msg.operate'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'warning',
draggable: true,
}).then(async () => {
loading.value = true;
InstalledOp(req)
.then(() => {})
.finally(() => {
loading.value = false;
});
});
};
const buttons = [
{
label: i18n.global.t('app.restart'),
click: (row: any) => {
operate(row, 'restart');
},
},
{
label: i18n.global.t('app.up'),
click: (row: any) => {
operate(row, 'up');
},
},
{
label: i18n.global.t('app.down'),
click: (row: any) => {
operate(row, 'down');
},
},
];