mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-27 09:49:28 +08:00
feat: Add container orchestration navigation in app store (#9950)
Refs #8431
This commit is contained in:
parent
4df0c3e461
commit
6a8bd11fc9
17 changed files with 206 additions and 193 deletions
|
|
@ -229,15 +229,16 @@ type BatchDelete struct {
|
|||
}
|
||||
|
||||
type ComposeInfo struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
ContainerNumber int `json:"containerNumber"`
|
||||
ConfigFile string `json:"configFile"`
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env []string `json:"env"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
ContainerCount int `json:"containerCount"`
|
||||
RunningCount int `json:"runningCount"`
|
||||
ConfigFile string `json:"configFile"`
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeContainer struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
|
|
|
|||
|
|
@ -10,12 +10,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
|
|
@ -24,6 +21,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
|
@ -54,14 +52,13 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||
}
|
||||
|
||||
composeCreatedByLocal, _ := composeRepo.ListRecord()
|
||||
|
||||
composeLocalMap := make(map[string]dto.ComposeInfo)
|
||||
for _, localItem := range composeCreatedByLocal {
|
||||
composeItemLocal := dto.ComposeInfo{
|
||||
ContainerNumber: 0,
|
||||
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
|
||||
ConfigFile: localItem.Path,
|
||||
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
|
||||
ContainerCount: 0,
|
||||
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
|
||||
ConfigFile: localItem.Path,
|
||||
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
|
||||
}
|
||||
composeItemLocal.CreatedBy = "1Panel"
|
||||
composeItemLocal.Path = localItem.Path
|
||||
|
|
@ -78,18 +75,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||
CreateTime: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
|
||||
}
|
||||
if compose, has := composeMap[name]; has {
|
||||
compose.ContainerNumber++
|
||||
compose.ContainerCount++
|
||||
if strings.ToLower(containerItem.State) == "running" {
|
||||
compose.RunningCount++
|
||||
}
|
||||
compose.Containers = append(compose.Containers, containerItem)
|
||||
composeMap[name] = compose
|
||||
} else {
|
||||
config := container.Labels[composeConfigLabel]
|
||||
workdir := container.Labels[composeWorkdirLabel]
|
||||
composeItem := dto.ComposeInfo{
|
||||
ContainerNumber: 1,
|
||||
CreatedAt: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
|
||||
ConfigFile: config,
|
||||
Workdir: workdir,
|
||||
Containers: []dto.ComposeContainer{containerItem},
|
||||
ContainerCount: 1,
|
||||
CreatedAt: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
|
||||
ConfigFile: config,
|
||||
Workdir: workdir,
|
||||
Containers: []dto.ComposeContainer{containerItem},
|
||||
}
|
||||
if strings.ToLower(containerItem.State) == "running" {
|
||||
composeItem.RunningCount = 1
|
||||
}
|
||||
createdBy, ok := container.Labels[composeCreatedBy]
|
||||
if ok {
|
||||
|
|
@ -118,8 +121,8 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||
}
|
||||
for key, item := range composeMap {
|
||||
if existingItem, exists := mergedMap[key]; exists {
|
||||
if item.ContainerNumber > 0 {
|
||||
if existingItem.ContainerNumber <= 0 {
|
||||
if item.ContainerCount > 0 {
|
||||
if existingItem.ContainerCount <= 0 {
|
||||
mergedMap[key] = item
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,8 @@ export namespace Container {
|
|||
name: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
containerNumber: number;
|
||||
containerCount: number;
|
||||
runningCount: number;
|
||||
configFile: string;
|
||||
workdir: string;
|
||||
path: string;
|
||||
|
|
|
|||
|
|
@ -930,7 +930,7 @@ const message = {
|
|||
containerNumber: 'Container number',
|
||||
containerStatus: 'Container status',
|
||||
exited: 'Exited',
|
||||
running: 'Running',
|
||||
running: 'Running ( {0} / {1} )',
|
||||
composeDetailHelper:
|
||||
'The compose is created external to 1Panel. The start and stop operations are not supported.',
|
||||
composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?',
|
||||
|
|
|
|||
|
|
@ -904,7 +904,7 @@ const message = {
|
|||
containerNumber: 'コンテナ番号',
|
||||
containerStatus: 'コンテナステータス',
|
||||
exited: '終了',
|
||||
running: 'ランニング',
|
||||
running: 'ランニング ( {0} / {1} )',
|
||||
composeDetailHelper: '構成は1パネルの外部に作成されます。開始および停止操作はサポートされていません。',
|
||||
composeOperatorHelper: '{1}操作は{0}で実行されます。続けたいですか?',
|
||||
composeDownHelper:
|
||||
|
|
|
|||
|
|
@ -895,7 +895,7 @@ const message = {
|
|||
containerNumber: '컨테이너 수',
|
||||
containerStatus: '컨테이너 상태',
|
||||
exited: '종료됨',
|
||||
running: '실행 중',
|
||||
running: '실행 중 ( {0} / {1} )',
|
||||
composeDetailHelper: '이 컴포즈는 1Panel 외부에서 생성되었습니다. 시작 및 중지 작업은 지원되지 않습니다.',
|
||||
composeOperatorHelper: '{1} 작업이 {0}에서 수행됩니다. 계속 하시겠습니까?',
|
||||
composeDownHelper:
|
||||
|
|
|
|||
|
|
@ -923,7 +923,7 @@ const message = {
|
|||
containerNumber: 'Bilangan kontena',
|
||||
containerStatus: 'Status kontena',
|
||||
exited: 'Keluar',
|
||||
running: 'Berjalan',
|
||||
running: 'Berjalan ( {0} / {1} )',
|
||||
composeDetailHelper: 'Komposisi dibuat di luar 1Panel. Operasi mula dan berhenti tidak disokong.',
|
||||
composeOperatorHelper: 'Operasi {1} akan dilakukan pada {0}. Adakah anda mahu meneruskan?',
|
||||
composeDownHelper:
|
||||
|
|
|
|||
|
|
@ -917,7 +917,7 @@ const message = {
|
|||
containerNumber: 'Número de containers',
|
||||
containerStatus: 'Status do container',
|
||||
exited: 'Finalizado',
|
||||
running: 'Em execução',
|
||||
running: 'Em execução ( {0} / {1} )',
|
||||
composeDetailHelper:
|
||||
'A composição foi criada externamente ao 1Panel. As operações de iniciar e parar não são suportadas.',
|
||||
composeOperatorHelper: 'A operação {1} será realizada no {0}. Deseja continuar?',
|
||||
|
|
|
|||
|
|
@ -917,7 +917,7 @@ const message = {
|
|||
containerNumber: 'Количество контейнеров',
|
||||
containerStatus: 'Статус контейнера',
|
||||
exited: 'Завершен',
|
||||
running: 'Работает',
|
||||
running: 'Работает ( {0} / {1} )',
|
||||
composeDetailHelper: 'Compose создан вне 1Panel. Операции запуска и остановки не поддерживаются.',
|
||||
composeOperatorHelper: 'Операция {1} будет выполнена для {0}. Хотите продолжить?',
|
||||
composeDownHelper: 'Это остановит и удалит все контейнеры и сети под compose {0}. Хотите продолжить?',
|
||||
|
|
|
|||
|
|
@ -943,7 +943,7 @@ const message = {
|
|||
containerNumber: 'Konteyner sayısı',
|
||||
containerStatus: 'Konteyner durumu',
|
||||
exited: 'Çıktı',
|
||||
running: 'Çalışıyor',
|
||||
running: 'Çalışıyor ( {0} / {1} )',
|
||||
composeDetailHelper: 'Compose, 1Panel dışında oluşturulmuştur. Başlatma ve durdurma işlemleri desteklenmez.',
|
||||
composeOperatorHelper: '{0} üzerinde {1} işlemi gerçekleştirilecek. Devam etmek istiyor musunuz?',
|
||||
composeDownHelper:
|
||||
|
|
|
|||
|
|
@ -891,7 +891,7 @@ const message = {
|
|||
containerNumber: '容器數量',
|
||||
containerStatus: '容器狀態',
|
||||
exited: '已停止',
|
||||
running: '運行中',
|
||||
running: '運行中 ( {0} / {1} )',
|
||||
composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。',
|
||||
composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
|
||||
composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網絡,是否繼續?',
|
||||
|
|
|
|||
|
|
@ -889,7 +889,7 @@ const message = {
|
|||
containerNumber: '容器数量',
|
||||
containerStatus: '容器状态',
|
||||
exited: '已停止',
|
||||
running: '运行中',
|
||||
running: '运行中 ( {0} / {1} )',
|
||||
composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。',
|
||||
composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
|
||||
composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?',
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ router.afterEach((to) => {
|
|||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/cronjobs/cronjob');
|
||||
} else if (to.meta.activeMenu === '/containers' && to.path === '/containers/container/operate') {
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/containers/container');
|
||||
} else if (to.meta.activeMenu === '/containers' && to.path === '/containers/compose/detail') {
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/containers/compose');
|
||||
} else if (to.meta.activeMenu === '/toolbox' && to.path === '/toolbox/clam/setting') {
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/toolbox/clam');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const containerRouter = {
|
|||
},
|
||||
},
|
||||
{
|
||||
path: 'composeDetail/:filters?',
|
||||
path: 'compose/detail',
|
||||
name: 'ComposeDetail',
|
||||
component: () => import('@/views/container/compose/detail/index.vue'),
|
||||
props: true,
|
||||
|
|
|
|||
|
|
@ -150,6 +150,29 @@
|
|||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span class="ml-1">
|
||||
<el-tooltip
|
||||
v-if="mode !== 'upgrade'"
|
||||
effect="dark"
|
||||
:content="$t('container.compose')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="
|
||||
routerToNameWithQuery('ComposeDetail', {
|
||||
name: installed.name,
|
||||
uncached: 'true',
|
||||
})
|
||||
"
|
||||
>
|
||||
<el-icon>
|
||||
<SvgIcon iconName="p-docker" />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span class="ml-1" v-if="mode === 'installed'">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
|
|
@ -433,7 +456,7 @@ import IgnoreApp from '@/views/app-store/installed/ignore/create/index.vue';
|
|||
import { getAgentSettingByKey } from '@/api/modules/setting';
|
||||
import Tags from '@/views/app-store/components/tag.vue';
|
||||
import SvgIcon from '@/components/svg-icon/svg-icon.vue';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import { routerToFileWithPath, routerToNameWithQuery } from '@/utils/router';
|
||||
|
||||
const data = ref<any>();
|
||||
const loading = ref(false);
|
||||
|
|
|
|||
|
|
@ -1,63 +1,31 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="app-status card-interval">
|
||||
<el-card>
|
||||
<el-row :gutter="20" class="items-center">
|
||||
<div class="ml-5">
|
||||
<el-tag effect="dark" type="success">{{ composeName }}</el-tag>
|
||||
</div>
|
||||
<div v-if="createdBy === '1Panel'" class="ml-10">
|
||||
<el-button link type="primary" @click="onComposeOperate('up')">
|
||||
{{ $t('commons.operate.start') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="primary" @click="onComposeOperate('stop')">
|
||||
{{ $t('commons.operate.stop') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="primary" @click="onComposeOperate('down')">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert
|
||||
style="margin-top: -5px; margin-left: 50px"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="$t('container.composeDetailHelper')"
|
||||
type="info"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
<LayoutContent
|
||||
style="margin-top: 10px"
|
||||
back-name="Compose"
|
||||
:title="$t('container.containerList')"
|
||||
:title="$t('container.containerList') + ' [ ' + composeName + ' ]'"
|
||||
:reload="true"
|
||||
>
|
||||
<template #main>
|
||||
<el-button-group>
|
||||
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
|
||||
<el-button :disabled="checkStatus('start', null)" @click="onOperate('start', null)">
|
||||
{{ $t('commons.operate.start') }}
|
||||
</el-button>
|
||||
<el-button :disabled="checkStatus('stop')" @click="onOperate('stop')">
|
||||
<el-button :disabled="checkStatus('stop', null)" @click="onOperate('stop', null)">
|
||||
{{ $t('commons.operate.stop') }}
|
||||
</el-button>
|
||||
<el-button :disabled="checkStatus('restart')" @click="onOperate('restart')">
|
||||
<el-button :disabled="checkStatus('restart', null)" @click="onOperate('restart', null)">
|
||||
{{ $t('commons.button.restart') }}
|
||||
</el-button>
|
||||
<el-button :disabled="checkStatus('kill')" @click="onOperate('kill')">
|
||||
<el-button :disabled="checkStatus('kill', null)" @click="onOperate('kill', null)">
|
||||
{{ $t('container.kill') }}
|
||||
</el-button>
|
||||
<el-button :disabled="checkStatus('pause')" @click="onOperate('pause')">
|
||||
<el-button :disabled="checkStatus('pause', null)" @click="onOperate('pause', null)">
|
||||
{{ $t('container.pause') }}
|
||||
</el-button>
|
||||
<el-button :disabled="checkStatus('unpause')" @click="onOperate('unpause')">
|
||||
<el-button :disabled="checkStatus('unpause', null)" @click="onOperate('unpause', null)">
|
||||
{{ $t('container.unpause') }}
|
||||
</el-button>
|
||||
<el-button :disabled="checkStatus('remove')" @click="onOperate('remove')">
|
||||
<el-button :disabled="checkStatus('remove', null)" @click="onOperate('remove', null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
|
|
@ -88,9 +56,51 @@
|
|||
min-width="100"
|
||||
prop="imageName"
|
||||
/>
|
||||
<el-table-column :label="$t('commons.table.status')" min-width="50" prop="state" fix>
|
||||
<el-table-column :label="$t('commons.table.status')" min-width="100" prop="state" sortable>
|
||||
<template #default="{ row }">
|
||||
<Status :key="row.state" :status="row.state"></Status>
|
||||
<el-dropdown placement="bottom">
|
||||
<Status :key="row.state" :status="row.state" :operate="true"></Status>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('start', row)"
|
||||
@click="onOperate('start', row)"
|
||||
>
|
||||
{{ $t('commons.operate.start') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('stop', row)"
|
||||
@click="onOperate('stop', row)"
|
||||
>
|
||||
{{ $t('commons.operate.stop') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:disabled="checkStatus('restart', row)"
|
||||
@click="onOperate('restart', row)"
|
||||
>
|
||||
{{ $t('commons.button.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 :label="$t('container.upTime')" min-width="100" prop="runTime" fix />
|
||||
|
|
@ -128,35 +138,15 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
|||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||
import Status from '@/components/status/index.vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { composeOperator, containerOperator, inspect, searchContainer } from '@/api/modules/container';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { containerOperator, inspect, searchContainer } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import router from '@/routers';
|
||||
|
||||
const composeName = ref();
|
||||
const composePath = ref();
|
||||
const filters = ref();
|
||||
const createdBy = ref();
|
||||
|
||||
const dialogContainerLogRef = ref();
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
interface DialogProps {
|
||||
createdBy: string;
|
||||
name: string;
|
||||
path: string;
|
||||
filters: string;
|
||||
}
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
composePath.value = props.path;
|
||||
composeName.value = props.name;
|
||||
filters.value = props.filters;
|
||||
createdBy.value = props.createdBy;
|
||||
search();
|
||||
};
|
||||
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
|
|
@ -169,13 +159,12 @@ const paginationConfig = reactive({
|
|||
const loading = ref(false);
|
||||
|
||||
const search = async () => {
|
||||
let filterItem = filters.value;
|
||||
let params = {
|
||||
name: '',
|
||||
state: 'all',
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
filters: filterItem,
|
||||
filters: 'com.docker.compose.project=' + composeName.value,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
};
|
||||
|
|
@ -204,34 +193,35 @@ const onInspect = async (id: string) => {
|
|||
myDetail.value!.acceptParams(param);
|
||||
};
|
||||
|
||||
const checkStatus = (operation: string) => {
|
||||
if (selects.value.length < 1) {
|
||||
const checkStatus = (operation: string, row: Container.ContainerInfo | null) => {
|
||||
let opList = row ? [row] : selects.value;
|
||||
if (opList.length < 1) {
|
||||
return true;
|
||||
}
|
||||
switch (operation) {
|
||||
case 'start':
|
||||
for (const item of selects.value) {
|
||||
for (const item of opList) {
|
||||
if (item.state === 'running') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case 'stop':
|
||||
for (const item of selects.value) {
|
||||
for (const item of opList) {
|
||||
if (item.state === 'stopped' || item.state === 'exited') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case 'pause':
|
||||
for (const item of selects.value) {
|
||||
for (const item of opList) {
|
||||
if (item.state === 'paused' || item.state === 'exited') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case 'unpause':
|
||||
for (const item of selects.value) {
|
||||
for (const item of opList) {
|
||||
if (item.state !== 'paused') {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -240,54 +230,24 @@ const checkStatus = (operation: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
const onOperate = async (op: string) => {
|
||||
const onOperate = async (op: string, row: Container.ContainerInfo | null) => {
|
||||
let opList = row ? [row] : selects.value;
|
||||
let msg = i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + op)]);
|
||||
let names = [];
|
||||
for (const item of selects.value) {
|
||||
for (const item of opList) {
|
||||
names.push(item.name);
|
||||
if (item.isFromApp) {
|
||||
msg = i18n.global.t('container.operatorAppHelper', [i18n.global.t('container.' + op)]);
|
||||
}
|
||||
}
|
||||
const successMsg = `${i18n.global.t('container.' + op)}${i18n.global.t('commons.status.success')}`;
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('container.' + op),
|
||||
names: names,
|
||||
msg: msg,
|
||||
api: containerOperator,
|
||||
params: { names: names, operation: op },
|
||||
successMsg: `${i18n.global.t('container.' + op)}${i18n.global.t('commons.status.success')}`,
|
||||
});
|
||||
};
|
||||
|
||||
const onComposeOperate = async (operation: string) => {
|
||||
let mes =
|
||||
operation === 'down'
|
||||
? i18n.global.t('container.composeDownHelper', [composeName.value])
|
||||
: i18n.global.t('container.composeOperatorHelper', [
|
||||
composeName.value,
|
||||
i18n.global.t('container.' + operation),
|
||||
]);
|
||||
ElMessageBox.confirm(mes, i18n.global.t('container.' + operation), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
let params = {
|
||||
name: composeName.value,
|
||||
path: composePath.value,
|
||||
operation: operation,
|
||||
withFile: false,
|
||||
};
|
||||
loading.value = true;
|
||||
await composeOperator(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
successMsg,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -329,8 +289,11 @@ const buttons = [
|
|||
},
|
||||
];
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query?.name) {
|
||||
composeName.value = router.currentRoute.value.query.name;
|
||||
}
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -338,8 +301,4 @@ defineExpose({
|
|||
.app-content {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<div v-show="isOnDetail">
|
||||
<ComposeDetail ref="composeDetailRef" />
|
||||
</div>
|
||||
<docker-status
|
||||
v-model:isActive="isActive"
|
||||
v-model:isExist="isExist"
|
||||
|
|
@ -10,7 +7,7 @@
|
|||
@search="search"
|
||||
/>
|
||||
|
||||
<LayoutContent v-if="!isOnDetail && isExist" :title="$t('container.compose', 2)" :class="{ mask: !isActive }">
|
||||
<LayoutContent v-if="isExist" :title="$t('container.compose', 2)" :class="{ mask: !isActive }">
|
||||
<template #leftToolBar>
|
||||
<el-button type="primary" @click="onOpenDialog()">
|
||||
{{ $t('container.createCompose') }}
|
||||
|
|
@ -60,21 +57,22 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.containerStatus')" min-width="80" fix>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
{{ getContainerStatus(scope.row.containers) }}
|
||||
</div>
|
||||
<template #default="{ row }">
|
||||
<el-text class="mx-1" v-if="row.containerCount == 0" type="danger">
|
||||
{{ $t('container.exited') }}
|
||||
</el-text>
|
||||
<el-text
|
||||
v-else
|
||||
class="mx-1"
|
||||
:type="row.containerCount === row.runningCount ? 'success' : 'warning'"
|
||||
>
|
||||
{{ $t('container.running', [row.runningCount, row.containerCount]) }}
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('container.containerNumber')"
|
||||
prop="containerNumber"
|
||||
min-width="80"
|
||||
fix
|
||||
/>
|
||||
<el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" min-width="80" fix />
|
||||
<fu-table-operations
|
||||
width="200px"
|
||||
width="300px"
|
||||
:ellipsis="10"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
|
|
@ -95,19 +93,17 @@ import { reactive, ref } from 'vue';
|
|||
import EditDialog from '@/views/container/compose/edit/index.vue';
|
||||
import CreateDialog from '@/views/container/compose/create/index.vue';
|
||||
import DeleteDialog from '@/views/container/compose/delete/index.vue';
|
||||
import ComposeDetail from '@/views/container/compose/detail/index.vue';
|
||||
import { inspect, searchCompose } from '@/api/modules/container';
|
||||
import { composeOperator, inspect, searchCompose } from '@/api/modules/container';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import { routerToFileWithPath, routerToNameWithQuery } from '@/utils/router';
|
||||
import { MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const isOnDetail = ref(false);
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'container-compose-page-size',
|
||||
currentPage: 1,
|
||||
|
|
@ -144,28 +140,8 @@ const search = async () => {
|
|||
});
|
||||
};
|
||||
|
||||
const composeDetailRef = ref();
|
||||
const loadDetail = async (row: Container.ComposeInfo) => {
|
||||
let params = {
|
||||
createdBy: row.createdBy,
|
||||
name: row.name,
|
||||
path: row.path,
|
||||
filters: 'com.docker.compose.project=' + row.name,
|
||||
};
|
||||
isOnDetail.value = true;
|
||||
composeDetailRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const getContainerStatus = (containers) => {
|
||||
const safeContainers = containers || [];
|
||||
const runningCount = safeContainers.filter((container) => container.state.toLowerCase() === 'running').length;
|
||||
const totalCount = safeContainers.length;
|
||||
const statusText = runningCount > 0 ? 'Running' : 'Exited';
|
||||
if (statusText === 'Exited') {
|
||||
return i18n.global.t('container.exited');
|
||||
} else {
|
||||
return i18n.global.t('container.running') + ` (${runningCount}/${totalCount})`;
|
||||
}
|
||||
routerToNameWithQuery('ComposeDetail', { name: row.name });
|
||||
};
|
||||
|
||||
const dialogRef = ref();
|
||||
|
|
@ -195,6 +171,42 @@ const onEdit = async (row: Container.ComposeInfo) => {
|
|||
dialogEditRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const onComposeOperate = async (operation: string, row: any) => {
|
||||
if (row.createdBy !== '1Panel' && row.createdBy !== 'App') {
|
||||
MsgInfo(i18n.global.t('container.composeDetailHelper'));
|
||||
return;
|
||||
}
|
||||
let mes =
|
||||
operation === 'down'
|
||||
? i18n.global.t('container.composeDownHelper', [row.name])
|
||||
: i18n.global.t('container.composeOperatorHelper', [
|
||||
row.name,
|
||||
i18n.global.t('commons.operate.' + operation),
|
||||
]);
|
||||
ElMessageBox.confirm(mes, i18n.global.t('commons.operate.' + operation), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
let params = {
|
||||
name: row.name,
|
||||
path: row.path,
|
||||
operation: operation,
|
||||
withFile: false,
|
||||
};
|
||||
loading.value = true;
|
||||
await composeOperator(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
|
|
@ -206,7 +218,19 @@ const buttons = [
|
|||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
label: i18n.global.t('commons.operate.start'),
|
||||
click: (row: Container.ComposeInfo) => {
|
||||
onComposeOperate('up', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.operate.stop'),
|
||||
click: (row: Container.ComposeInfo) => {
|
||||
onComposeOperate('stop', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.operate.delete'),
|
||||
click: (row: Container.ComposeInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue