mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-10 15:36:45 +08:00
parent
2096049708
commit
0c5a5a6454
7 changed files with 116 additions and 45 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue