feat: runtime 增加删除功能
This commit is contained in:

committed by
zhengkunwang223

parent
64a954df53
commit
22d9bdacf6
@@ -83,6 +83,15 @@ func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func SuccessWithOutData(ctx *gin.Context) {
|
||||
res := dto.Response{
|
||||
Code: constant.CodeSuccess,
|
||||
Message: "success",
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func SuccessWithMsg(ctx *gin.Context, msg string) {
|
||||
res := dto.Response{
|
||||
Code: constant.CodeSuccess,
|
||||
|
@@ -52,5 +52,28 @@ func (b *BaseApi) CreateRuntime(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Delete runtime
|
||||
// @Description 删除运行环境
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"删除网站 [name]","formatEN":"Delete website [name]"}
|
||||
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
|
||||
var req request.RuntimeDelete
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
err := runtimeService.Delete(req.ID)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
@@ -17,3 +17,7 @@ type RuntimeCreate struct {
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type RuntimeDelete struct {
|
||||
ID uint `json:"Id"`
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ type IRuntimeRepo interface {
|
||||
Create(ctx context.Context, runtime *model.Runtime) error
|
||||
Save(runtime *model.Runtime) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
GetFirst(opts ...DBOption) (*model.Runtime, error)
|
||||
}
|
||||
|
||||
func NewIRunTimeRepo() IRuntimeRepo {
|
||||
@@ -40,3 +41,11 @@ func (r *RuntimeRepo) Save(runtime *model.Runtime) error {
|
||||
func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.Runtime{}).Error
|
||||
}
|
||||
|
||||
func (r *RuntimeRepo) GetFirst(opts ...DBOption) (*model.Runtime, error) {
|
||||
var runtime model.Runtime
|
||||
if err := getDb(opts...).First(&runtime).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtime, nil
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/subosito/gotenv"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RuntimeService struct {
|
||||
@@ -20,6 +23,7 @@ type RuntimeService struct {
|
||||
type IRuntimeService interface {
|
||||
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
|
||||
Create(create request.RuntimeCreate) error
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
func NewRuntimeService() IRuntimeService {
|
||||
@@ -50,21 +54,25 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
|
||||
if !fileOp.Stat(buildDir) {
|
||||
return buserr.New(constant.ErrDirNotFound)
|
||||
}
|
||||
tempDir := path.Join(constant.RuntimeDir, app.Key)
|
||||
runtimeDir := path.Join(constant.RuntimeDir, create.Type)
|
||||
tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||
if err := fileOp.CopyDir(buildDir, tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
oldDir := path.Join(tempDir, "build")
|
||||
newNameDir := path.Join(tempDir, create.Name)
|
||||
defer func(defErr *error) {
|
||||
if defErr != nil {
|
||||
newNameDir := path.Join(runtimeDir, create.Name)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.DeleteDir(newNameDir)
|
||||
}
|
||||
}(&err)
|
||||
}()
|
||||
if oldDir != newNameDir {
|
||||
if err := fileOp.Rename(oldDir, newNameDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.DeleteDir(tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
composeFile, err := fileOp.GetContent(path.Join(newNameDir, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
@@ -95,9 +103,6 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
|
||||
return err
|
||||
}
|
||||
composeService.SetProject(project)
|
||||
if err := composeService.ComposeBuild(); err != nil {
|
||||
return err
|
||||
}
|
||||
runtime := &model.Runtime{
|
||||
Name: create.Name,
|
||||
DockerCompose: string(composeFile),
|
||||
@@ -106,9 +111,14 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
|
||||
Type: create.Type,
|
||||
Image: create.Image,
|
||||
Resource: create.Resource,
|
||||
Status: constant.RuntimeNormal,
|
||||
Status: constant.RuntimeBuildIng,
|
||||
Version: create.Version,
|
||||
}
|
||||
return runtimeRepo.Create(context.Background(), runtime)
|
||||
if err := runtimeRepo.Create(context.Background(), runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
go buildRuntime(runtime, composeService)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) {
|
||||
@@ -130,3 +140,18 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
|
||||
}
|
||||
return total, res, nil
|
||||
}
|
||||
|
||||
func (r *RuntimeService) Delete(id uint) error {
|
||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO 校验网站关联
|
||||
if runtime.Resource == constant.ResourceAppstore {
|
||||
runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
|
||||
if err := files.NewFileOp().DeleteDir(runtimeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return runtimeRepo.DeleteBy(commonRepo.WithByID(id))
|
||||
}
|
||||
|
@@ -1 +1,19 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
)
|
||||
|
||||
func buildRuntime(runtime *model.Runtime, service *docker.ComposeService) {
|
||||
err := service.ComposeBuild()
|
||||
if err != nil {
|
||||
runtime.Status = constant.RuntimeError
|
||||
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
|
||||
} else {
|
||||
runtime.Status = constant.RuntimeNormal
|
||||
}
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
}
|
||||
|
@@ -105,8 +105,8 @@ var (
|
||||
)
|
||||
|
||||
// runtime
|
||||
|
||||
var (
|
||||
ErrDirNotFound = "ErrDirNotFound"
|
||||
ErrFileNotExist = "ErrFileNotExist"
|
||||
ErrImageBuildErr = "ErrImageBuildErr"
|
||||
)
|
||||
|
@@ -1,10 +1,10 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ResourceLocal = "Local"
|
||||
ResourceAppstore = "Appstore"
|
||||
ResourceLocal = "local"
|
||||
ResourceAppstore = "appstore"
|
||||
|
||||
RuntimeNormal = "Normal"
|
||||
RuntimeBuildSuccess = "BuildSuccess"
|
||||
RuntimeBuildFailed = "BuildFailed"
|
||||
RuntimeNormal = "normal"
|
||||
RuntimeError = "error"
|
||||
RuntimeBuildIng = "building"
|
||||
)
|
||||
|
@@ -63,3 +63,4 @@ ErrObjectInUsed: "This object is in use and cannot be deleted"
|
||||
#runtime
|
||||
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
|
||||
ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity!"
|
||||
ErrImageBuildErr: "Image build failed"
|
@@ -63,3 +63,4 @@ ErrObjectInUsed: "该对象正被使用,无法删除"
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
|
||||
ErrImageBuildErr: "镜像 build 失败"
|
@@ -17,5 +17,6 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) {
|
||||
{
|
||||
groupRouter.POST("/search", baseApi.SearchRuntimes)
|
||||
groupRouter.POST("", baseApi.CreateRuntime)
|
||||
groupRouter.POST("/del", baseApi.DeleteRuntime)
|
||||
}
|
||||
}
|
||||
|
@@ -29,4 +29,8 @@ export namespace Runtime {
|
||||
appId?: number;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface RuntimeDelete {
|
||||
id: number;
|
||||
}
|
||||
}
|
||||
|
@@ -9,3 +9,7 @@ export const SearchRuntimes = (req: Runtime.RuntimeReq) => {
|
||||
export const CreateRuntime = (req: Runtime.RuntimeCreate) => {
|
||||
return http.post<any>(`/runtimes`, req);
|
||||
};
|
||||
|
||||
export const DeleteRuntime = (req: Runtime.RuntimeDelete) => {
|
||||
return http.post<any>(`/runtimes/del`, req);
|
||||
};
|
||||
|
@@ -199,6 +199,8 @@ const message = {
|
||||
exited: 'Exited',
|
||||
enabled: 'Enabled',
|
||||
disabled: 'Disabled',
|
||||
normal: 'Normal',
|
||||
building: 'Building',
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
@@ -1237,7 +1239,16 @@ const message = {
|
||||
runtime: {
|
||||
runtime: 'Runtime',
|
||||
image: 'Image',
|
||||
workDir: 'WorkDir',
|
||||
workDir: 'working directory',
|
||||
create: 'Create runtime',
|
||||
name: 'Name',
|
||||
resource: 'Source',
|
||||
appstore: 'App Store',
|
||||
local: 'Local',
|
||||
app: 'Application',
|
||||
localHelper: 'The local operating environment needs to be installed by itself',
|
||||
version: 'Version',
|
||||
status: 'Status',
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -203,6 +203,8 @@ const message = {
|
||||
installing: '安装中',
|
||||
enabled: '已启用',
|
||||
disabled: '已停止',
|
||||
normal: '正常',
|
||||
building: '制作镜像中',
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
@@ -1231,11 +1233,12 @@ const message = {
|
||||
create: '创建运行环境',
|
||||
name: '名称',
|
||||
resource: '来源',
|
||||
appStore: '应用商店',
|
||||
appstore: '应用商店',
|
||||
local: '本地',
|
||||
app: '应用',
|
||||
localHelper: '本地运行环境需要自行安装',
|
||||
version: '版本',
|
||||
status: '状态',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
|
@@ -226,3 +226,7 @@ export function isJson(str: string) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function toLowerCase(str: string) {
|
||||
return str.toLowerCase();
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@
|
||||
@change="changeResource(runtimeCreate.resource)"
|
||||
>
|
||||
<el-radio :label="'AppStore'" :value="'AppStore'">
|
||||
{{ $t('runtime.appStore') }}
|
||||
{{ $t('runtime.appstore') }}
|
||||
</el-radio>
|
||||
<el-radio :label="'Local'" :value="'Local'">
|
||||
{{ $t('runtime.local') }}
|
||||
@@ -135,6 +135,7 @@ const changeResource = (resource: string) => {
|
||||
runtimeCreate.value.image = '';
|
||||
} else {
|
||||
runtimeCreate.value.version = '';
|
||||
searchApp();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -179,7 +180,15 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
});
|
||||
};
|
||||
|
||||
const acceptParams = async () => {
|
||||
const acceptParams = async (type: string) => {
|
||||
runtimeCreate.value = {
|
||||
name: '',
|
||||
appDetailId: undefined,
|
||||
image: '',
|
||||
params: {},
|
||||
type: type,
|
||||
resource: 'AppStore',
|
||||
};
|
||||
searchApp();
|
||||
open.value = true;
|
||||
};
|
||||
|
@@ -21,30 +21,64 @@
|
||||
<Tooltip :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.resource')" prop="resource">
|
||||
<template #default="{ row }">
|
||||
<span>{{ $t('runtime.' + toLowerCase(row.resource)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
|
||||
<el-table-column :label="$t('runtime.image')" prop="image"></el-table-column>
|
||||
<el-table-column :label="$t('runtime.workDir')" prop="workDir"></el-table-column>
|
||||
<el-table-column :label="$t('runtime.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
v-if="row.status === 'error'"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.message"
|
||||
>
|
||||
<template #reference>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div v-else>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="10"
|
||||
width="260px"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<CreateRuntime ref="createRef" />
|
||||
<CreateRuntime ref="createRef" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { DeleteRuntime, SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat, toLowerCase } from '@/utils/util';
|
||||
import RouterButton from '@/components/router-button/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import CreateRuntime from '@/views/website/runtime/create/index.vue';
|
||||
import Status from '@/components/status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
@@ -56,6 +90,15 @@ let req = reactive<Runtime.RuntimeReq>({
|
||||
page: 1,
|
||||
pageSize: 15,
|
||||
});
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
const loading = ref(false);
|
||||
const items = ref<Runtime.RuntimeDTO[]>([]);
|
||||
const createRef = ref();
|
||||
@@ -75,7 +118,12 @@ const search = async () => {
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
createRef.value.acceptParams();
|
||||
createRef.value.acceptParams('php');
|
||||
};
|
||||
|
||||
const openDelete = async (row: Runtime.Runtime) => {
|
||||
await useDeleteData(DeleteRuntime, { id: row.id }, 'commons.msg.delete');
|
||||
search();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-for="(p, index) in paramObjs" :key="index">
|
||||
<el-form-item :label="getLabel(p)" :prop="p.prop">
|
||||
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple">
|
||||
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple" filterable>
|
||||
<el-option
|
||||
v-for="service in p.values"
|
||||
:key="service.label"
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { App } from '@/api/interface/app';
|
||||
// import { Rules } from '@/global/form-rules';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -47,7 +47,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
let form = reactive({});
|
||||
// let rules = reactive({});
|
||||
let rules = reactive({});
|
||||
const params = computed({
|
||||
get() {
|
||||
return props.params;
|
||||
@@ -58,7 +58,7 @@ const params = computed({
|
||||
const paramObjs = ref<ParamObj[]>([]);
|
||||
|
||||
const handleParams = () => {
|
||||
// rules = props.rules;
|
||||
rules = props.rules;
|
||||
form = props.form;
|
||||
if (params.value != undefined && params.value.formFields != undefined) {
|
||||
for (const p of params.value.formFields) {
|
||||
@@ -67,15 +67,15 @@ const handleParams = () => {
|
||||
pObj.disabled = p.disabled;
|
||||
form[p.envKey] = p.default;
|
||||
paramObjs.value.push(pObj);
|
||||
// if (p.required) {
|
||||
// if (p.type === 'select') {
|
||||
// rules[p.envKey] = [Rules.requiredSelect];
|
||||
// } else {
|
||||
// rules[p.envKey] = [Rules.requiredInput];
|
||||
// }
|
||||
// }
|
||||
console.log(p);
|
||||
if (p.required) {
|
||||
if (p.type === 'select') {
|
||||
rules[p.envKey] = [Rules.requiredSelect];
|
||||
} else {
|
||||
rules[p.envKey] = [Rules.requiredInput];
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(form);
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user