fix: 编排创建样式调整、日志改为异步加载 (#794)

This commit is contained in:
ssongliu 2023-04-26 17:00:20 +08:00 committed by GitHub
parent 8a32d8032f
commit 0c09b12680
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 80 deletions

View file

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -154,9 +155,10 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
go func() { go func() {
defer file.Close() defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
stdout, err := cmd.CombinedOutput() multiWriter := io.MultiWriter(os.Stdout, file)
_, _ = file.Write(stdout) cmd.Stdout = multiWriter
if err != nil { cmd.Stderr = multiWriter
if err := cmd.Run(); err != nil {
global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err) global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err)
_, _ = compose.Down(req.Path) _, _ = compose.Down(req.Path)
_, _ = file.WriteString("docker-compose up failed!") _, _ = file.WriteString("docker-compose up failed!")

View file

@ -539,6 +539,7 @@ const message = {
registrieHelper: 'One in a row, for example:\n172.16.10.111:8081 \n172.16.10.112:8081', registrieHelper: 'One in a row, for example:\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: 'Compose', compose: 'Compose',
fromChangeHelper: 'Switching the source will clear the current edited content. Do you want to continue?',
composeHelper: composeHelper:
'The current content has passed the format verification. Please click Submit to complete the creation', 'The current content has passed the format verification. Please click Submit to complete the creation',
composePathHelper: 'Config file save path: {0}', composePathHelper: 'Config file save path: {0}',

View file

@ -556,6 +556,7 @@ const message = {
registrieHelper: '一行一个\n172.16.10.111:8081 \n172.16.10.112:8081', registrieHelper: '一行一个\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: '编排', compose: '编排',
fromChangeHelper: '切换来源将清空当前已编辑内容是否继续',
composeHelper: '当前内容已通过格式验证请点击确认完成创建', composeHelper: '当前内容已通过格式验证请点击确认完成创建',
composePathHelper: '配置文件保存路径: {0}', composePathHelper: '配置文件保存路径: {0}',
apps: '应用商店', apps: '应用商店',

View file

@ -14,7 +14,7 @@
<el-col :span="22"> <el-col :span="22">
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" label-width="80px"> <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" @change="hasChecked = false"> <el-radio-group v-model="form.from" @change="changeFrom">
<el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio> <el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio label="path">{{ $t('container.pathSelect') }}</el-radio> <el-radio label="path">{{ $t('container.pathSelect') }}</el-radio>
<el-radio label="template">{{ $t('container.composeTemplate') }}</el-radio> <el-radio label="template">{{ $t('container.composeTemplate') }}</el-radio>
@ -30,14 +30,20 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item v-if="form.from === 'edit' || form.from === 'template'" prop="name"> <el-form-item v-if="form.from === 'edit' || form.from === 'template'" prop="name">
<el-input @input="changePath" v-model.trim="form.name"> <el-input @input="changePath" v-model.trim="form.name">
<template #prepend>{{ $t('file.dir') }}</template> <template #prepend>{{ $t('file.dir') }}</template>
</el-input> </el-input>
<span class="input-help">{{ $t('container.composePathHelper', [composeFile]) }}</span> <span class="input-help">
{{ $t('container.composePathHelper', [composeFile]) }}
</span>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="form.from === 'template'" prop="template"> <el-form-item v-if="form.from === 'template'" prop="template">
<el-select v-model="form.template" @change="hasChecked = false"> <el-select v-model="form.template" @change="changeTemplate">
<el-option <el-option
v-for="item in templateOptions" v-for="item in templateOptions"
:key="item.id" :key="item.id"
@ -46,13 +52,21 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="form.from === 'edit'"> </el-col>
</el-row>
<el-form-item>
<div v-if="form.from === 'edit' || form.from === 'template'" style="width: 100%">
<el-radio-group v-model="mode" size="small">
<el-radio-button label="edit">{{ $t('commons.button.edit') }}</el-radio-button>
<el-radio-button label="log">{{ $t('commons.button.log') }}</el-radio-button>
</el-radio-group>
<codemirror <codemirror
v-if="mode === 'edit'"
:autofocus="true" :autofocus="true"
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: 250px" style="width: 100%; height: calc(100vh - 375px)"
:lineWrapping="true" :lineWrapping="true"
:matchBrackets="true" :matchBrackets="true"
theme="cobalt" theme="cobalt"
@ -60,41 +74,27 @@
:styleActiveLine="true" :styleActiveLine="true"
:extensions="extensions" :extensions="extensions"
v-model="form.file" v-model="form.file"
:disabled="onCreating"
/>
</div>
<codemirror
v-if="mode === 'log'"
:autofocus="true"
placeholder="Waiting for docker-compose up output..."
:indent-with-tab="true"
:tabSize="4"
style="width: 100%; height: calc(100vh - 375px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
@ready="handleReady"
v-model="logInfo"
:disabled="true"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<codemirror
v-if="logVisiable && form.from !== 'edit'"
:autofocus="true"
placeholder="Waiting for docker-compose up output..."
:indent-with-tab="true"
:tabSize="4"
style="height: calc(100vh - 370px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
@ready="handleReady"
v-model="logInfo"
:readOnly="true"
/>
<codemirror
v-if="logVisiable && form.from === 'edit'"
:autofocus="true"
placeholder="Waiting for docker-compose up output..."
:indent-with-tab="true"
:tabSize="4"
style="height: calc(100vh - 590px)"
: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>
</div> </div>
@ -122,7 +122,7 @@ import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark'; import { oneDark } from '@codemirror/theme-one-dark';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElForm } from 'element-plus'; import { ElForm, ElMessageBox } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { listComposeTemplate, testCompose, upCompose } from '@/api/modules/container'; import { listComposeTemplate, testCompose, upCompose } from '@/api/modules/container';
import { loadBaseDir } from '@/api/modules/setting'; import { loadBaseDir } from '@/api/modules/setting';
@ -132,12 +132,15 @@ import { MsgSuccess } from '@/utils/message';
const loading = ref(); const loading = ref();
const mode = ref('edit');
const onCreating = ref();
const oldFrom = ref('edit');
const extensions = [javascript(), oneDark]; const extensions = [javascript(), oneDark];
const view = shallowRef(); const view = shallowRef();
const handleReady = (payload) => { const handleReady = (payload) => {
view.value = payload.view; view.value = payload.view;
}; };
const logVisiable = ref();
const logInfo = ref(); const logInfo = ref();
const drawerVisiable = ref(false); const drawerVisiable = ref(false);
@ -161,14 +164,12 @@ const form = reactive({
const rules = reactive({ const rules = reactive({
name: [Rules.requiredInput, Rules.imageName], name: [Rules.requiredInput, Rules.imageName],
path: [Rules.requiredSelect], path: [Rules.requiredSelect],
template: [Rules.requiredSelect],
}); });
const loadTemplates = async () => { const loadTemplates = async () => {
const res = await listComposeTemplate(); const res = await listComposeTemplate();
templateOptions.value = res.data; templateOptions.value = res.data;
if (templateOptions.value && templateOptions.value.length !== 0) {
form.template = templateOptions.value[0].id;
}
}; };
const acceptParams = (): void => { const acceptParams = (): void => {
@ -177,7 +178,6 @@ const acceptParams = (): void => {
form.from = 'edit'; form.from = 'edit';
form.path = ''; form.path = '';
form.file = ''; form.file = '';
logVisiable.value = false;
hasChecked.value = false; hasChecked.value = false;
logInfo.value = ''; logInfo.value = '';
loadTemplates(); loadTemplates();
@ -185,6 +185,44 @@ const acceptParams = (): void => {
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
const changeTemplate = () => {
hasChecked.value = false;
for (const item of templateOptions.value) {
if (form.template === item.id) {
form.file = item.content;
break;
}
}
};
const changeFrom = () => {
if ((oldFrom.value === 'edit' || oldFrom.value === 'template') && form.file) {
ElMessageBox.confirm(i18n.global.t('container.fromChangeHelper'), i18n.global.t('container.from'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
})
.then(() => {
hasChecked.value = false;
if (form.from === 'template') {
if (!form.template && templateOptions.value && templateOptions.value.length !== 0) {
form.template = templateOptions.value[0].id;
}
changeTemplate();
}
if (form.from === 'edit') {
form.file = '';
}
oldFrom.value = form.from;
})
.catch(() => {
form.from = oldFrom.value;
});
} else {
oldFrom.value = form.from;
}
};
const handleClose = () => { const handleClose = () => {
emit('search'); emit('search');
clearInterval(Number(timer)); clearInterval(Number(timer));
@ -210,6 +248,7 @@ const onTest = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
loading.value = true; loading.value = true;
logInfo.value = '';
await testCompose(form) await testCompose(form)
.then((res) => { .then((res) => {
loading.value = false; loading.value = false;
@ -228,17 +267,17 @@ 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;
onCreating.value = true;
mode.value = 'log';
const res = await upCompose(form); const res = await upCompose(form);
logInfo.value = ''; logInfo.value = '';
buttonDisabled.value = true; buttonDisabled.value = true;
logVisiable.value = true;
loadLogs(res.data); loadLogs(res.data);
}); });
}; };
const loadLogs = async (path: string) => { const loadLogs = async (path: string) => {
timer = setInterval(async () => { timer = setInterval(async () => {
if (logVisiable.value) {
const res = await LoadFile({ path: path }); const res = await LoadFile({ path: path });
logInfo.value = formatImageStdout(res.data); logInfo.value = formatImageStdout(res.data);
nextTick(() => { nextTick(() => {
@ -252,11 +291,11 @@ const loadLogs = async (path: string) => {
logInfo.value.endsWith('docker-compose up failed!') || logInfo.value.endsWith('docker-compose up failed!') ||
logInfo.value.endsWith('docker-compose up successful!') logInfo.value.endsWith('docker-compose up successful!')
) { ) {
onCreating.value = false;
clearInterval(Number(timer)); clearInterval(Number(timer));
timer = null; timer = null;
buttonDisabled.value = false; buttonDisabled.value = false;
} }
}
}, 1000 * 3); }, 1000 * 3);
}; };