1Panel/frontend/src/views/container/compose/create/index.vue

275 lines
10 KiB
Vue

<template>
<el-drawer
v-model="drawerVisible"
@close="handleClose"
:destroy-on-close="true"
:close-on-click-modal="false"
size="50%"
>
<template #header>
<DrawerHeader :header="$t('container.compose')" :back="handleClose" />
</template>
<div v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form ref="formRef" @submit.prevent label-position="top" :model="form" :rules="rules">
<el-form-item :label="$t('container.from')">
<el-radio-group v-model="form.from" @change="changeFrom">
<el-radio value="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio value="path">{{ $t('container.pathSelect') }}</el-radio>
<el-radio value="template">{{ $t('container.composeTemplate') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.from === 'path'" prop="path">
<el-input
:placeholder="$t('commons.example') + '/tmp/docker-compose.yml'"
v-model="form.path"
>
<template #prepend>
<FileList @choose="loadDir" :dir="false"></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="form.from === 'template'" prop="template">
<el-select v-model="form.template" @change="changeTemplate">
<template #prefix>{{ $t('container.template') }}</template>
<el-option
v-for="item in templateOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</el-select>
</el-form-item>
<el-form-item v-if="form.from === 'edit' || form.from === 'template'" prop="name">
<el-input @input="changePath" v-model.trim="form.name">
<template #prefix>
<span style="margin-right: 8px">{{ $t('file.dir') }}</span>
</template>
</el-input>
<span class="input-help">
{{ $t('container.composePathHelper', [composeFile]) }}
</span>
</el-form-item>
<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
v-if="mode === 'edit'"
:autofocus="true"
placeholder="#Define or paste the content of your docker-compose file here"
:indent-with-tab="true"
:tabSize="4"
style="width: 100%; height: calc(100vh - 376px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
v-model="form.file"
/>
</div>
<div style="width: 100%">
<LogFile
ref="logRef"
v-model:is-reading="isReading"
:config="logConfig"
:default-button="false"
v-if="mode === 'log' && showLog"
:style="'height: calc(100vh - 370px);min-height: 200px'"
/>
</div>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" :disabled="isReading" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { nextTick, onBeforeUnmount, reactive, ref } from 'vue';
import FileList from '@/components/file-list/index.vue';
import { Codemirror } from 'vue-codemirror';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm, ElMessageBox } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { listComposeTemplate, testCompose, upCompose } from '@/api/modules/container';
import { loadBaseDir } from '@/api/modules/setting';
import { MsgError } from '@/utils/message';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
const extensions = [javascript(), oneDark];
const showLog = ref(false);
const loading = ref();
const mode = ref('edit');
const oldFrom = ref('edit');
const drawerVisible = ref(false);
const templateOptions = ref();
const baseDir = ref();
const composeFile = ref();
let timer: NodeJS.Timer | null = null;
const logRef = ref();
const isReading = ref();
const logConfig = reactive({
type: 'compose-create',
name: '',
});
const form = reactive({
name: '',
from: 'edit',
path: '',
file: '',
template: null as number,
});
const rules = reactive({
name: [Rules.requiredInput, Rules.imageName],
path: [Rules.requiredInput],
template: [Rules.requiredSelect],
});
const loadTemplates = async () => {
const res = await listComposeTemplate();
templateOptions.value = res.data;
};
const acceptParams = (): void => {
mode.value = 'edit';
drawerVisible.value = true;
form.name = '';
form.from = 'edit';
form.path = '';
form.file = '';
form.template = null;
loadTemplates();
loadPath();
};
const emit = defineEmits<{ (e: 'search'): void }>();
const changeTemplate = () => {
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(() => {
if (oldFrom.value === 'template') {
form.template = null;
form.file = '';
}
if (oldFrom.value === 'edit') {
form.file = '';
}
oldFrom.value = form.from;
})
.catch(() => {
form.from = oldFrom.value;
});
} else {
oldFrom.value = form.from;
}
};
const handleClose = () => {
emit('search');
clearInterval(Number(timer));
timer = null;
drawerVisible.value = false;
};
const loadPath = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
changePath();
};
const changePath = async () => {
composeFile.value = baseDir.value + '/docker/compose/' + form.name;
};
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if ((form.from === 'edit' || form.from === 'template') && form.file.length === 0) {
MsgError(i18n.global.t('container.contentEmpty'));
return;
}
loading.value = true;
await testCompose(form)
.then(async (res) => {
loading.value = false;
if (res.data) {
mode.value = 'log';
await upCompose(form)
.then((res) => {
logConfig.name = res.data;
loadLogs();
})
.catch(() => {
loading.value = false;
});
}
})
.catch(() => {
loading.value = false;
});
});
};
const loadLogs = () => {
showLog.value = false;
nextTick(() => {
showLog.value = true;
nextTick(() => {
logRef.value.changeTail(true);
});
});
};
const loadDir = async (path: string) => {
form.path = path;
};
onBeforeUnmount(() => {
clearInterval(Number(timer));
timer = null;
});
defineExpose({
acceptParams,
});
</script>
<style scoped lang="scss"></style>