feat: runtime 增加删除功能

This commit is contained in:
zhengkunwang223
2023-03-31 14:02:28 +08:00
committed by zhengkunwang223
parent 64a954df53
commit 22d9bdacf6
19 changed files with 217 additions and 43 deletions

View File

@@ -83,6 +83,15 @@ func SuccessWithData(ctx *gin.Context, data interface{}) {
ctx.Abort() 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) { func SuccessWithMsg(ctx *gin.Context, msg string) {
res := dto.Response{ res := dto.Response{
Code: constant.CodeSuccess, Code: constant.CodeSuccess,

View File

@@ -52,5 +52,28 @@ func (b *BaseApi) CreateRuntime(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return 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)
} }

View File

@@ -17,3 +17,7 @@ type RuntimeCreate struct {
Type string `json:"type"` Type string `json:"type"`
Version string `json:"version"` Version string `json:"version"`
} }
type RuntimeDelete struct {
ID uint `json:"Id"`
}

View File

@@ -13,6 +13,7 @@ type IRuntimeRepo interface {
Create(ctx context.Context, runtime *model.Runtime) error Create(ctx context.Context, runtime *model.Runtime) error
Save(runtime *model.Runtime) error Save(runtime *model.Runtime) error
DeleteBy(opts ...DBOption) error DeleteBy(opts ...DBOption) error
GetFirst(opts ...DBOption) (*model.Runtime, error)
} }
func NewIRunTimeRepo() IRuntimeRepo { func NewIRunTimeRepo() IRuntimeRepo {
@@ -40,3 +41,11 @@ func (r *RuntimeRepo) Save(runtime *model.Runtime) error {
func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error { func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.Runtime{}).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
}

View File

@@ -2,6 +2,7 @@ package service
import ( import (
"context" "context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "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/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
@@ -12,6 +13,8 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/subosito/gotenv" "github.com/subosito/gotenv"
"path" "path"
"path/filepath"
"time"
) )
type RuntimeService struct { type RuntimeService struct {
@@ -20,6 +23,7 @@ type RuntimeService struct {
type IRuntimeService interface { type IRuntimeService interface {
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
Create(create request.RuntimeCreate) error Create(create request.RuntimeCreate) error
Delete(id uint) error
} }
func NewRuntimeService() IRuntimeService { func NewRuntimeService() IRuntimeService {
@@ -50,21 +54,25 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
if !fileOp.Stat(buildDir) { if !fileOp.Stat(buildDir) {
return buserr.New(constant.ErrDirNotFound) 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 { if err := fileOp.CopyDir(buildDir, tempDir); err != nil {
return err return err
} }
oldDir := path.Join(tempDir, "build") oldDir := path.Join(tempDir, "build")
newNameDir := path.Join(tempDir, create.Name) newNameDir := path.Join(runtimeDir, create.Name)
defer func(defErr *error) { defer func() {
if defErr != nil { if err != nil {
_ = fileOp.DeleteDir(newNameDir) _ = fileOp.DeleteDir(newNameDir)
} }
}(&err) }()
if oldDir != newNameDir { if oldDir != newNameDir {
if err := fileOp.Rename(oldDir, newNameDir); err != nil { if err := fileOp.Rename(oldDir, newNameDir); err != nil {
return err return err
} }
if err := fileOp.DeleteDir(tempDir); err != nil {
return err
}
} }
composeFile, err := fileOp.GetContent(path.Join(newNameDir, "docker-compose.yml")) composeFile, err := fileOp.GetContent(path.Join(newNameDir, "docker-compose.yml"))
if err != nil { if err != nil {
@@ -95,9 +103,6 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
return err return err
} }
composeService.SetProject(project) composeService.SetProject(project)
if err := composeService.ComposeBuild(); err != nil {
return err
}
runtime := &model.Runtime{ runtime := &model.Runtime{
Name: create.Name, Name: create.Name,
DockerCompose: string(composeFile), DockerCompose: string(composeFile),
@@ -106,9 +111,14 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
Type: create.Type, Type: create.Type,
Image: create.Image, Image: create.Image,
Resource: create.Resource, 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) { 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 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))
}

View File

@@ -1 +1,19 @@
package service 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)
}

View File

@@ -104,9 +104,9 @@ var (
ErrObjectInUsed = "ErrObjectInUsed" ErrObjectInUsed = "ErrObjectInUsed"
) )
//runtime // runtime
var ( var (
ErrDirNotFound = "ErrDirNotFound" ErrDirNotFound = "ErrDirNotFound"
ErrFileNotExist = "ErrFileNotExist" ErrFileNotExist = "ErrFileNotExist"
ErrImageBuildErr = "ErrImageBuildErr"
) )

View File

@@ -1,10 +1,10 @@
package constant package constant
const ( const (
ResourceLocal = "Local" ResourceLocal = "local"
ResourceAppstore = "Appstore" ResourceAppstore = "appstore"
RuntimeNormal = "Normal" RuntimeNormal = "normal"
RuntimeBuildSuccess = "BuildSuccess" RuntimeError = "error"
RuntimeBuildFailed = "BuildFailed" RuntimeBuildIng = "building"
) )

View File

@@ -63,3 +63,4 @@ ErrObjectInUsed: "This object is in use and cannot be deleted"
#runtime #runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity" ErrDirNotFound: "The build folder does not exist! Please check file integrity"
ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity" ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity"
ErrImageBuildErr: "Image build failed"

View File

@@ -63,3 +63,4 @@ ErrObjectInUsed: "该对象正被使用,无法删除"
#runtime #runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!" ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!" ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
ErrImageBuildErr: "镜像 build 失败"

View File

@@ -17,5 +17,6 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) {
{ {
groupRouter.POST("/search", baseApi.SearchRuntimes) groupRouter.POST("/search", baseApi.SearchRuntimes)
groupRouter.POST("", baseApi.CreateRuntime) groupRouter.POST("", baseApi.CreateRuntime)
groupRouter.POST("/del", baseApi.DeleteRuntime)
} }
} }

View File

@@ -29,4 +29,8 @@ export namespace Runtime {
appId?: number; appId?: number;
version?: string; version?: string;
} }
export interface RuntimeDelete {
id: number;
}
} }

View File

@@ -9,3 +9,7 @@ export const SearchRuntimes = (req: Runtime.RuntimeReq) => {
export const CreateRuntime = (req: Runtime.RuntimeCreate) => { export const CreateRuntime = (req: Runtime.RuntimeCreate) => {
return http.post<any>(`/runtimes`, req); return http.post<any>(`/runtimes`, req);
}; };
export const DeleteRuntime = (req: Runtime.RuntimeDelete) => {
return http.post<any>(`/runtimes/del`, req);
};

View File

@@ -199,6 +199,8 @@ const message = {
exited: 'Exited', exited: 'Exited',
enabled: 'Enabled', enabled: 'Enabled',
disabled: 'Disabled', disabled: 'Disabled',
normal: 'Normal',
building: 'Building',
}, },
}, },
menu: { menu: {
@@ -1237,7 +1239,16 @@ const message = {
runtime: { runtime: {
runtime: 'Runtime', runtime: 'Runtime',
image: 'Image', 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',
}, },
}; };

View File

@@ -203,6 +203,8 @@ const message = {
installing: '安装中', installing: '安装中',
enabled: '已启用', enabled: '已启用',
disabled: '已停止', disabled: '已停止',
normal: '正常',
building: '制作镜像中',
}, },
}, },
menu: { menu: {
@@ -1231,11 +1233,12 @@ const message = {
create: '创建运行环境', create: '创建运行环境',
name: '名称', name: '名称',
resource: '来源', resource: '来源',
appStore: '应用商店', appstore: '应用商店',
local: '本地', local: '本地',
app: '应用', app: '应用',
localHelper: '本地运行环境需要自行安装', localHelper: '本地运行环境需要自行安装',
version: '版本', version: '版本',
status: '状态',
}, },
}; };
export default { export default {

View File

@@ -226,3 +226,7 @@ export function isJson(str: string) {
return false; return false;
} }
} }
export function toLowerCase(str: string) {
return str.toLowerCase();
}

View File

@@ -22,7 +22,7 @@
@change="changeResource(runtimeCreate.resource)" @change="changeResource(runtimeCreate.resource)"
> >
<el-radio :label="'AppStore'" :value="'AppStore'"> <el-radio :label="'AppStore'" :value="'AppStore'">
{{ $t('runtime.appStore') }} {{ $t('runtime.appstore') }}
</el-radio> </el-radio>
<el-radio :label="'Local'" :value="'Local'"> <el-radio :label="'Local'" :value="'Local'">
{{ $t('runtime.local') }} {{ $t('runtime.local') }}
@@ -135,6 +135,7 @@ const changeResource = (resource: string) => {
runtimeCreate.value.image = ''; runtimeCreate.value.image = '';
} else { } else {
runtimeCreate.value.version = ''; 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(); searchApp();
open.value = true; open.value = true;
}; };

View File

@@ -21,30 +21,64 @@
<Tooltip :text="row.name" /> <Tooltip :text="row.name" />
</template> </template>
</el-table-column> </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.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 <el-table-column
prop="createdAt" prop="createdAt"
:label="$t('commons.table.date')" :label="$t('commons.table.date')"
:formatter="dateFormat" :formatter="dateFormat"
show-overflow-tooltip show-overflow-tooltip
/> />
<fu-table-operations
:ellipsis="10"
width="260px"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable> </ComplexTable>
</template> </template>
</LayoutContent> </LayoutContent>
<CreateRuntime ref="createRef" /> <CreateRuntime ref="createRef" @close="search" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime'; import { Runtime } from '@/api/interface/runtime';
import { SearchRuntimes } from '@/api/modules/runtime'; import { DeleteRuntime, SearchRuntimes } from '@/api/modules/runtime';
import { dateFormat } from '@/utils/util'; import { dateFormat, toLowerCase } from '@/utils/util';
import RouterButton from '@/components/router-button/index.vue'; import RouterButton from '@/components/router-button/index.vue';
import ComplexTable from '@/components/complex-table/index.vue'; import ComplexTable from '@/components/complex-table/index.vue';
import LayoutContent from '@/layout/layout-content.vue'; import LayoutContent from '@/layout/layout-content.vue';
import CreateRuntime from '@/views/website/runtime/create/index.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({ const paginationConfig = reactive({
currentPage: 1, currentPage: 1,
@@ -56,6 +90,15 @@ let req = reactive<Runtime.RuntimeReq>({
page: 1, page: 1,
pageSize: 15, pageSize: 15,
}); });
const buttons = [
{
label: i18n.global.t('commons.button.delete'),
click: function (row: Runtime.Runtime) {
openDelete(row);
},
},
];
const loading = ref(false); const loading = ref(false);
const items = ref<Runtime.RuntimeDTO[]>([]); const items = ref<Runtime.RuntimeDTO[]>([]);
const createRef = ref(); const createRef = ref();
@@ -75,7 +118,12 @@ const search = async () => {
}; };
const openCreate = () => { 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(() => { onMounted(() => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div v-for="(p, index) in paramObjs" :key="index"> <div v-for="(p, index) in paramObjs" :key="index">
<el-form-item :label="getLabel(p)" :prop="p.prop"> <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 <el-option
v-for="service in p.values" v-for="service in p.values"
:key="service.label" :key="service.label"
@@ -15,7 +15,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { App } from '@/api/interface/app'; 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 { computed, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -47,7 +47,7 @@ const props = defineProps({
}); });
let form = reactive({}); let form = reactive({});
// let rules = reactive({}); let rules = reactive({});
const params = computed({ const params = computed({
get() { get() {
return props.params; return props.params;
@@ -58,7 +58,7 @@ const params = computed({
const paramObjs = ref<ParamObj[]>([]); const paramObjs = ref<ParamObj[]>([]);
const handleParams = () => { const handleParams = () => {
// rules = props.rules; rules = props.rules;
form = props.form; form = props.form;
if (params.value != undefined && params.value.formFields != undefined) { if (params.value != undefined && params.value.formFields != undefined) {
for (const p of params.value.formFields) { for (const p of params.value.formFields) {
@@ -67,15 +67,15 @@ const handleParams = () => {
pObj.disabled = p.disabled; pObj.disabled = p.disabled;
form[p.envKey] = p.default; form[p.envKey] = p.default;
paramObjs.value.push(pObj); paramObjs.value.push(pObj);
// if (p.required) { console.log(p);
// if (p.type === 'select') { if (p.required) {
// rules[p.envKey] = [Rules.requiredSelect]; if (p.type === 'select') {
// } else { rules[p.envKey] = [Rules.requiredSelect];
// rules[p.envKey] = [Rules.requiredInput]; } else {
// } rules[p.envKey] = [Rules.requiredInput];
// } }
}
} }
console.log(form);
} }
}; };