mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-23 15:59:07 +08:00
parent
e46d8c5812
commit
2a21f194d3
6 changed files with 111 additions and 145 deletions
|
|
@ -203,9 +203,8 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
|
||||||
cmd := getComposeCmd(req.Path, "up")
|
err := compose.UpWithTask(req.Path, t)
|
||||||
out, err := cmd.CombinedOutput()
|
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
|
||||||
taskItem.Log(i18n.GetWithName("ComposeCreateRes", string(out)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = compose.Down(req.Path)
|
_, _ = compose.Down(req.Path)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
package compose
|
package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
"time"
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkCmd() error {
|
func checkCmd() error {
|
||||||
|
|
@ -27,6 +37,58 @@ func Up(filePath string) (string, error) {
|
||||||
return stdout, err
|
return stdout, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpWithTask(filePath string, task *task.Task) error {
|
||||||
|
content, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env, _ := os.ReadFile(path.Join(path.Dir(filePath), ".env"))
|
||||||
|
var compose docker.ComposeProject
|
||||||
|
if err := yaml.Unmarshal(content, &compose); err != nil {
|
||||||
|
return fmt.Errorf("parse docker-compose file failed: %v", err)
|
||||||
|
}
|
||||||
|
images, err := docker.GetImagesFromDockerCompose(env, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dockerCLi, err := docker.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
errMsg := ""
|
||||||
|
for _, image := range images {
|
||||||
|
task.Log(i18n.GetWithName("PullImageStart", image))
|
||||||
|
if err = dockerCLi.PullImageWithProcess(task, image); err != nil {
|
||||||
|
errOur := err.Error()
|
||||||
|
if errOur != "" {
|
||||||
|
if strings.Contains(errOur, "no such host") {
|
||||||
|
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||||
|
}
|
||||||
|
if strings.Contains(errOur, "Error response from daemon") {
|
||||||
|
errMsg = i18n.GetMsgByKey("PullImageTimeout") + ":"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message := errMsg + errOur
|
||||||
|
installErr := errors.New(message)
|
||||||
|
task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr)
|
||||||
|
if exist, _ := dockerCLi.ImageExists(image); !exist {
|
||||||
|
return installErr
|
||||||
|
} else {
|
||||||
|
task.Log(i18n.GetMsgByKey("UseExistImage"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
task.Log(i18n.GetMsgByKey("PullImageSuccess"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerCommand := global.CONF.DockerConfig.Command
|
||||||
|
if dockerCommand == "docker-compose" {
|
||||||
|
return cmd.NewCommandMgr(cmd.WithTask(*task)).Run("docker-compose", "-f", filePath, "up", "-d")
|
||||||
|
} else {
|
||||||
|
return cmd.NewCommandMgr(cmd.WithTask(*task)).Run("docker", "compose", "-f", filePath, "up", "-d")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Down(filePath string) (string, error) {
|
func Down(filePath string) (string, error) {
|
||||||
if err := checkCmd(); err != nil {
|
if err := checkCmd(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,12 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</DrawerPro>
|
</DrawerPro>
|
||||||
<TaskLog ref="taskLogRef" width="70%" />
|
<TaskLog ref="taskLogRef" width="70%">
|
||||||
|
<template #task-footer>
|
||||||
|
<el-button @click="handleClose">{{ $t('commons.table.backToList') }}</el-button>
|
||||||
|
<el-button type="primary" @click="closeTask">{{ $t('commons.table.keepEdit') }}</el-button>
|
||||||
|
</template>
|
||||||
|
</TaskLog>
|
||||||
<FileList ref="fileRef" @choose="loadDir" />
|
<FileList ref="fileRef" @choose="loadDir" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -167,8 +172,12 @@ const changeFrom = () => {
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
emit('search');
|
emit('search');
|
||||||
|
taskLogRef.value?.handleClose();
|
||||||
drawerVisible.value = false;
|
drawerVisible.value = false;
|
||||||
};
|
};
|
||||||
|
const closeTask = () => {
|
||||||
|
taskLogRef.value?.handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
const loadPath = async () => {
|
const loadPath = async () => {
|
||||||
const pathRes = await loadBaseDir();
|
const pathRes = await loadBaseDir();
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
<template>
|
|
||||||
<DrawerPro
|
|
||||||
v-model="composeVisible"
|
|
||||||
:header="$t('commons.button.edit')"
|
|
||||||
@close="handleClose"
|
|
||||||
:resource="name"
|
|
||||||
size="large"
|
|
||||||
:autoClose="false"
|
|
||||||
:fullScreen="true"
|
|
||||||
>
|
|
||||||
<div v-loading="loading">
|
|
||||||
<el-form ref="formRef" @submit.prevent label-position="top">
|
|
||||||
<el-form-item>
|
|
||||||
<CodemirrorPro
|
|
||||||
v-model="content"
|
|
||||||
mode="yaml"
|
|
||||||
:heightDiff="175"
|
|
||||||
placeholder="#Define or paste the content of your docker-compose file here"
|
|
||||||
></CodemirrorPro>
|
|
||||||
</el-form-item>
|
|
||||||
<div v-if="createdBy === '1Panel'">
|
|
||||||
<el-form-item :label="$t('container.env')" prop="environmentStr">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
:placeholder="$t('container.tagHelper')"
|
|
||||||
:rows="3"
|
|
||||||
v-model="environmentStr"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<span class="input-help whitespace-break-spaces">
|
|
||||||
{{ $t('container.editComposeHelper') }}
|
|
||||||
</span>
|
|
||||||
<CodemirrorPro
|
|
||||||
v-model="envFileContent"
|
|
||||||
:height="45"
|
|
||||||
:minHeight="45"
|
|
||||||
disabled
|
|
||||||
mode="yaml"
|
|
||||||
></CodemirrorPro>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button :disabled="loading" @click="composeVisible = false">
|
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button :disabled="loading" type="primary" @click="onSubmitEdit()">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</DrawerPro>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { composeUpdate } from '@/api/modules/container';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const composeVisible = ref(false);
|
|
||||||
const path = ref();
|
|
||||||
const content = ref();
|
|
||||||
const name = ref();
|
|
||||||
const environmentStr = ref();
|
|
||||||
const environmentEnv = ref();
|
|
||||||
const createdBy = ref();
|
|
||||||
const envFileContent = ref(`env_file:\n - 1panel.env`);
|
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
|
||||||
|
|
||||||
const onSubmitEdit = async () => {
|
|
||||||
const param = {
|
|
||||||
name: name.value,
|
|
||||||
path: path.value,
|
|
||||||
content: content.value,
|
|
||||||
createdBy: createdBy.value,
|
|
||||||
env: environmentStr.value?.split('\n') || [],
|
|
||||||
};
|
|
||||||
loading.value = true;
|
|
||||||
await composeUpdate(param)
|
|
||||||
.then(() => {
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
composeVisible.value = false;
|
|
||||||
if (environmentStr.value) {
|
|
||||||
emit('search');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DialogProps {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
content: string;
|
|
||||||
env: Array<string>;
|
|
||||||
envStr: string;
|
|
||||||
createdBy: string;
|
|
||||||
}
|
|
||||||
const acceptParams = (props: DialogProps): void => {
|
|
||||||
composeVisible.value = true;
|
|
||||||
path.value = props.path;
|
|
||||||
name.value = props.name;
|
|
||||||
content.value = props.content;
|
|
||||||
createdBy.value = props.createdBy;
|
|
||||||
environmentEnv.value = props.env || [];
|
|
||||||
environmentStr.value = environmentEnv.value.join('\n');
|
|
||||||
};
|
|
||||||
const handleClose = () => {
|
|
||||||
composeVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
@ -13,14 +13,19 @@
|
||||||
{{ $t('container.createCompose') }}
|
{{ $t('container.createCompose') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
<template #rightToolBar>
|
||||||
|
<TableRefresh @search="search()" />
|
||||||
|
<TableSetting title="container-refresh" @search="search()" />
|
||||||
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<el-row v-if="data.length > 0" :gutter="20" class="row-box">
|
<el-row v-if="data.length > 0" :gutter="20" class="row-box">
|
||||||
<el-col :span="6">
|
<el-col :span="7">
|
||||||
<el-card>
|
<el-card>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchName"
|
v-model="searchName"
|
||||||
:placeholder="$t('commons.button.search')"
|
:placeholder="$t('commons.button.search')"
|
||||||
clearable
|
clearable
|
||||||
|
class="w-4/5"
|
||||||
@clear="search"
|
@clear="search"
|
||||||
@keyup.enter="search"
|
@keyup.enter="search"
|
||||||
>
|
>
|
||||||
|
|
@ -29,17 +34,32 @@
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<ComplexTable :show-header="false" @row-click="loadDetail" :data="data">
|
<el-table class="mt-2" :show-header="false" @row-click="loadDetail" :data="data">
|
||||||
<el-table-column prop="name">
|
<el-table-column prop="name">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="cursor-pointer">
|
<div class="cursor-pointer">
|
||||||
<div class="font-medium truncate">
|
<div class="font-medium text-base">
|
||||||
{{ row.name }}
|
{{ row.name }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-1">
|
||||||
|
<el-text class="w-12" link size="small" type="info">
|
||||||
|
{{ loadFrom(row) }}
|
||||||
|
</el-text>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-text v-if="row.containerCount === 0" type="danger" size="small">
|
<el-text link size="small" type="info" class="ml-2">
|
||||||
|
{{ row.createdAt }}
|
||||||
|
</el-text>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-text
|
||||||
|
link
|
||||||
|
v-if="row.containerCount === 0"
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{{ $t('container.exited') }}
|
{{ $t('container.exited') }}
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-text
|
<el-text
|
||||||
|
link
|
||||||
v-else
|
v-else
|
||||||
:type="
|
:type="
|
||||||
row.containerCount === row.runningCount ? 'success' : 'warning'
|
row.containerCount === row.runningCount ? 'success' : 'warning'
|
||||||
|
|
@ -50,21 +70,16 @@
|
||||||
$t('container.running', [row.runningCount, row.containerCount])
|
$t('container.running', [row.runningCount, row.containerCount])
|
||||||
}}
|
}}
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-divider direction="vertical" />
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
icon="Folder"
|
|
||||||
:disabled="!currentCompose?.workdir"
|
|
||||||
@click="openComposeFolder"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 mb-2">
|
|
||||||
<el-tag size="small" type="info">{{ loadFrom(row) }}</el-tag>
|
|
||||||
<el-tag size="small" type="info" class="ml-2">
|
|
||||||
{{ row.createdAt }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
</div>
|
||||||
|
<el-button
|
||||||
|
plain
|
||||||
|
round
|
||||||
|
size="small"
|
||||||
|
:disabled="!currentCompose?.workdir"
|
||||||
|
@click="openComposeFolder"
|
||||||
|
>
|
||||||
|
{{ $t('home.dir') }}
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
plain
|
plain
|
||||||
round
|
round
|
||||||
|
|
@ -101,10 +116,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</ComplexTable>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="18">
|
<el-col :span="17">
|
||||||
<el-card v-if="currentCompose" v-loading="detailLoading">
|
<el-card v-if="currentCompose" v-loading="detailLoading">
|
||||||
<el-table
|
<el-table
|
||||||
v-if="composeContainers.length > 0"
|
v-if="composeContainers.length > 0"
|
||||||
|
|
|
||||||
|
|
@ -365,7 +365,7 @@
|
||||||
<TerminalDialog ref="dialogTerminalRef" />
|
<TerminalDialog ref="dialogTerminalRef" />
|
||||||
|
|
||||||
<PortJumpDialog ref="dialogPortJumpRef" />
|
<PortJumpDialog ref="dialogPortJumpRef" />
|
||||||
<TaskLog ref="taskLogRef" width="70%" />
|
<TaskLog ref="taskLogRef" width="70%" @close="search" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue