fix: 创建编排改为异步操作 (#318)

fix:  创建编排改为异步操作
This commit is contained in:
ssongliu 2023-03-20 18:16:26 +08:00 committed by GitHub
parent 2096049708
commit 0c5a5a6454
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 45 deletions

View file

@ -90,11 +90,12 @@ func (b *BaseApi) CreateCompose(c *gin.Context) {
return return
} }
if err := containerService.CreateCompose(req); err != nil { log, err := containerService.CreateCompose(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, log)
} }
// @Tags Container Compose // @Tags Container Compose

View file

@ -33,7 +33,7 @@ type IContainerService interface {
PageVolume(req dto.SearchWithPage) (int64, interface{}, error) PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error) ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error) PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) error CreateCompose(req dto.ComposeCreate) (string, error)
ComposeOperation(req dto.ComposeOperation) error ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerCreate) error ContainerCreate(req dto.ContainerCreate) error
ContainerOperation(req dto.ContainerOperation) error ContainerOperation(req dto.ContainerOperation) error

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"path" "path"
"sort" "sort"
"strings" "strings"
@ -124,11 +125,11 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
return int64(total), BackDatas, nil return int64(total), BackDatas, nil
} }
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error { func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
if req.From == "template" { if req.From == "template" {
template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) template, err := composeRepo.Get(commonRepo.WithByID(req.Template))
if err != nil { if err != nil {
return err return "", err
} }
req.From = "edit" req.From = "edit"
req.File = template.Content req.File = template.Content
@ -137,14 +138,14 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name) dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name)
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil { if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return err return "", err
} }
} }
path := fmt.Sprintf("%s/docker-compose.yml", dir) path := fmt.Sprintf("%s/docker-compose.yml", dir)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil { if err != nil {
return err return "", err
} }
defer file.Close() defer file.Close()
write := bufio.NewWriter(file) write := bufio.NewWriter(file)
@ -157,13 +158,28 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
if req.From == "path" { if req.From == "path" {
req.Name = path.Base(strings.ReplaceAll(req.Path, "/docker-compose.yml", "")) req.Name = path.Base(strings.ReplaceAll(req.Path, "/docker-compose.yml", ""))
} }
if stdout, err := compose.Up(req.Path); err != nil { logName := strings.ReplaceAll(req.Path, "docker-compose.yml", "compose.log")
_, _ = compose.Down(req.Path) file, err := os.OpenFile(logName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
return errors.New(stdout) if err != nil {
return "", err
} }
go func() {
defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
stdout, err := cmd.CombinedOutput()
_, _ = file.Write(stdout)
if err != nil {
global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err)
_, _ = compose.Down(req.Path)
_, _ = file.WriteString("docker-compose up failed!")
return
}
global.LOG.Infof("docker-compose up %s successful!", req.Name)
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name}) _ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
_, _ = file.WriteString("docker-compose up successful!")
}()
return nil return logName, nil
} }
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error { func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {

View file

@ -7,7 +7,7 @@ import (
) )
func Up(filePath string) (string, error) { func Up(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s up -d --quiet-pull", filePath) stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
return stdout, err return stdout, err
} }

View file

@ -117,7 +117,7 @@ export const searchCompose = (params: SearchWithPage) => {
return http.post<ResPage<Container.ComposeInfo>>(`/containers/compose/search`, params); return http.post<ResPage<Container.ComposeInfo>>(`/containers/compose/search`, params);
}; };
export const upCompose = (params: Container.ComposeCreate) => { export const upCompose = (params: Container.ComposeCreate) => {
return http.post(`/containers/compose`, params, 600000); return http.post<string>(`/containers/compose`, params, 600000);
}; };
export const composeOperator = (params: Container.ComposeOpration) => { export const composeOperator = (params: Container.ComposeOpration) => {
return http.post(`/containers/compose/operate`, params); return http.post(`/containers/compose/operate`, params);

View file

@ -1,12 +1,18 @@
<template> <template>
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%"> <el-drawer
v-model="drawerVisiable"
@close="handleClose"
:destroy-on-close="true"
:close-on-click-modal="false"
size="50%"
>
<template #header> <template #header>
<DrawerHeader :header="$t('container.compose')" :back="handleClose" /> <DrawerHeader :header="$t('container.compose')" :back="handleClose" />
</template> </template>
<div v-loading="loading"> <div>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" label-width="80px">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col :span="22"> <el-col :span="22">
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" label-width="80px">
<el-form-item :label="$t('container.from')"> <el-form-item :label="$t('container.from')">
<el-radio-group v-model="form.from"> <el-radio-group v-model="form.from">
<el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio> <el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio>
@ -46,7 +52,7 @@
placeholder="#Define or paste the content of your docker-compose file here" placeholder="#Define or paste the content of your docker-compose file here"
:indent-with-tab="true" :indent-with-tab="true"
:tabSize="4" :tabSize="4"
style="width: 100%; height: calc(100vh - 340px)" style="width: 100%; height: 200px"
:lineWrapping="true" :lineWrapping="true"
:matchBrackets="true" :matchBrackets="true"
theme="cobalt" theme="cobalt"
@ -55,16 +61,32 @@
v-model="form.file" v-model="form.file"
/> />
</el-form-item> </el-form-item>
</el-form>
<codemirror
v-if="logVisiable"
:autofocus="true"
placeholder="Waiting for build output..."
:indent-with-tab="true"
:tabSize="4"
style="max-height: calc(100vh - 537px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
@ready="handleReady"
v-model="logInfo"
:readOnly="true"
/>
</el-col> </el-col>
</el-row> </el-row>
</el-form>
</div> </div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button :disabled="loading" @click="drawerVisiable = false"> <el-button @click="drawerVisiable = false">
{{ $t('commons.button.cancel') }} {{ $t('commons.button.cancel') }}
</el-button> </el-button>
<el-button type="primary" :disabled="loading" @click="onSubmit(formRef)"> <el-button type="primary" :disabled="buttonDisabled" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@ -73,7 +95,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from 'vue'; import { nextTick, reactive, ref, shallowRef } from 'vue';
import FileList from '@/components/file-list/index.vue'; import FileList from '@/components/file-list/index.vue';
import { Codemirror } from 'vue-codemirror'; import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
@ -83,23 +105,34 @@ import i18n from '@/lang';
import { ElForm } from 'element-plus'; import { ElForm } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { listComposeTemplate, upCompose } from '@/api/modules/container'; import { listComposeTemplate, upCompose } from '@/api/modules/container';
import { MsgSuccess } from '@/utils/message';
import { loadBaseDir } from '@/api/modules/setting'; import { loadBaseDir } from '@/api/modules/setting';
import { LoadFile } from '@/api/modules/files';
import { formatImageStdout } from '@/utils/docker';
const extensions = [javascript(), oneDark]; const extensions = [javascript(), oneDark];
const view = shallowRef();
const handleReady = (payload) => {
view.value = payload.view;
};
const logVisiable = ref();
const logInfo = ref();
const drawerVisiable = ref(false); const drawerVisiable = ref(false);
const templateOptions = ref(); const templateOptions = ref();
const buttonDisabled = ref(false);
const loading = ref(false);
const baseDir = ref(); const baseDir = ref();
const composeFile = ref(); const composeFile = ref();
let timer: NodeJS.Timer | null = null;
const varifyPath = (rule: any, value: any, callback: any) => { const varifyPath = (rule: any, value: any, callback: any) => {
if (value.indexOf('docker-compose.yml') === -1) { if (value.indexOf('docker-compose.yml') === -1) {
callback(new Error(i18n.global.t('commons.rule.selectHelper', ['docker-compose.yml']))); callback(new Error(i18n.global.t('commons.rule.selectHelper', ['docker-compose.yml'])));
} }
callback(); callback();
}; };
const form = reactive({ const form = reactive({
name: '', name: '',
from: 'edit', from: 'edit',
@ -126,12 +159,17 @@ const acceptParams = (): void => {
form.from = 'edit'; form.from = 'edit';
form.path = ''; form.path = '';
form.file = ''; form.file = '';
logVisiable.value = false;
logInfo.value = '';
loadTemplates(); loadTemplates();
loadPath(); loadPath();
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => { const handleClose = () => {
emit('search');
clearInterval(Number(timer));
timer = null;
drawerVisiable.value = false; drawerVisiable.value = false;
}; };
@ -152,18 +190,36 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
loading.value = true; const res = await upCompose(form);
upCompose(form) logInfo.value = '';
.then(() => { buttonDisabled.value = true;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); logVisiable.value = true;
loading.value = false; loadLogs(res.data);
emit('search'); });
drawerVisiable.value = false; };
})
.finally(() => { const loadLogs = async (path: string) => {
loading.value = false; timer = setInterval(async () => {
if (logVisiable.value) {
const res = await LoadFile({ path: path });
logInfo.value = formatImageStdout(res.data);
nextTick(() => {
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
}); });
}); });
if (
logInfo.value.endsWith('docker-compose up failed!') ||
logInfo.value.endsWith('docker-compose up successful!')
) {
clearInterval(Number(timer));
timer = null;
buttonDisabled.value = false;
}
}
}, 1000 * 3);
}; };
const loadDir = async (path: string) => { const loadDir = async (path: string) => {

View file

@ -2,7 +2,7 @@
<el-drawer <el-drawer
v-model="drawerVisiable" v-model="drawerVisiable"
:destroy-on-close="true" :destroy-on-close="true"
@close="onCloseLog" @close="handleClose"
:close-on-click-modal="false" :close-on-click-modal="false"
size="50%" size="50%"
> >
@ -141,6 +141,9 @@ const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => { const handleClose = () => {
drawerVisiable.value = false; drawerVisiable.value = false;
emit('search');
clearInterval(Number(timer));
timer = null;
}; };
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
@ -181,11 +184,6 @@ const loadLogs = async (path: string) => {
} }
}, 1000 * 3); }, 1000 * 3);
}; };
const onCloseLog = async () => {
emit('search');
clearInterval(Number(timer));
timer = null;
};
const loadBuildDir = async (path: string) => { const loadBuildDir = async (path: string) => {
form.dockerfile = path; form.dockerfile = path;