mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 06:32:59 +08:00
feat: 增加容器状态统计及状态快速切换 (#6594)
This commit is contained in:
parent
1e1fce4c77
commit
c101dfb694
@ -54,6 +54,24 @@ func (b *BaseApi) ListContainer(c *gin.Context) {
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Load containers status
|
||||
// @Description 获取容器状态
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/status [get]
|
||||
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
||||
data, err := containerService.LoadStatus()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Container Compose
|
||||
// @Summary Page composes
|
||||
// @Description 获取编排列表分页
|
||||
|
@ -8,7 +8,7 @@ type PageContainer struct {
|
||||
PageInfo
|
||||
Name string `json:"name"`
|
||||
State string `json:"state" validate:"required,oneof=all created running paused restarting removing exited dead"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=name state created_at"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
Filters string `json:"filters"`
|
||||
ExcludeAppStore bool `json:"excludeAppStore"`
|
||||
@ -39,6 +39,16 @@ type ContainerInfo struct {
|
||||
Websites []string `json:"websites"`
|
||||
}
|
||||
|
||||
type ContainerStatus struct {
|
||||
All uint `json:"all"`
|
||||
Created uint `json:"created"`
|
||||
Running uint `json:"running"`
|
||||
Paused uint `json:"paused"`
|
||||
Restarting uint `json:"restarting"`
|
||||
Removing uint `json:"removing"`
|
||||
Exited uint `json:"exited"`
|
||||
Dead uint `json:"dead"`
|
||||
}
|
||||
type ResourceLimit struct {
|
||||
CPU int `json:"cpu"`
|
||||
Memory uint64 `json:"memory"`
|
||||
|
@ -51,6 +51,7 @@ type ContainerService struct{}
|
||||
type IContainerService interface {
|
||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||
List() ([]string, error)
|
||||
LoadStatus() (dto.ContainerStatus, error)
|
||||
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListNetwork() ([]dto.Options, error)
|
||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
@ -149,13 +150,6 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
}
|
||||
return list[i].Names[0][1:] > list[j].Names[0][1:]
|
||||
})
|
||||
case "state":
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if req.Order == constant.OrderAsc {
|
||||
return list[i].State < list[j].State
|
||||
}
|
||||
return list[i].State > list[j].State
|
||||
})
|
||||
default:
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if req.Order == constant.OrderAsc {
|
||||
@ -245,6 +239,38 @@ func (u *ContainerService) List() ([]string, error) {
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
||||
var data dto.ContainerStatus
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
defer client.Close()
|
||||
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.All = uint(len(containers))
|
||||
for _, item := range containers {
|
||||
switch item.State {
|
||||
case "created":
|
||||
data.Created++
|
||||
case "running":
|
||||
data.Running++
|
||||
case "paused":
|
||||
data.Paused++
|
||||
case "restarting":
|
||||
data.Restarting++
|
||||
case "dead":
|
||||
data.Dead++
|
||||
case "exited":
|
||||
data.Exited++
|
||||
case "removing":
|
||||
data.Removing++
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
|
@ -20,6 +20,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
baRouter.POST("/info", baseApi.ContainerInfo)
|
||||
baRouter.POST("/search", baseApi.SearchContainer)
|
||||
baRouter.POST("/list", baseApi.ListContainer)
|
||||
baRouter.GET("/status", baseApi.LoadContainerStatus)
|
||||
baRouter.GET("/list/stats", baseApi.ContainerListStats)
|
||||
baRouter.GET("/search/log", baseApi.ContainerLogs)
|
||||
baRouter.POST("/download/log", baseApi.DownloadContainerLogs)
|
||||
|
@ -24,6 +24,16 @@ export namespace Container {
|
||||
orderBy: string;
|
||||
order: string;
|
||||
}
|
||||
export interface ContainerStatus {
|
||||
all: number;
|
||||
created: number;
|
||||
running: number;
|
||||
paused: number;
|
||||
restarting: number;
|
||||
removing: number;
|
||||
exited: number;
|
||||
dead: number;
|
||||
}
|
||||
export interface ResourceLimit {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
|
@ -9,6 +9,9 @@ export const searchContainer = (params: Container.ContainerSearch) => {
|
||||
export const listContainer = () => {
|
||||
return http.post<Array<string>>(`/containers/list`, {});
|
||||
};
|
||||
export const loadContainerStatus = () => {
|
||||
return http.get<Container.ContainerStatus>(`/containers/status`);
|
||||
};
|
||||
export const loadResourceLimit = () => {
|
||||
return http.get<Container.ResourceLimit>(`/containers/limit`);
|
||||
};
|
||||
|
@ -318,7 +318,7 @@ const message = {
|
||||
host: '主机',
|
||||
files: '文件',
|
||||
monitor: '监控',
|
||||
terminal: '终端',
|
||||
terminal: 'WEB终端',
|
||||
settings: '面板设置',
|
||||
toolbox: '工具箱',
|
||||
logs: '日志审计',
|
||||
@ -1137,7 +1137,7 @@ const message = {
|
||||
role: '权限',
|
||||
info: '属性',
|
||||
linkFile: '软连接文件',
|
||||
terminal: 'Web终端',
|
||||
terminal: '终端',
|
||||
shareList: '分享列表',
|
||||
zip: '压缩',
|
||||
group: '用户组',
|
||||
|
@ -28,6 +28,17 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'container/operate',
|
||||
name: 'ContainerCreate',
|
||||
component: () => import('@/views/container/container/operate/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'composeDetail/:filters?',
|
||||
name: 'ComposeDetail',
|
||||
|
@ -5,9 +5,73 @@
|
||||
<el-button type="primary" class="bt" link @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
|
||||
<span>{{ $t('container.startIn') }}</span>
|
||||
</el-card>
|
||||
|
||||
<div class="mt-5">
|
||||
<el-tag @click="searchWithStatus('all')" v-if="countItem.all" effect="plain" size="large">
|
||||
{{ $t('commons.table.all') }} * {{ countItem.all }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@click="searchWithStatus('running')"
|
||||
v-if="countItem.running"
|
||||
effect="plain"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('commons.status.running') }} * {{ countItem.running }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@click="searchWithStatus('created')"
|
||||
v-if="countItem.created"
|
||||
effect="plain"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('commons.status.created') }} * {{ countItem.created }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@click="searchWithStatus('paused')"
|
||||
v-if="countItem.paused"
|
||||
effect="plain"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('commons.status.paused') }} * {{ countItem.paused }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@click="searchWithStatus('restarting')"
|
||||
v-if="countItem.restarting"
|
||||
effect="plain"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('commons.status.restarting') }} * {{ countItem.restarting }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@click="searchWithStatus('removing')"
|
||||
v-if="countItem.removing"
|
||||
effect="plain"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('commons.status.removing') }} * {{ countItem.removing }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@click="searchWithStatus('exited')"
|
||||
v-if="countItem.exited"
|
||||
effect="plain"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('commons.status.exited') }} * {{ countItem.exited }}
|
||||
</el-tag>
|
||||
<el-tag @click="searchWithStatus('dead')" v-if="countItem.dead" effect="plain" size="large" class="ml-2">
|
||||
{{ $t('commons.status.dead') }} * {{ countItem.dead }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<LayoutContent :title="$t('container.container')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #leftToolBar>
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
<el-button type="primary" @click="onContainerOperate('')">
|
||||
{{ $t('container.create') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onClean()">
|
||||
@ -41,17 +105,6 @@
|
||||
<el-checkbox v-model="includeAppStore" @change="search()" class="!mr-2.5">
|
||||
{{ $t('container.includeAppstore') }}
|
||||
</el-checkbox>
|
||||
<el-select v-model="searchState" @change="search()" clearable class="p-w-200 mr-2.5">
|
||||
<template #prefix>{{ $t('commons.table.status') }}</template>
|
||||
<el-option :label="$t('commons.table.all')" value="all"></el-option>
|
||||
<el-option :label="$t('commons.status.created')" value="created"></el-option>
|
||||
<el-option :label="$t('commons.status.running')" value="running"></el-option>
|
||||
<el-option :label="$t('commons.status.paused')" value="paused"></el-option>
|
||||
<el-option :label="$t('commons.status.restarting')" value="restarting"></el-option>
|
||||
<el-option :label="$t('commons.status.removing')" value="removing"></el-option>
|
||||
<el-option :label="$t('commons.status.exited')" value="exited"></el-option>
|
||||
<el-option :label="$t('commons.status.dead')" value="dead"></el-option>
|
||||
</el-select>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" class="mr-2.5" />
|
||||
<TableSetting title="container-refresh" @search="refresh()" class="mr-2.5" />
|
||||
<fu-table-column-select
|
||||
@ -98,9 +151,51 @@
|
||||
min-width="150"
|
||||
prop="imageName"
|
||||
/>
|
||||
<el-table-column :label="$t('commons.table.status')" min-width="100" prop="state" sortable>
|
||||
<el-table-column :label="$t('commons.table.status')" min-width="100" prop="state">
|
||||
<template #default="{ row }">
|
||||
<Status :key="row.state" :status="row.state"></Status>
|
||||
<el-dropdown placement="bottom">
|
||||
<Status :key="row.state" :status="row.state"></Status>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('start', row)"
|
||||
@click="onOperate('start', row)"
|
||||
>
|
||||
{{ $t('container.start') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('stop', row)"
|
||||
@click="onOperate('stop', row)"
|
||||
>
|
||||
{{ $t('container.stop') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('restart', row)"
|
||||
@click="onOperate('restart', row)"
|
||||
>
|
||||
{{ $t('container.restart') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('kill', row)"
|
||||
@click="onOperate('kill', row)"
|
||||
>
|
||||
{{ $t('container.kill') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('pause', row)"
|
||||
@click="onOperate('pause', row)"
|
||||
>
|
||||
{{ $t('container.pause') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('unpause', row)"
|
||||
@click="onOperate('unpause', row)"
|
||||
>
|
||||
{{ $t('container.unpause') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -299,7 +394,6 @@
|
||||
|
||||
<RenameDialog @search="search" ref="dialogRenameRef" />
|
||||
<ContainerLogDialog ref="dialogContainerLogRef" />
|
||||
<OperateDialog @search="search" ref="dialogOperateRef" />
|
||||
<UpgradeDialog @search="search" ref="dialogUpgradeRef" />
|
||||
<CommitDialog @search="search" ref="dialogCommitRef" />
|
||||
<MonitorDialog ref="dialogMonitorRef" />
|
||||
@ -312,7 +406,6 @@
|
||||
<script lang="ts" setup>
|
||||
import PruneDialog from '@/views/container/container/prune/index.vue';
|
||||
import RenameDialog from '@/views/container/container/rename/index.vue';
|
||||
import OperateDialog from '@/views/container/container/operate/index.vue';
|
||||
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
||||
import CommitDialog from '@/views/container/container/commit/index.vue';
|
||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||
@ -326,7 +419,7 @@ import {
|
||||
containerListStats,
|
||||
containerOperator,
|
||||
inspect,
|
||||
loadContainerInfo,
|
||||
loadContainerStatus,
|
||||
loadDockerStatus,
|
||||
searchContainer,
|
||||
} from '@/api/modules/container';
|
||||
@ -349,11 +442,11 @@ const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
state: 'all',
|
||||
orderBy: 'created_at',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
const searchState = ref('all');
|
||||
const dialogUpgradeRef = ref();
|
||||
const dialogCommitRef = ref();
|
||||
const dialogPortJumpRef = ref();
|
||||
@ -361,6 +454,17 @@ const opRef = ref();
|
||||
const includeAppStore = ref(true);
|
||||
const columns = ref([]);
|
||||
|
||||
const countItem = reactive({
|
||||
all: 0,
|
||||
created: 0,
|
||||
running: 0,
|
||||
paused: 0,
|
||||
restarting: 0,
|
||||
removing: 0,
|
||||
exited: 0,
|
||||
dead: 0,
|
||||
});
|
||||
|
||||
const dockerStatus = ref('Running');
|
||||
const loadStatus = async () => {
|
||||
loading.value = true;
|
||||
@ -417,7 +521,7 @@ const search = async (column?: any) => {
|
||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||
let params = {
|
||||
name: searchName.value,
|
||||
state: searchState.value || 'all',
|
||||
state: paginationConfig.state || 'all',
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
filters: filterItem,
|
||||
@ -427,6 +531,7 @@ const search = async (column?: any) => {
|
||||
};
|
||||
loading.value = true;
|
||||
loadStats();
|
||||
loadContainerCount();
|
||||
await searchContainer(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
@ -438,11 +543,29 @@ const search = async (column?: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const searchWithStatus = (item: any) => {
|
||||
paginationConfig.state = item;
|
||||
search();
|
||||
};
|
||||
|
||||
const loadContainerCount = async () => {
|
||||
await loadContainerStatus().then((res) => {
|
||||
countItem.all = res.data.all;
|
||||
countItem.running = res.data.running;
|
||||
countItem.paused = res.data.paused;
|
||||
countItem.restarting = res.data.restarting;
|
||||
countItem.removing = res.data.removing;
|
||||
countItem.created = res.data.created;
|
||||
countItem.dead = res.data.dead;
|
||||
countItem.exited = res.data.exited;
|
||||
});
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
let filterItem = props.filters ? props.filters : '';
|
||||
let params = {
|
||||
name: searchName.value,
|
||||
state: searchState.value || 'all',
|
||||
state: paginationConfig.state || 'all',
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
filters: filterItem,
|
||||
@ -522,35 +645,8 @@ function loadMemValue(t: number) {
|
||||
return Number((t / Math.pow(num, 3)).toFixed(2));
|
||||
}
|
||||
|
||||
const dialogOperateRef = ref();
|
||||
const onEdit = async (container: string) => {
|
||||
const res = await loadContainerInfo(container);
|
||||
if (res.data) {
|
||||
onOpenDialog('edit', res.data);
|
||||
}
|
||||
};
|
||||
const onOpenDialog = async (
|
||||
title: string,
|
||||
rowData: Partial<Container.ContainerHelper> = {
|
||||
cmd: [],
|
||||
cmdStr: '',
|
||||
publishAllPorts: false,
|
||||
exposedPorts: [],
|
||||
cpuShares: 1024,
|
||||
nanoCPUs: 0,
|
||||
memory: 0,
|
||||
memoryItem: 0,
|
||||
volumes: [],
|
||||
labels: [],
|
||||
env: [],
|
||||
restartPolicy: 'no',
|
||||
},
|
||||
) => {
|
||||
let params = {
|
||||
title,
|
||||
rowData: { ...rowData },
|
||||
};
|
||||
dialogOperateRef.value!.acceptParams(params);
|
||||
const onContainerOperate = async (containerID: string) => {
|
||||
router.push({ name: 'ContainerCreate', query: { containerID: containerID } });
|
||||
};
|
||||
|
||||
const dialogMonitorRef = ref();
|
||||
@ -654,7 +750,7 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onEdit(row.containerID);
|
||||
onContainerOperate(row.containerID);
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -690,60 +786,6 @@ const buttons = [
|
||||
return checkStatus('commit', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.start'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onOperate('start', row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return checkStatus('start', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.stop'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onOperate('stop', row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return checkStatus('stop', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.restart'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onOperate('restart', row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return checkStatus('restart', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.kill'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onOperate('kill', row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return checkStatus('kill', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.pause'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onOperate('pause', row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return checkStatus('pause', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.unpause'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onOperate('unpause', row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return checkStatus('unpause', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.remove'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
|
@ -1,252 +1,286 @@
|
||||
<template>
|
||||
<DrawerPro
|
||||
v-model="drawerVisible"
|
||||
:header="title"
|
||||
:back="handleClose"
|
||||
:resource="dialogData.title === 'create' ? '' : dialogData.rowData?.name"
|
||||
size="large"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
label-position="top"
|
||||
v-loading="loading"
|
||||
:model="dialogData.rowData!"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-alert
|
||||
v-if="dialogData.title === 'edit' && isFromApp(dialogData.rowData!)"
|
||||
:title="$t('container.containerFromAppHelper')"
|
||||
:closable="false"
|
||||
type="error"
|
||||
/>
|
||||
<el-form-item class="mt-5" :label="$t('commons.table.name')" prop="name">
|
||||
<el-input
|
||||
:disabled="isFromApp(dialogData.rowData!)"
|
||||
clearable
|
||||
v-model.trim="dialogData.rowData!.name"
|
||||
<div>
|
||||
<LayoutContent :title="isCreate ? $t('container.create') : $t('commons.button.edit') + ' - ' + form.name">
|
||||
<template #prompt>
|
||||
<el-alert
|
||||
v-if="!isCreate && isFromApp(form)"
|
||||
:title="$t('container.containerFromAppHelper')"
|
||||
:closable="false"
|
||||
type="error"
|
||||
/>
|
||||
<div v-if="dialogData.title === 'edit' && isFromApp(dialogData.rowData!)">
|
||||
<span class="input-help">
|
||||
{{ $t('container.containerFromAppHelper1') }}
|
||||
<el-button style="margin-left: -5px" size="small" text type="primary" @click="goRouter()">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.image')" prop="image">
|
||||
<el-checkbox v-model="dialogData.rowData!.imageInput" :label="$t('container.input')" />
|
||||
<el-select v-if="!dialogData.rowData!.imageInput" filterable v-model="dialogData.rowData!.image">
|
||||
<el-option v-for="(item, index) of images" :key="index" :value="item.option" :label="item.option" />
|
||||
</el-select>
|
||||
<el-input v-else v-model="dialogData.rowData!.image" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="forcePull">
|
||||
<el-checkbox v-model="dialogData.rowData!.forcePull">
|
||||
{{ $t('container.forcePull') }}
|
||||
</el-checkbox>
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.port')">
|
||||
<el-radio-group v-model="dialogData.rowData!.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="!dialogData.rowData!.publishAllPorts">
|
||||
<el-card class="widthClass">
|
||||
<el-table
|
||||
v-if="dialogData.rowData!.exposedPorts.length !== 0"
|
||||
:data="dialogData.rowData!.exposedPorts"
|
||||
>
|
||||
<el-table-column :label="$t('container.server')" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input :placeholder="$t('container.serverExample')" v-model="row.host" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.container')" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-input :placeholder="$t('container.containerExample')" v-model="row.containerPort" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.protocol')" min-width="50">
|
||||
<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" />
|
||||
</template>
|
||||
<template #main>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
label-position="top"
|
||||
v-loading="loading"
|
||||
:model="form"
|
||||
: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-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">
|
||||
{{ $t('container.containerFromAppHelper1') }}
|
||||
<el-button
|
||||
style="margin-left: -5px"
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
@click="goRouter()"
|
||||
>
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $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-option
|
||||
v-for="(item, index) of images"
|
||||
:key="index"
|
||||
:value="item.option"
|
||||
:label="item.option"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="35">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handlePortsDelete(scope.$index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
<el-input v-else v-model="form.image" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="forcePull">
|
||||
<el-checkbox v-model="form.forcePull">
|
||||
{{ $t('container.forcePull') }}
|
||||
</el-checkbox>
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</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-card class="widthClass">
|
||||
<el-table v-if="form.exposedPorts.length !== 0" :data="form.exposedPorts">
|
||||
<el-table-column :label="$t('container.server')" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
:placeholder="$t('container.serverExample')"
|
||||
v-model="row.host"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.container')" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
:placeholder="$t('container.containerExample')"
|
||||
v-model="row.containerPort"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.protocol')" min-width="50">
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="35">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handlePortsDelete(scope.$index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-button class="ml-3 mt-2" @click="handlePortsAdd()">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
<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-input 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-form-item>
|
||||
|
||||
<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-radio-group v-model="row.type">
|
||||
<el-radio-button value="volume">
|
||||
{{ $t('container.volumeOption') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button value="bind">
|
||||
{{ $t('container.hostOption') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button
|
||||
class="float-right mt-3"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleVolumesDelete(index)"
|
||||
>
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<el-row class="mt-4" :gutter="5">
|
||||
<el-col :span="10">
|
||||
<el-form-item
|
||||
v-if="row.type === 'volume'"
|
||||
:label="$t('container.volumeOption')"
|
||||
>
|
||||
<el-select filterable v-model="row.sourceDir">
|
||||
<div v-for="(item, indexV) of volumes" :key="indexV">
|
||||
<el-tooltip
|
||||
:hide-after="20"
|
||||
:content="item.option"
|
||||
placement="top"
|
||||
>
|
||||
<el-option
|
||||
:value="item.option"
|
||||
:label="item.option.substring(0, 30)"
|
||||
/>
|
||||
</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-model="row.containerDir" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-button @click="handleVolumesAdd()">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</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 class="ml-3 mt-2" @click="handlePortsAdd()">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.network')" prop="network">
|
||||
<el-select v-model="dialogData.rowData!.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-input v-model="dialogData.rowData!.ipv4" :placeholder="$t('container.inputIpv4')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="ipv6" prop="ipv6">
|
||||
<el-input v-model="dialogData.rowData!.ipv6" :placeholder="$t('container.inputIpv6')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('container.mount')">
|
||||
<div v-for="(row, index) in dialogData.rowData!.volumes" :key="index" style="width: 100%">
|
||||
<el-card class="mt-1">
|
||||
<el-radio-group v-model="row.type">
|
||||
<el-radio-button value="volume">{{ $t('container.volumeOption') }}</el-radio-button>
|
||||
<el-radio-button value="bind">{{ $t('container.hostOption') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button class="float-right mt-3" link type="primary" @click="handleVolumesDelete(index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<el-row class="mt-4" :gutter="5">
|
||||
<el-col :span="10">
|
||||
<el-form-item v-if="row.type === 'volume'" :label="$t('container.volumeOption')">
|
||||
<el-select filterable v-model="row.sourceDir">
|
||||
<div v-for="(item, indexV) of volumes" :key="indexV">
|
||||
<el-tooltip :hide-after="20" :content="item.option" placement="top">
|
||||
<el-option :value="item.option" :label="item.option.substring(0, 30)" />
|
||||
</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-model="row.containerDir" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-button @click="handleVolumesAdd()">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="Command" prop="cmdStr">
|
||||
<el-input v-model="dialogData.rowData!.cmdStr" :placeholder="$t('container.cmdHelper')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Entrypoint" prop="entrypointStr">
|
||||
<el-input v-model="dialogData.rowData!.entrypointStr" :placeholder="$t('container.entrypointHelper')" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="autoRemove">
|
||||
<el-checkbox v-model="dialogData.rowData!.autoRemove">
|
||||
{{ $t('container.autoRemove') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="dialogData.rowData!.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="dialogData.rowData!.tty">{{ $t('container.tty') }}</el-checkbox>
|
||||
<el-checkbox v-model="dialogData.rowData!.openStdin">
|
||||
{{ $t('container.openStdin') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
||||
<el-radio-group v-model="dialogData.rowData!.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="dialogData.rowData!.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="dialogData.rowData!.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="dialogData.rowData!.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="dialogData.rowData!.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="dialogData.rowData!.envStr"
|
||||
/>
|
||||
</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>
|
||||
</DrawerPro>
|
||||
<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-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -262,6 +296,7 @@ import {
|
||||
loadResourceLimit,
|
||||
listNetwork,
|
||||
searchContainer,
|
||||
loadContainerInfo,
|
||||
} from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
@ -269,61 +304,90 @@ import { checkIpV4V6, checkPort } from '@/utils/util';
|
||||
import router from '@/routers';
|
||||
|
||||
const loading = ref(false);
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Container.ContainerHelper;
|
||||
getTableList?: () => Promise<any>;
|
||||
}
|
||||
|
||||
const title = ref<string>('');
|
||||
const drawerVisible = ref(false);
|
||||
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
const isCreate = ref();
|
||||
const form = reactive<Container.ContainerHelper>({
|
||||
containerID: '',
|
||||
name: '',
|
||||
image: '',
|
||||
imageInput: false,
|
||||
forcePull: false,
|
||||
network: '',
|
||||
ipv4: '',
|
||||
ipv6: '',
|
||||
cmdStr: '',
|
||||
entrypointStr: '',
|
||||
memoryItem: 0,
|
||||
cmd: [],
|
||||
openStdin: false,
|
||||
tty: false,
|
||||
entrypoint: [],
|
||||
publishAllPorts: false,
|
||||
exposedPorts: [],
|
||||
nanoCPUs: 0,
|
||||
cpuShares: 1024,
|
||||
memory: 0,
|
||||
volumes: [],
|
||||
privileged: false,
|
||||
autoRemove: false,
|
||||
labels: [],
|
||||
labelsStr: '',
|
||||
env: [],
|
||||
envStr: '',
|
||||
restartPolicy: 'no',
|
||||
});
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
title.value = i18n.global.t('container.' + dialogData.value.title);
|
||||
if (params.title === 'edit') {
|
||||
dialogData.value.rowData.memory = Number(dialogData.value.rowData.memory.toFixed(2));
|
||||
dialogData.value.rowData.cmd = dialogData.value.rowData.cmd || [];
|
||||
let itemCmd = '';
|
||||
for (const item of dialogData.value.rowData.cmd) {
|
||||
itemCmd += `'${item}' `;
|
||||
}
|
||||
dialogData.value.rowData.cmdStr = itemCmd ? itemCmd.substring(0, itemCmd.length - 1) : '';
|
||||
|
||||
let itemEntrypoint = '';
|
||||
if (dialogData.value.rowData?.entrypoint) {
|
||||
for (const item of dialogData.value.rowData.entrypoint) {
|
||||
itemEntrypoint += `'${item}' `;
|
||||
const search = async () => {
|
||||
if (!isCreate.value) {
|
||||
const res = await loadContainerInfo(form.containerID);
|
||||
if (res.data) {
|
||||
form.name = res.data.name;
|
||||
form.image = res.data.image;
|
||||
form.network = res.data.network;
|
||||
form.ipv4 = res.data.ipv4;
|
||||
form.ipv6 = res.data.ipv6;
|
||||
form.openStdin = res.data.openStdin;
|
||||
form.tty = res.data.tty;
|
||||
form.publishAllPorts = res.data.publishAllPorts;
|
||||
form.nanoCPUs = res.data.nanoCPUs;
|
||||
form.cpuShares = res.data.cpuShares;
|
||||
form.privileged = res.data.privileged;
|
||||
form.autoRemove = res.data.autoRemove;
|
||||
form.restartPolicy = res.data.restartPolicy;
|
||||
form.memory = Number(res.data.memory.toFixed(2));
|
||||
form.cmd = res.data.cmd || [];
|
||||
let itemCmd = '';
|
||||
for (const item of form.cmd) {
|
||||
itemCmd += `'${item}' `;
|
||||
}
|
||||
}
|
||||
form.cmdStr = itemCmd ? itemCmd.substring(0, itemCmd.length - 1) : '';
|
||||
|
||||
dialogData.value.rowData.entrypointStr = itemEntrypoint
|
||||
? itemEntrypoint.substring(0, itemEntrypoint.length - 1)
|
||||
: '';
|
||||
dialogData.value.rowData.labels = dialogData.value.rowData.labels || [];
|
||||
dialogData.value.rowData.env = dialogData.value.rowData.env || [];
|
||||
dialogData.value.rowData.labelsStr = dialogData.value.rowData.labels.join('\n');
|
||||
dialogData.value.rowData.envStr = dialogData.value.rowData.env.join('\n');
|
||||
dialogData.value.rowData.exposedPorts = dialogData.value.rowData.exposedPorts || [];
|
||||
for (const item of dialogData.value.rowData.exposedPorts) {
|
||||
if (item.hostIP) {
|
||||
item.host = item.hostIP + ':' + item.hostPort;
|
||||
} else {
|
||||
item.host = item.hostPort;
|
||||
let itemEntrypoint = '';
|
||||
if (res.data.entrypoint) {
|
||||
for (const item of res.data.entrypoint) {
|
||||
itemEntrypoint += `'${item}' `;
|
||||
}
|
||||
}
|
||||
|
||||
form.entrypointStr = itemEntrypoint ? itemEntrypoint.substring(0, itemEntrypoint.length - 1) : '';
|
||||
form.labels = res.data.labels || [];
|
||||
form.env = res.data.env || [];
|
||||
form.labelsStr = res.data.labels.join('\n');
|
||||
form.envStr = res.data.env.join('\n');
|
||||
form.exposedPorts = res.data.exposedPorts || [];
|
||||
for (const item of res.data.exposedPorts) {
|
||||
if (item.hostIP) {
|
||||
item.host = item.hostIP + ':' + item.hostPort;
|
||||
} else {
|
||||
item.host = item.hostPort;
|
||||
}
|
||||
}
|
||||
form.volumes = res.data.volumes || [];
|
||||
}
|
||||
dialogData.value.rowData.volumes = dialogData.value.rowData.volumes || [];
|
||||
}
|
||||
loadLimit();
|
||||
loadImageOptions();
|
||||
loadVolumeOptions();
|
||||
loadNetworkOptions();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const images = ref();
|
||||
const volumes = ref();
|
||||
@ -333,11 +397,6 @@ const limits = ref<Container.ResourceLimit>({
|
||||
memory: null as number,
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.containerName],
|
||||
image: [Rules.imageName],
|
||||
@ -349,6 +408,10 @@ const rules = reactive({
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const goBack = () => {
|
||||
router.push({ name: 'Container' });
|
||||
};
|
||||
|
||||
const handlePortsAdd = () => {
|
||||
let item = {
|
||||
host: '',
|
||||
@ -357,10 +420,10 @@ const handlePortsAdd = () => {
|
||||
hostPort: '',
|
||||
protocol: 'tcp',
|
||||
};
|
||||
dialogData.value.rowData!.exposedPorts.push(item);
|
||||
form.exposedPorts.push(item);
|
||||
};
|
||||
const handlePortsDelete = (index: number) => {
|
||||
dialogData.value.rowData!.exposedPorts.splice(index, 1);
|
||||
form.exposedPorts.splice(index, 1);
|
||||
};
|
||||
|
||||
const goRouter = async () => {
|
||||
@ -374,10 +437,10 @@ const handleVolumesAdd = () => {
|
||||
containerDir: '',
|
||||
mode: 'rw',
|
||||
};
|
||||
dialogData.value.rowData!.volumes.push(item);
|
||||
form.volumes.push(item);
|
||||
};
|
||||
const handleVolumesDelete = (index: number) => {
|
||||
dialogData.value.rowData!.volumes.splice(index, 1);
|
||||
form.volumes.splice(index, 1);
|
||||
};
|
||||
|
||||
const loadLimit = async () => {
|
||||
@ -399,8 +462,8 @@ const loadNetworkOptions = async () => {
|
||||
networks.value = res.data;
|
||||
};
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (dialogData.value.rowData!.volumes.length !== 0) {
|
||||
for (const item of dialogData.value.rowData!.volumes) {
|
||||
if (form.volumes.length !== 0) {
|
||||
for (const item of form.volumes) {
|
||||
if (!item.containerDir || !item.sourceDir) {
|
||||
MsgError(i18n.global.t('container.volumeHelper'));
|
||||
return;
|
||||
@ -410,62 +473,60 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (dialogData.value.rowData?.envStr) {
|
||||
dialogData.value.rowData.env = dialogData.value.rowData!.envStr.split('\n');
|
||||
if (form.envStr) {
|
||||
form.env = form.envStr.split('\n');
|
||||
}
|
||||
if (dialogData.value.rowData?.labelsStr) {
|
||||
dialogData.value.rowData!.labels = dialogData.value.rowData!.labelsStr.split('\n');
|
||||
if (form.labelsStr) {
|
||||
form.labels = form.labelsStr.split('\n');
|
||||
}
|
||||
dialogData.value.rowData!.cmd = [];
|
||||
if (dialogData.value.rowData?.cmdStr) {
|
||||
if (dialogData.value.rowData?.cmdStr.indexOf(`'`) !== -1) {
|
||||
let itemCmd = dialogData.value.rowData!.cmdStr.split(`'`);
|
||||
form.cmd = [];
|
||||
if (form.cmdStr) {
|
||||
if (form.cmdStr.indexOf(`'`) !== -1) {
|
||||
let itemCmd = form.cmdStr.split(`'`);
|
||||
for (const cmd of itemCmd) {
|
||||
if (cmd && cmd !== ' ') {
|
||||
dialogData.value.rowData!.cmd.push(cmd);
|
||||
form.cmd.push(cmd);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let itemCmd = dialogData.value.rowData!.cmdStr.split(` `);
|
||||
let itemCmd = form.cmdStr.split(` `);
|
||||
for (const cmd of itemCmd) {
|
||||
dialogData.value.rowData!.cmd.push(cmd);
|
||||
form.cmd.push(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
dialogData.value.rowData!.entrypoint = [];
|
||||
if (dialogData.value.rowData?.entrypointStr) {
|
||||
if (dialogData.value.rowData?.entrypointStr.indexOf(`'`) !== -1) {
|
||||
let itemEntrypoint = dialogData.value.rowData!.entrypointStr.split(`'`);
|
||||
form.entrypoint = [];
|
||||
if (form.entrypointStr) {
|
||||
if (form.entrypointStr.indexOf(`'`) !== -1) {
|
||||
let itemEntrypoint = form.entrypointStr.split(`'`);
|
||||
for (const entry of itemEntrypoint) {
|
||||
if (entry && entry !== ' ') {
|
||||
dialogData.value.rowData!.entrypoint.push(entry);
|
||||
form.entrypoint.push(entry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let itemEntrypoint = dialogData.value.rowData!.entrypointStr.split(` `);
|
||||
let itemEntrypoint = form.entrypointStr.split(` `);
|
||||
for (const entry of itemEntrypoint) {
|
||||
dialogData.value.rowData!.entrypoint.push(entry);
|
||||
form.entrypoint.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dialogData.value.rowData!.publishAllPorts) {
|
||||
dialogData.value.rowData!.exposedPorts = [];
|
||||
if (form.publishAllPorts) {
|
||||
form.exposedPorts = [];
|
||||
} else {
|
||||
if (!checkPortValid()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
dialogData.value.rowData!.memory = Number(dialogData.value.rowData!.memory);
|
||||
dialogData.value.rowData!.nanoCPUs = Number(dialogData.value.rowData!.nanoCPUs);
|
||||
form.memory = Number(form.memory);
|
||||
form.nanoCPUs = Number(form.nanoCPUs);
|
||||
|
||||
loading.value = true;
|
||||
if (dialogData.value.title === 'create') {
|
||||
await createContainer(dialogData.value.rowData!)
|
||||
if (isCreate.value) {
|
||||
await createContainer(form)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
@ -480,12 +541,10 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
},
|
||||
)
|
||||
.then(async () => {
|
||||
await updateContainer(dialogData.value.rowData!)
|
||||
await updateContainer(form)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
updateContainerID();
|
||||
@ -504,24 +563,24 @@ const updateContainerID = async () => {
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
state: 'all',
|
||||
name: dialogData.value.rowData.name,
|
||||
name: form.name,
|
||||
filters: '',
|
||||
orderBy: 'created_at',
|
||||
order: 'null',
|
||||
};
|
||||
await searchContainer(params).then((res) => {
|
||||
if (res.data.items?.length === 1) {
|
||||
dialogData.value.rowData.containerID = res.data.items[0].containerID;
|
||||
form.containerID = res.data.items[0].containerID;
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const checkPortValid = () => {
|
||||
if (dialogData.value.rowData!.exposedPorts.length === 0) {
|
||||
if (form.exposedPorts.length === 0) {
|
||||
return true;
|
||||
}
|
||||
for (const port of dialogData.value.rowData!.exposedPorts) {
|
||||
for (const port of form.exposedPorts) {
|
||||
if (port.host.indexOf(':') !== -1) {
|
||||
port.hostIP = port.host.substring(0, port.host.lastIndexOf(':'));
|
||||
if (checkIpV4V6(port.hostIP)) {
|
||||
@ -573,8 +632,14 @@ const isFromApp = (rowData: Container.ContainerHelper) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query.containerID) {
|
||||
isCreate.value = false;
|
||||
form.containerID = String(router.currentRoute.value.query.containerID);
|
||||
} else {
|
||||
isCreate.value = true;
|
||||
}
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user