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)
|
||||
}
|
||||
|
||||
// @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
|
||||
// @Summary Upgrade container
|
||||
// @Description 更新容器镜像
|
||||
|
@ -90,6 +90,11 @@ type ContainerOperate struct {
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
}
|
||||
|
||||
type ContainerCreateByCommand struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Command string `json:"command"`
|
||||
}
|
||||
|
||||
type ContainerUpgrade struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Image string `json:"image" validate:"required"`
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -63,6 +64,7 @@ type IContainerService interface {
|
||||
CreateCompose(req dto.ComposeCreate) error
|
||||
ComposeOperation(req dto.ComposeOperation) error
|
||||
ContainerCreate(req dto.ContainerOperate) error
|
||||
ContainerCreateByCommand(req dto.ContainerCreateByCommand) error
|
||||
ContainerUpdate(req dto.ContainerOperate) error
|
||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||
@ -313,6 +315,25 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
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) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
|
@ -15,6 +15,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||
|
||||
baRouter.POST("", baseApi.ContainerCreate)
|
||||
baRouter.POST("command", baseApi.ContainerCreateByCommand)
|
||||
baRouter.POST("/update", baseApi.ContainerUpdate)
|
||||
baRouter.POST("/upgrade", baseApi.ContainerUpgrade)
|
||||
baRouter.POST("/info", baseApi.ContainerInfo)
|
||||
|
@ -18,6 +18,9 @@ export const loadResourceLimit = () => {
|
||||
export const createContainer = (params: Container.ContainerHelper) => {
|
||||
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) => {
|
||||
return http.post(`/containers/update`, params, TimeoutEnum.T_10M);
|
||||
};
|
||||
|
@ -37,6 +37,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
heightDiff: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
@ -45,6 +49,10 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
lineWrapping: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
@ -62,7 +70,7 @@ const initCodeMirror = () => {
|
||||
const defaultTheme = EditorView.theme({
|
||||
'&.cm-editor': {
|
||||
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),
|
||||
EditorView.editable.of(!props.disabled),
|
||||
];
|
||||
if (props.lineWrapping) {
|
||||
extensions.push(EditorView.lineWrapping);
|
||||
}
|
||||
switch (props.mode) {
|
||||
case 'dockerfile':
|
||||
extensions.push(StreamLanguage.define(dockerFile));
|
||||
|
@ -580,6 +580,10 @@ const message = {
|
||||
},
|
||||
container: {
|
||||
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',
|
||||
updateContainerHelper:
|
||||
'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',
|
||||
appHelper: 'This container is sourced from the app store; upgrading might render the service unavailable',
|
||||
|
||||
resource: 'Resource',
|
||||
input: 'Input',
|
||||
forcePull: 'forced image pull ',
|
||||
forcePullHelper: 'Ignore existing images on the server and pull again.',
|
||||
|
@ -562,6 +562,10 @@ const message = {
|
||||
},
|
||||
container: {
|
||||
create: '創建容器',
|
||||
createByCommand: '命令創建',
|
||||
commandInput: '命令輸入',
|
||||
commandRule: '請輸入正確的 docker run 容器創建命令!',
|
||||
commandHelper: '將在伺服器上執行該條命令以創建容器,是否繼續?',
|
||||
edit: '編輯容器',
|
||||
updateContainerHelper: '容器編輯需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
||||
containerList: '容器列表',
|
||||
@ -643,6 +647,7 @@ const message = {
|
||||
imageLoadErr: '未檢測到容器的鏡像名稱',
|
||||
appHelper: '該容器來源於應用商店,升級可能導致該服務不可用',
|
||||
|
||||
resource: '資源',
|
||||
input: '手動輸入',
|
||||
forcePull: '強製拉取鏡像',
|
||||
forcePullHelper: '忽略服務器已存在的鏡像,重新拉取一次',
|
||||
|
@ -562,6 +562,10 @@ const message = {
|
||||
},
|
||||
container: {
|
||||
create: '创建容器',
|
||||
createByCommand: '命令创建',
|
||||
commandInput: '命令输入',
|
||||
commandRule: '请输入正确的 docker run 容器创建命令!',
|
||||
commandHelper: '将在服务器上执行该条命令以创建容器,是否继续?',
|
||||
edit: '编辑容器',
|
||||
updateContainerHelper: '容器编辑需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||
containerList: '容器列表',
|
||||
@ -644,6 +648,7 @@ const message = {
|
||||
imageLoadErr: '未检测到容器的镜像名称',
|
||||
appHelper: '该容器来源于应用商店,升级可能导致该服务不可用',
|
||||
|
||||
resource: '资源',
|
||||
input: '手动输入',
|
||||
forcePull: '强制拉取镜像',
|
||||
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>
|
||||
<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>
|
||||
<el-alert
|
||||
v-if="!isCreate && isFromApp(form)"
|
||||
@ -18,13 +21,20 @@
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :xs="24" :sm="20" :md="15" :lg="12" :xl="12">
|
||||
<el-row type="flex" justify="center" :gutter="20">
|
||||
<el-col :span="20">
|
||||
<el-card>
|
||||
<el-button v-if="isCreate" type="primary" icon="EditPen" plain @click="openDialog()">
|
||||
{{ $t('container.commandInput') }}
|
||||
</el-button>
|
||||
<el-form-item class="mt-5" :label="$t('commons.table.name')" prop="name">
|
||||
<el-input :disabled="isFromApp(form)" clearable v-model.trim="form.name" />
|
||||
<div v-if="!isCreate && isFromApp(form)">
|
||||
<span class="input-help">
|
||||
<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') }}
|
||||
<el-button
|
||||
style="margin-left: -5px"
|
||||
@ -37,11 +47,17 @@
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.image')" prop="image">
|
||||
<el-checkbox v-model="form.imageInput" :label="$t('container.input')" />
|
||||
<el-select v-if="!form.imageInput" filterable v-model="form.image">
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select
|
||||
class="mini-form-item"
|
||||
v-if="!form.imageInput"
|
||||
filterable
|
||||
v-model="form.image"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) of images"
|
||||
:key="index"
|
||||
@ -49,7 +65,7 @@
|
||||
:label="item.option"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input v-else v-model="form.image" />
|
||||
<el-input class="mini-form-item" v-else v-model="form.image" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="forcePull">
|
||||
<el-checkbox v-model="form.forcePull">
|
||||
@ -57,6 +73,12 @@
|
||||
</el-checkbox>
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</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 :label="$t('commons.table.port')">
|
||||
<el-radio-group v-model="form.publishAllPorts" class="ml-4">
|
||||
<el-radio :value="false">{{ $t('container.exposePort') }}</el-radio>
|
||||
@ -64,9 +86,8 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!form.publishAllPorts">
|
||||
<el-card class="widthClass">
|
||||
<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 }">
|
||||
<el-input
|
||||
:placeholder="$t('container.serverExample')"
|
||||
@ -74,7 +95,7 @@
|
||||
/>
|
||||
</template>
|
||||
</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 }">
|
||||
<el-input
|
||||
:placeholder="$t('container.containerExample')"
|
||||
@ -82,19 +103,15 @@
|
||||
/>
|
||||
</template>
|
||||
</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 }">
|
||||
<el-select
|
||||
v-model="row.protocol"
|
||||
style="width: 100%"
|
||||
:placeholder="$t('container.serverExample')"
|
||||
>
|
||||
<el-option label="tcp" value="tcp" />
|
||||
<el-option label="udp" value="udp" />
|
||||
</el-select>
|
||||
<el-radio-group v-model="row.protocol">
|
||||
<el-radio value="tcp">tcp</el-radio>
|
||||
<el-radio value="udp">udp</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="35">
|
||||
<el-table-column min-width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handlePortsDelete(scope.$index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
@ -103,13 +120,16 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-button class="ml-3 mt-2" @click="handlePortsAdd()">
|
||||
<el-button class="ml-3" @click="handlePortsAdd()">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-tabs type="border-card" class="mt-5">
|
||||
<el-tab-pane :label="$t('container.network')">
|
||||
<el-form-item :label="$t('container.network')" prop="network">
|
||||
<el-select v-model="form.network">
|
||||
<el-select class="mini-form-item" v-model="form.network">
|
||||
<el-option
|
||||
v-for="(item, indexV) of networks"
|
||||
:key="indexV"
|
||||
@ -118,17 +138,27 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="ipv4" prop="ipv4">
|
||||
<el-input v-model="form.ipv4" :placeholder="$t('container.inputIpv4')" />
|
||||
<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 v-model="form.ipv6" :placeholder="$t('container.inputIpv6')" />
|
||||
<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')">
|
||||
<div v-for="(row, index) in form.volumes" :key="index" style="width: 100%">
|
||||
<el-card class="mt-1">
|
||||
<el-tab-pane :label="$t('container.mount')">
|
||||
<el-form-item>
|
||||
<el-table v-if="form.volumes.length !== 0" :data="form.volumes">
|
||||
<el-table-column :label="$t('container.server')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-radio-group v-model="row.type">
|
||||
<el-radio-button value="volume">
|
||||
{{ $t('container.volumeOption') }}
|
||||
@ -137,21 +167,18 @@
|
||||
{{ $t('container.hostOption') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button
|
||||
class="float-right mt-3"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleVolumesDelete(index)"
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('container.volumeOption') + '/' + $t('container.hostOption')"
|
||||
min-width="200"
|
||||
>
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<el-row class="mt-4" :gutter="5">
|
||||
<el-col :span="10">
|
||||
<el-form-item
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="row.type === 'volume'"
|
||||
:label="$t('container.volumeOption')"
|
||||
filterable
|
||||
v-model="row.sourceDir"
|
||||
>
|
||||
<el-select filterable v-model="row.sourceDir">
|
||||
<div v-for="(item, indexV) of volumes" :key="indexV">
|
||||
<el-tooltip
|
||||
:hide-after="20"
|
||||
@ -165,31 +192,41 @@
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-else :label="$t('container.hostOption')">
|
||||
<el-input v-model="row.sourceDir" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item :label="$t('container.mode')">
|
||||
<el-select class="widthClass" filterable v-model="row.mode">
|
||||
<el-option value="rw" :label="$t('container.modeRW')" />
|
||||
<el-option value="ro" :label="$t('container.modeR')" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<el-form-item :label="$t('container.containerDir')">
|
||||
<el-input v-else v-model="row.sourceDir" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.mode')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-radio-group v-model="row.mode">
|
||||
<el-radio value="rw">{{ $t('container.modeRW') }}</el-radio>
|
||||
<el-radio value="ro">{{ $t('container.modeR') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.containerDir')" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.containerDir" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="80">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
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>
|
||||
@ -199,31 +236,15 @@
|
||||
: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-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>
|
||||
@ -239,7 +260,8 @@
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.cpu]) }}{{ $t('commons.units.core') }}
|
||||
{{ $t('container.limitHelper', [limits.cpu])
|
||||
}}{{ $t('commons.units.core') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@ -250,8 +272,19 @@
|
||||
<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>
|
||||
<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"
|
||||
@ -268,18 +301,36 @@
|
||||
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-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<Command ref="commandRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -288,6 +339,7 @@ import { reactive, ref } from 'vue';
|
||||
import { Rules, checkFloatNumberRange, checkNumberRange } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm, ElMessageBox } from 'element-plus';
|
||||
import Command from '@/views/container/container/command/index.vue';
|
||||
import {
|
||||
listImage,
|
||||
listVolume,
|
||||
@ -389,6 +441,7 @@ const search = async () => {
|
||||
loadNetworkOptions();
|
||||
};
|
||||
|
||||
const commandRef = ref();
|
||||
const images = ref();
|
||||
const volumes = ref();
|
||||
const networks = ref();
|
||||
@ -412,6 +465,10 @@ const goBack = () => {
|
||||
router.push({ name: 'Container' });
|
||||
};
|
||||
|
||||
const openDialog = () => {
|
||||
commandRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const handlePortsAdd = () => {
|
||||
let item = {
|
||||
host: '',
|
||||
|
@ -19,10 +19,12 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<div class="w-full">
|
||||
<el-checkbox v-model="form.deleteTag">
|
||||
{{ $t('container.imageTagDeleteHelper') }}
|
||||
</el-checkbox>
|
||||
<el-checkbox-group class="ml-5" v-if="form.deleteTag" v-model="form.deleteTags">
|
||||
</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-group>
|
||||
</el-form-item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user