mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 支持直接从命令创建容器,表单样式优化 (#6741)
This commit is contained in:
parent
14bbb8c65d
commit
9c5da23a38
@ -257,6 +257,27 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Container
|
||||||
|
// @Summary Create container by command
|
||||||
|
// @Description 命令创建容器
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ContainerCreateByCommand true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /containers/command [post]
|
||||||
|
func (b *BaseApi) ContainerCreateByCommand(c *gin.Context) {
|
||||||
|
var req dto.ContainerCreateByCommand
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerService.ContainerCreateByCommand(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Container
|
// @Tags Container
|
||||||
// @Summary Upgrade container
|
// @Summary Upgrade container
|
||||||
// @Description 更新容器镜像
|
// @Description 更新容器镜像
|
||||||
|
@ -90,6 +90,11 @@ type ContainerOperate struct {
|
|||||||
RestartPolicy string `json:"restartPolicy"`
|
RestartPolicy string `json:"restartPolicy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerCreateByCommand struct {
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
Command string `json:"command"`
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerUpgrade struct {
|
type ContainerUpgrade struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Image string `json:"image" validate:"required"`
|
Image string `json:"image" validate:"required"`
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -63,6 +64,7 @@ type IContainerService interface {
|
|||||||
CreateCompose(req dto.ComposeCreate) error
|
CreateCompose(req dto.ComposeCreate) error
|
||||||
ComposeOperation(req dto.ComposeOperation) error
|
ComposeOperation(req dto.ComposeOperation) error
|
||||||
ContainerCreate(req dto.ContainerOperate) error
|
ContainerCreate(req dto.ContainerOperate) error
|
||||||
|
ContainerCreateByCommand(req dto.ContainerCreateByCommand) error
|
||||||
ContainerUpdate(req dto.ContainerOperate) error
|
ContainerUpdate(req dto.ContainerOperate) error
|
||||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||||
@ -313,6 +315,25 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
|||||||
return datas, nil
|
return datas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerCreateByCommand(req dto.ContainerCreateByCommand) error {
|
||||||
|
if cmd.CheckIllegal(req.Command) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
|
taskItem, err := task.NewTaskWithOps("-", task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", "-"), func(t *task.Task) error {
|
||||||
|
logPath := path.Join(constant.LogDir, task.TaskScopeContainer, req.TaskID+".log")
|
||||||
|
return cmd.ExecShell(logPath, 5*time.Minute, "bash", "-c", req.Command)
|
||||||
|
}, nil)
|
||||||
|
_ = taskItem.Execute()
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -15,6 +15,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||||
|
|
||||||
baRouter.POST("", baseApi.ContainerCreate)
|
baRouter.POST("", baseApi.ContainerCreate)
|
||||||
|
baRouter.POST("command", baseApi.ContainerCreateByCommand)
|
||||||
baRouter.POST("/update", baseApi.ContainerUpdate)
|
baRouter.POST("/update", baseApi.ContainerUpdate)
|
||||||
baRouter.POST("/upgrade", baseApi.ContainerUpgrade)
|
baRouter.POST("/upgrade", baseApi.ContainerUpgrade)
|
||||||
baRouter.POST("/info", baseApi.ContainerInfo)
|
baRouter.POST("/info", baseApi.ContainerInfo)
|
||||||
|
@ -18,6 +18,9 @@ export const loadResourceLimit = () => {
|
|||||||
export const createContainer = (params: Container.ContainerHelper) => {
|
export const createContainer = (params: Container.ContainerHelper) => {
|
||||||
return http.post(`/containers`, params, TimeoutEnum.T_10M);
|
return http.post(`/containers`, params, TimeoutEnum.T_10M);
|
||||||
};
|
};
|
||||||
|
export const createContainerByCommand = (command: string, taskID: string) => {
|
||||||
|
return http.post(`/containers/command`, { command: command, taskID: taskID });
|
||||||
|
};
|
||||||
export const updateContainer = (params: Container.ContainerHelper) => {
|
export const updateContainer = (params: Container.ContainerHelper) => {
|
||||||
return http.post(`/containers/update`, params, TimeoutEnum.T_10M);
|
return http.post(`/containers/update`, params, TimeoutEnum.T_10M);
|
||||||
};
|
};
|
||||||
|
@ -37,6 +37,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
heightDiff: {
|
heightDiff: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 200,
|
default: 200,
|
||||||
@ -45,6 +49,10 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 400,
|
default: 400,
|
||||||
},
|
},
|
||||||
|
lineWrapping: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
@ -62,7 +70,7 @@ const initCodeMirror = () => {
|
|||||||
const defaultTheme = EditorView.theme({
|
const defaultTheme = EditorView.theme({
|
||||||
'&.cm-editor': {
|
'&.cm-editor': {
|
||||||
minHeight: props.minHeight + 'px',
|
minHeight: props.minHeight + 'px',
|
||||||
height: 'calc(100vh - ' + props.heightDiff + 'px)',
|
height: props.height ? props.height + 'px' : 'calc(100vh - ' + props.heightDiff + 'px)',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,6 +86,9 @@ const initCodeMirror = () => {
|
|||||||
placeholder(props.placeholder),
|
placeholder(props.placeholder),
|
||||||
EditorView.editable.of(!props.disabled),
|
EditorView.editable.of(!props.disabled),
|
||||||
];
|
];
|
||||||
|
if (props.lineWrapping) {
|
||||||
|
extensions.push(EditorView.lineWrapping);
|
||||||
|
}
|
||||||
switch (props.mode) {
|
switch (props.mode) {
|
||||||
case 'dockerfile':
|
case 'dockerfile':
|
||||||
extensions.push(StreamLanguage.define(dockerFile));
|
extensions.push(StreamLanguage.define(dockerFile));
|
||||||
|
@ -580,6 +580,10 @@ const message = {
|
|||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
create: 'Create',
|
create: 'Create',
|
||||||
|
createByCommand: 'Create by command',
|
||||||
|
commandInput: 'Command input',
|
||||||
|
commandRule: 'Please enter the correct docker run container creation command!',
|
||||||
|
commandHelper: 'This command will be executed on the server to create the container. Do you want to continue?',
|
||||||
edit: 'Edit container',
|
edit: 'Edit container',
|
||||||
updateContainerHelper:
|
updateContainerHelper:
|
||||||
'Container editing requires rebuilding the container. Any data that has not been persisted will be lost. Do you want to continue?',
|
'Container editing requires rebuilding the container. Any data that has not been persisted will be lost. Do you want to continue?',
|
||||||
@ -669,6 +673,7 @@ const message = {
|
|||||||
imageLoadErr: 'No image name detected for the container',
|
imageLoadErr: 'No image name detected for the container',
|
||||||
appHelper: 'This container is sourced from the app store; upgrading might render the service unavailable',
|
appHelper: 'This container is sourced from the app store; upgrading might render the service unavailable',
|
||||||
|
|
||||||
|
resource: 'Resource',
|
||||||
input: 'Input',
|
input: 'Input',
|
||||||
forcePull: 'forced image pull ',
|
forcePull: 'forced image pull ',
|
||||||
forcePullHelper: 'Ignore existing images on the server and pull again.',
|
forcePullHelper: 'Ignore existing images on the server and pull again.',
|
||||||
|
@ -562,6 +562,10 @@ const message = {
|
|||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
create: '創建容器',
|
create: '創建容器',
|
||||||
|
createByCommand: '命令創建',
|
||||||
|
commandInput: '命令輸入',
|
||||||
|
commandRule: '請輸入正確的 docker run 容器創建命令!',
|
||||||
|
commandHelper: '將在伺服器上執行該條命令以創建容器,是否繼續?',
|
||||||
edit: '編輯容器',
|
edit: '編輯容器',
|
||||||
updateContainerHelper: '容器編輯需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
updateContainerHelper: '容器編輯需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
||||||
containerList: '容器列表',
|
containerList: '容器列表',
|
||||||
@ -643,6 +647,7 @@ const message = {
|
|||||||
imageLoadErr: '未檢測到容器的鏡像名稱',
|
imageLoadErr: '未檢測到容器的鏡像名稱',
|
||||||
appHelper: '該容器來源於應用商店,升級可能導致該服務不可用',
|
appHelper: '該容器來源於應用商店,升級可能導致該服務不可用',
|
||||||
|
|
||||||
|
resource: '資源',
|
||||||
input: '手動輸入',
|
input: '手動輸入',
|
||||||
forcePull: '強製拉取鏡像',
|
forcePull: '強製拉取鏡像',
|
||||||
forcePullHelper: '忽略服務器已存在的鏡像,重新拉取一次',
|
forcePullHelper: '忽略服務器已存在的鏡像,重新拉取一次',
|
||||||
|
@ -562,6 +562,10 @@ const message = {
|
|||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
create: '创建容器',
|
create: '创建容器',
|
||||||
|
createByCommand: '命令创建',
|
||||||
|
commandInput: '命令输入',
|
||||||
|
commandRule: '请输入正确的 docker run 容器创建命令!',
|
||||||
|
commandHelper: '将在服务器上执行该条命令以创建容器,是否继续?',
|
||||||
edit: '编辑容器',
|
edit: '编辑容器',
|
||||||
updateContainerHelper: '容器编辑需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
updateContainerHelper: '容器编辑需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||||
containerList: '容器列表',
|
containerList: '容器列表',
|
||||||
@ -644,6 +648,7 @@ const message = {
|
|||||||
imageLoadErr: '未检测到容器的镜像名称',
|
imageLoadErr: '未检测到容器的镜像名称',
|
||||||
appHelper: '该容器来源于应用商店,升级可能导致该服务不可用',
|
appHelper: '该容器来源于应用商店,升级可能导致该服务不可用',
|
||||||
|
|
||||||
|
resource: '资源',
|
||||||
input: '手动输入',
|
input: '手动输入',
|
||||||
forcePull: '强制拉取镜像',
|
forcePull: '强制拉取镜像',
|
||||||
forcePullHelper: '忽略服务器已存在的镜像,重新拉取一次',
|
forcePullHelper: '忽略服务器已存在的镜像,重新拉取一次',
|
||||||
|
110
frontend/src/views/container/container/command/index.vue
Normal file
110
frontend/src/views/container/container/command/index.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-dialog v-model="drawerVisible" :title="$t('container.createByCommand')" :back="handleClose" width="70%">
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
ref="formRef"
|
||||||
|
:rules="rules"
|
||||||
|
:model="form"
|
||||||
|
label-position="top"
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<el-form-item prop="command">
|
||||||
|
<CodemirrorPro
|
||||||
|
:lineWrapping="true"
|
||||||
|
v-model="form.command"
|
||||||
|
:height="300"
|
||||||
|
:minHeight="50"
|
||||||
|
mode="shell"
|
||||||
|
placeholder="e.g. docker run -p 80:80 --name my-nginx nginx"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="drawerVisible = false">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<TaskLog ref="taskLogRef" width="70%" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import TaskLog from '@/components/task-log/index.vue';
|
||||||
|
import { createContainerByCommand } from '@/api/modules/container';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { newUUID } from '@/utils/util';
|
||||||
|
|
||||||
|
const drawerVisible = ref<boolean>(false);
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
const loading = ref(false);
|
||||||
|
const form = reactive({
|
||||||
|
command: '',
|
||||||
|
});
|
||||||
|
const taskLogRef = ref();
|
||||||
|
|
||||||
|
const acceptParams = (): void => {
|
||||||
|
form.command = '';
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
|
||||||
|
const verifyCommand = (rule: any, value: any, callback: any) => {
|
||||||
|
if (!form.command || !form.command.startsWith('docker run')) {
|
||||||
|
callback(new Error(i18n.global.t('container.commandRule')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
const rules = reactive({
|
||||||
|
command: [{ validator: verifyCommand, trigger: 'blur', required: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(i18n.global.t('container.commandHelper'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
let taskID = newUUID();
|
||||||
|
await createContainerByCommand(form.command, taskID)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
emit('search');
|
||||||
|
openTaskLog(taskID);
|
||||||
|
drawerVisible.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const openTaskLog = (taskID: string) => {
|
||||||
|
taskLogRef.value.openWithTaskID(taskID);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = async () => {
|
||||||
|
drawerVisible.value = false;
|
||||||
|
emit('search');
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<LayoutContent :title="isCreate ? $t('container.create') : $t('commons.button.edit') + ' - ' + form.name">
|
<LayoutContent
|
||||||
|
back-name="Container"
|
||||||
|
:title="isCreate ? $t('container.create') : $t('commons.button.edit') + ' - ' + form.name"
|
||||||
|
>
|
||||||
<template #prompt>
|
<template #prompt>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="!isCreate && isFromApp(form)"
|
v-if="!isCreate && isFromApp(form)"
|
||||||
@ -18,13 +21,20 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-width="80px"
|
label-width="80px"
|
||||||
>
|
>
|
||||||
<el-row>
|
<el-row type="flex" justify="center" :gutter="20">
|
||||||
<el-col :span="1"><br /></el-col>
|
<el-col :span="20">
|
||||||
<el-col :xs="24" :sm="20" :md="15" :lg="12" :xl="12">
|
<el-card>
|
||||||
<el-form-item class="mt-5" :label="$t('commons.table.name')" prop="name">
|
<el-button v-if="isCreate" type="primary" icon="EditPen" plain @click="openDialog()">
|
||||||
<el-input :disabled="isFromApp(form)" clearable v-model.trim="form.name" />
|
{{ $t('container.commandInput') }}
|
||||||
<div v-if="!isCreate && isFromApp(form)">
|
</el-button>
|
||||||
<span class="input-help">
|
<el-form-item class="mt-5" :label="$t('commons.table.name')" prop="name">
|
||||||
|
<el-input
|
||||||
|
:disabled="isFromApp(form)"
|
||||||
|
class="mini-form-item"
|
||||||
|
clearable
|
||||||
|
v-model.trim="form.name"
|
||||||
|
/>
|
||||||
|
<span class="input-help" v-if="!isCreate && isFromApp(form)">
|
||||||
{{ $t('container.containerFromAppHelper1') }}
|
{{ $t('container.containerFromAppHelper1') }}
|
||||||
<el-button
|
<el-button
|
||||||
style="margin-left: -5px"
|
style="margin-left: -5px"
|
||||||
@ -37,36 +47,47 @@
|
|||||||
{{ $t('firewall.quickJump') }}
|
{{ $t('firewall.quickJump') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item :label="$t('container.image')" prop="image">
|
||||||
<el-form-item :label="$t('container.image')" prop="image">
|
<el-checkbox v-model="form.imageInput" :label="$t('container.input')" />
|
||||||
<el-checkbox v-model="form.imageInput" :label="$t('container.input')" />
|
</el-form-item>
|
||||||
<el-select v-if="!form.imageInput" filterable v-model="form.image">
|
<el-form-item>
|
||||||
<el-option
|
<el-select
|
||||||
v-for="(item, index) of images"
|
class="mini-form-item"
|
||||||
:key="index"
|
v-if="!form.imageInput"
|
||||||
:value="item.option"
|
filterable
|
||||||
:label="item.option"
|
v-model="form.image"
|
||||||
/>
|
>
|
||||||
</el-select>
|
<el-option
|
||||||
<el-input v-else v-model="form.image" />
|
v-for="(item, index) of images"
|
||||||
</el-form-item>
|
:key="index"
|
||||||
<el-form-item prop="forcePull">
|
:value="item.option"
|
||||||
<el-checkbox v-model="form.forcePull">
|
:label="item.option"
|
||||||
{{ $t('container.forcePull') }}
|
/>
|
||||||
</el-checkbox>
|
</el-select>
|
||||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
<el-input class="mini-form-item" v-else v-model="form.image" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('commons.table.port')">
|
<el-form-item prop="forcePull">
|
||||||
<el-radio-group v-model="form.publishAllPorts" class="ml-4">
|
<el-checkbox v-model="form.forcePull">
|
||||||
<el-radio :value="false">{{ $t('container.exposePort') }}</el-radio>
|
{{ $t('container.forcePull') }}
|
||||||
<el-radio :value="true">{{ $t('container.exposeAll') }}</el-radio>
|
</el-checkbox>
|
||||||
</el-radio-group>
|
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="!form.publishAllPorts">
|
|
||||||
<el-card class="widthClass">
|
<el-form-item prop="autoRemove">
|
||||||
|
<el-checkbox v-model="form.autoRemove">
|
||||||
|
{{ $t('container.autoRemove') }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('commons.table.port')">
|
||||||
|
<el-radio-group v-model="form.publishAllPorts" class="ml-4">
|
||||||
|
<el-radio :value="false">{{ $t('container.exposePort') }}</el-radio>
|
||||||
|
<el-radio :value="true">{{ $t('container.exposeAll') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="!form.publishAllPorts">
|
||||||
<el-table v-if="form.exposedPorts.length !== 0" :data="form.exposedPorts">
|
<el-table v-if="form.exposedPorts.length !== 0" :data="form.exposedPorts">
|
||||||
<el-table-column :label="$t('container.server')" min-width="150">
|
<el-table-column :label="$t('container.server')" min-width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input
|
<el-input
|
||||||
:placeholder="$t('container.serverExample')"
|
:placeholder="$t('container.serverExample')"
|
||||||
@ -74,7 +95,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('container.container')" min-width="80">
|
<el-table-column :label="$t('container.container')" min-width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input
|
<el-input
|
||||||
:placeholder="$t('container.containerExample')"
|
:placeholder="$t('container.containerExample')"
|
||||||
@ -82,19 +103,15 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('commons.table.protocol')" min-width="50">
|
<el-table-column :label="$t('commons.table.protocol')" min-width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-radio-group v-model="row.protocol">
|
||||||
v-model="row.protocol"
|
<el-radio value="tcp">tcp</el-radio>
|
||||||
style="width: 100%"
|
<el-radio value="udp">udp</el-radio>
|
||||||
:placeholder="$t('container.serverExample')"
|
</el-radio-group>
|
||||||
>
|
|
||||||
<el-option label="tcp" value="tcp" />
|
|
||||||
<el-option label="udp" value="udp" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column min-width="35">
|
<el-table-column min-width="80">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button link type="primary" @click="handlePortsDelete(scope.$index)">
|
<el-button link type="primary" @click="handlePortsDelete(scope.$index)">
|
||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
@ -103,55 +120,65 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<el-button class="ml-3 mt-2" @click="handlePortsAdd()">
|
<el-button class="ml-3" @click="handlePortsAdd()">
|
||||||
{{ $t('commons.button.add') }}
|
{{ $t('commons.button.add') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-card>
|
</el-form-item>
|
||||||
</el-form-item>
|
</el-card>
|
||||||
<el-form-item :label="$t('container.network')" prop="network">
|
|
||||||
<el-select v-model="form.network">
|
|
||||||
<el-option
|
|
||||||
v-for="(item, indexV) of networks"
|
|
||||||
:key="indexV"
|
|
||||||
:value="item.option"
|
|
||||||
:label="item.option"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="ipv4" prop="ipv4">
|
<el-tabs type="border-card" class="mt-5">
|
||||||
<el-input v-model="form.ipv4" :placeholder="$t('container.inputIpv4')" />
|
<el-tab-pane :label="$t('container.network')">
|
||||||
</el-form-item>
|
<el-form-item :label="$t('container.network')" prop="network">
|
||||||
<el-form-item label="ipv6" prop="ipv6">
|
<el-select class="mini-form-item" v-model="form.network">
|
||||||
<el-input v-model="form.ipv6" :placeholder="$t('container.inputIpv6')" />
|
<el-option
|
||||||
</el-form-item>
|
v-for="(item, indexV) of networks"
|
||||||
|
:key="indexV"
|
||||||
|
:value="item.option"
|
||||||
|
:label="item.option"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="ipv4" prop="ipv4">
|
||||||
|
<el-input
|
||||||
|
class="mini-form-item"
|
||||||
|
v-model="form.ipv4"
|
||||||
|
:placeholder="$t('container.inputIpv4')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="ipv6" prop="ipv6">
|
||||||
|
<el-input
|
||||||
|
class="mini-form-item"
|
||||||
|
v-model="form.ipv6"
|
||||||
|
:placeholder="$t('container.inputIpv6')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-form-item :label="$t('container.mount')">
|
<el-tab-pane :label="$t('container.mount')">
|
||||||
<div v-for="(row, index) in form.volumes" :key="index" style="width: 100%">
|
<el-form-item>
|
||||||
<el-card class="mt-1">
|
<el-table v-if="form.volumes.length !== 0" :data="form.volumes">
|
||||||
<el-radio-group v-model="row.type">
|
<el-table-column :label="$t('container.server')" min-width="120">
|
||||||
<el-radio-button value="volume">
|
<template #default="{ row }">
|
||||||
{{ $t('container.volumeOption') }}
|
<el-radio-group v-model="row.type">
|
||||||
</el-radio-button>
|
<el-radio-button value="volume">
|
||||||
<el-radio-button value="bind">
|
{{ $t('container.volumeOption') }}
|
||||||
{{ $t('container.hostOption') }}
|
</el-radio-button>
|
||||||
</el-radio-button>
|
<el-radio-button value="bind">
|
||||||
</el-radio-group>
|
{{ $t('container.hostOption') }}
|
||||||
<el-button
|
</el-radio-button>
|
||||||
class="float-right mt-3"
|
</el-radio-group>
|
||||||
link
|
</template>
|
||||||
type="primary"
|
</el-table-column>
|
||||||
@click="handleVolumesDelete(index)"
|
<el-table-column
|
||||||
>
|
:label="$t('container.volumeOption') + '/' + $t('container.hostOption')"
|
||||||
{{ $t('commons.button.delete') }}
|
min-width="200"
|
||||||
</el-button>
|
>
|
||||||
<el-row class="mt-4" :gutter="5">
|
<template #default="{ row }">
|
||||||
<el-col :span="10">
|
<el-select
|
||||||
<el-form-item
|
v-if="row.type === 'volume'"
|
||||||
v-if="row.type === 'volume'"
|
filterable
|
||||||
:label="$t('container.volumeOption')"
|
v-model="row.sourceDir"
|
||||||
>
|
>
|
||||||
<el-select filterable v-model="row.sourceDir">
|
|
||||||
<div v-for="(item, indexV) of volumes" :key="indexV">
|
<div v-for="(item, indexV) of volumes" :key="indexV">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
:hide-after="20"
|
:hide-after="20"
|
||||||
@ -165,121 +192,145 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
<el-input v-else v-model="row.sourceDir" />
|
||||||
<el-form-item v-else :label="$t('container.hostOption')">
|
</template>
|
||||||
<el-input v-model="row.sourceDir" />
|
</el-table-column>
|
||||||
</el-form-item>
|
<el-table-column :label="$t('container.mode')" min-width="120">
|
||||||
</el-col>
|
<template #default="{ row }">
|
||||||
<el-col :span="5">
|
<el-radio-group v-model="row.mode">
|
||||||
<el-form-item :label="$t('container.mode')">
|
<el-radio value="rw">{{ $t('container.modeRW') }}</el-radio>
|
||||||
<el-select class="widthClass" filterable v-model="row.mode">
|
<el-radio value="ro">{{ $t('container.modeR') }}</el-radio>
|
||||||
<el-option value="rw" :label="$t('container.modeRW')" />
|
</el-radio-group>
|
||||||
<el-option value="ro" :label="$t('container.modeR')" />
|
</template>
|
||||||
</el-select>
|
</el-table-column>
|
||||||
</el-form-item>
|
<el-table-column :label="$t('container.containerDir')" min-width="200">
|
||||||
</el-col>
|
<template #default="{ row }">
|
||||||
<el-col :span="9">
|
|
||||||
<el-form-item :label="$t('container.containerDir')">
|
|
||||||
<el-input v-model="row.containerDir" />
|
<el-input v-model="row.containerDir" />
|
||||||
</el-form-item>
|
</template>
|
||||||
</el-col>
|
</el-table-column>
|
||||||
</el-row>
|
<el-table-column min-width="80">
|
||||||
</el-card>
|
<template #default="scope">
|
||||||
</div>
|
<el-button
|
||||||
<el-button @click="handleVolumesAdd()">
|
link
|
||||||
{{ $t('commons.button.add') }}
|
type="primary"
|
||||||
|
@click="handleVolumesDelete(scope.$index)"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-button @click="handleVolumesAdd()">
|
||||||
|
{{ $t('commons.button.add') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="Command">
|
||||||
|
<el-form-item label="Command" prop="cmdStr">
|
||||||
|
<el-input v-model="form.cmdStr" :placeholder="$t('container.cmdHelper')" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Entrypoint" prop="entrypointStr">
|
||||||
|
<el-input
|
||||||
|
v-model="form.entrypointStr"
|
||||||
|
:placeholder="$t('container.entrypointHelper')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.console')">
|
||||||
|
<el-checkbox v-model="form.tty">{{ $t('container.tty') }}</el-checkbox>
|
||||||
|
<el-checkbox v-model="form.openStdin">
|
||||||
|
{{ $t('container.openStdin') }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('container.resource')">
|
||||||
|
<el-form-item :label="$t('container.cpuShare')" prop="cpuShares">
|
||||||
|
<el-input class="mini-form-item" v-model.number="form.cpuShares" />
|
||||||
|
<span class="input-help">{{ $t('container.cpuShareHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('container.cpuQuota')"
|
||||||
|
prop="nanoCPUs"
|
||||||
|
:rules="checkFloatNumberRange(0, Number(limits.cpu))"
|
||||||
|
>
|
||||||
|
<el-input class="mini-form-item" v-model="form.nanoCPUs">
|
||||||
|
<template #append>
|
||||||
|
<div style="width: 35px">{{ $t('commons.units.core') }}</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ $t('container.limitHelper', [limits.cpu])
|
||||||
|
}}{{ $t('commons.units.core') }}
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('container.memoryLimit')"
|
||||||
|
prop="memory"
|
||||||
|
:rules="checkFloatNumberRange(0, Number(limits.memory))"
|
||||||
|
>
|
||||||
|
<el-input class="mini-form-item" v-model="form.memory">
|
||||||
|
<template #append><div style="width: 35px">MB</div></template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ $t('container.limitHelper', [limits.memory]) }}MB
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-checkbox v-model="form.privileged">
|
||||||
|
{{ $t('container.privileged') }}
|
||||||
|
</el-checkbox>
|
||||||
|
<span class="input-help">{{ $t('container.privilegedHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('container.tag') + ' & ' + $t('container.env')">
|
||||||
|
<el-form-item :label="$t('container.tag')" prop="labelsStr">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:rows="3"
|
||||||
|
v-model="form.labelsStr"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.env')" prop="envStr">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:rows="3"
|
||||||
|
v-model="form.envStr"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('container.restartPolicy')">
|
||||||
|
<el-form-item prop="restartPolicy">
|
||||||
|
<el-radio-group v-model="form.restartPolicy">
|
||||||
|
<el-radio value="no">{{ $t('container.no') }}</el-radio>
|
||||||
|
<el-radio value="always">{{ $t('container.always') }}</el-radio>
|
||||||
|
<el-radio value="on-failure">{{ $t('container.onFailure') }}</el-radio>
|
||||||
|
<el-radio value="unless-stopped">
|
||||||
|
{{ $t('container.unlessStopped') }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<el-form-item class="mt-5">
|
||||||
|
<el-button :disabled="loading" @click="goBack">
|
||||||
|
{{ $t('commons.button.back') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Command" prop="cmdStr">
|
|
||||||
<el-input v-model="form.cmdStr" :placeholder="$t('container.cmdHelper')" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Entrypoint" prop="entrypointStr">
|
|
||||||
<el-input
|
|
||||||
v-model="form.entrypointStr"
|
|
||||||
:placeholder="$t('container.entrypointHelper')"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="autoRemove">
|
|
||||||
<el-checkbox v-model="form.autoRemove">
|
|
||||||
{{ $t('container.autoRemove') }}
|
|
||||||
</el-checkbox>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-checkbox v-model="form.privileged">
|
|
||||||
{{ $t('container.privileged') }}
|
|
||||||
</el-checkbox>
|
|
||||||
<span class="input-help">{{ $t('container.privilegedHelper') }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('container.console')">
|
|
||||||
<el-checkbox v-model="form.tty">{{ $t('container.tty') }}</el-checkbox>
|
|
||||||
<el-checkbox v-model="form.openStdin">
|
|
||||||
{{ $t('container.openStdin') }}
|
|
||||||
</el-checkbox>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
|
||||||
<el-radio-group v-model="form.restartPolicy">
|
|
||||||
<el-radio value="no">{{ $t('container.no') }}</el-radio>
|
|
||||||
<el-radio value="always">{{ $t('container.always') }}</el-radio>
|
|
||||||
<el-radio value="on-failure">{{ $t('container.onFailure') }}</el-radio>
|
|
||||||
<el-radio value="unless-stopped">{{ $t('container.unlessStopped') }}</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('container.cpuShare')" prop="cpuShares">
|
|
||||||
<el-input class="mini-form-item" v-model.number="form.cpuShares" />
|
|
||||||
<span class="input-help">{{ $t('container.cpuShareHelper') }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('container.cpuQuota')"
|
|
||||||
prop="nanoCPUs"
|
|
||||||
:rules="checkFloatNumberRange(0, Number(limits.cpu))"
|
|
||||||
>
|
|
||||||
<el-input class="mini-form-item" v-model="form.nanoCPUs">
|
|
||||||
<template #append>
|
|
||||||
<div style="width: 35px">{{ $t('commons.units.core') }}</div>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
<span class="input-help">
|
|
||||||
{{ $t('container.limitHelper', [limits.cpu]) }}{{ $t('commons.units.core') }}
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('container.memoryLimit')"
|
|
||||||
prop="memory"
|
|
||||||
:rules="checkFloatNumberRange(0, Number(limits.memory))"
|
|
||||||
>
|
|
||||||
<el-input class="mini-form-item" v-model="form.memory">
|
|
||||||
<template #append><div style="width: 35px">MB</div></template>
|
|
||||||
</el-input>
|
|
||||||
<span class="input-help">{{ $t('container.limitHelper', [limits.memory]) }}MB</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('container.tag')" prop="labelsStr">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
:placeholder="$t('container.tagHelper')"
|
|
||||||
:rows="3"
|
|
||||||
v-model="form.labelsStr"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('container.env')" prop="envStr">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
:placeholder="$t('container.tagHelper')"
|
|
||||||
:rows="3"
|
|
||||||
v-model="form.envStr"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-button :disabled="loading" @click="goBack">
|
|
||||||
{{ $t('commons.button.back') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
|
<Command ref="commandRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -288,6 +339,7 @@ import { reactive, ref } from 'vue';
|
|||||||
import { Rules, checkFloatNumberRange, checkNumberRange } from '@/global/form-rules';
|
import { Rules, checkFloatNumberRange, checkNumberRange } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElForm, ElMessageBox } from 'element-plus';
|
import { ElForm, ElMessageBox } from 'element-plus';
|
||||||
|
import Command from '@/views/container/container/command/index.vue';
|
||||||
import {
|
import {
|
||||||
listImage,
|
listImage,
|
||||||
listVolume,
|
listVolume,
|
||||||
@ -389,6 +441,7 @@ const search = async () => {
|
|||||||
loadNetworkOptions();
|
loadNetworkOptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const commandRef = ref();
|
||||||
const images = ref();
|
const images = ref();
|
||||||
const volumes = ref();
|
const volumes = ref();
|
||||||
const networks = ref();
|
const networks = ref();
|
||||||
@ -412,6 +465,10 @@ const goBack = () => {
|
|||||||
router.push({ name: 'Container' });
|
router.push({ name: 'Container' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openDialog = () => {
|
||||||
|
commandRef.value.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
const handlePortsAdd = () => {
|
const handlePortsAdd = () => {
|
||||||
let item = {
|
let item = {
|
||||||
host: '',
|
host: '',
|
||||||
|
@ -19,10 +19,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-checkbox v-model="form.deleteTag">
|
<div class="w-full">
|
||||||
{{ $t('container.imageTagDeleteHelper') }}
|
<el-checkbox v-model="form.deleteTag">
|
||||||
</el-checkbox>
|
{{ $t('container.imageTagDeleteHelper') }}
|
||||||
<el-checkbox-group class="ml-5" v-if="form.deleteTag" v-model="form.deleteTags">
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<el-checkbox-group v-if="form.deleteTag" v-model="form.deleteTags">
|
||||||
<el-checkbox v-for="item in tags" :key="item" :value="item" :label="item" />
|
<el-checkbox v-for="item in tags" :key="item" :value="item" :label="item" />
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user