feat: 增加容器概览页面 (#6614)

This commit is contained in:
ssongliu 2024-09-29 14:27:45 +08:00 committed by GitHub
parent ed75ecc63b
commit 53e9af1a62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 273 additions and 2 deletions

View file

@ -48,6 +48,16 @@ type ContainerStatus struct {
Removing uint `json:"removing"`
Exited uint `json:"exited"`
Dead uint `json:"dead"`
ContainerCount int `json:"containerCount"`
ComposeCount int `json:"composeCount"`
ComposeTemplateCount int `json:"composeTemplateCount"`
ImageCount int `json:"imageCount"`
NetworkCount int `json:"networkCount"`
VolumeCount int `json:"volumeCount"`
RepoCount int `json:"repoCount"`
ImageSize uint64 `json:"imageSize"`
}
type ResourceLimit struct {
CPU int `json:"cpu"`

View file

@ -40,6 +40,7 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/gorilla/websocket"
@ -248,7 +249,22 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
return data, err
}
defer client.Close()
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
c := context.Background()
images, _ := client.ImageList(c, image.ListOptions{})
for _, image := range images {
data.ImageSize += uint64(image.Size)
}
data.ImageCount = len(images)
repo, _ := imageRepoRepo.List()
data.RepoCount = len(repo)
templates, _ := composeRepo.List()
data.ComposeTemplateCount = len(templates)
networks, _ := client.NetworkList(c, network.ListOptions{})
data.NetworkCount = len(networks)
volumes, _ := client.VolumeList(c, volume.ListOptions{})
data.VolumeCount = len(volumes.Volumes)
data.ComposeCount = loadComposeCount(client)
containers, err := client.ContainerList(c, container.ListOptions{All: true})
if err != nil {
return data, err
}
@ -271,6 +287,7 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
data.Removing++
}
}
data.ContainerCount = int(data.All)
return data, nil
}
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
@ -1363,3 +1380,29 @@ func simplifyPort(ports []types.Port) []string {
}
return datas
}
func loadComposeCount(client *client.Client) int {
options := container.ListOptions{All: true}
options.Filters = filters.NewArgs()
options.Filters.Add("label", composeProjectLabel)
list, err := client.ContainerList(context.Background(), options)
if err != nil {
return 0
}
composeCreatedByLocal, _ := composeRepo.ListRecord()
composeMap := make(map[string]struct{})
for _, container := range list {
if name, ok := container.Labels[composeProjectLabel]; ok {
if _, has := composeMap[name]; !has {
composeMap[name] = struct{}{}
}
}
}
for _, compose := range composeCreatedByLocal {
if _, has := composeMap[compose.Name]; !has {
composeMap[compose.Name] = struct{}{}
}
}
return len(composeMap)
}

View file

@ -33,6 +33,16 @@ export namespace Container {
removing: number;
exited: number;
dead: number;
containerCount: number;
composeCount: number;
composeTemplateCount: number;
imageCount: number;
networkCount: number;
volumeCount: number;
repoCount: number;
imageSize: number;
}
export interface ResourceLimit {
cpu: number;

View file

@ -794,6 +794,7 @@ const message = {
composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?',
setting: 'Setting',
goSetting: 'Go to edit',
operatorStatusHelper: 'This action will {0} Docker service, do you want to continue?',
dockerStatus: 'Docker Service',
daemonJsonPathHelper: 'Ensure that the configuration path is the same as that specified in docker.service.',

View file

@ -763,6 +763,7 @@ const message = {
composeOperatorHelper: '將對 {0} 進行 {1} 操作是否繼續',
setting: '配置',
goSetting: '去修改',
operatorStatusHelper: '此操作將{0}Docker 服務是否繼續',
dockerStatus: 'Docker 服務',
daemonJsonPathHelper: '請保證配置路徑與 docker.service 中指定的配置路徑保持一致',

View file

@ -764,6 +764,7 @@ const message = {
composeOperatorHelper: '将对 {0} 进行 {1} 操作是否继续',
setting: '配置',
goSetting: '去修改',
operatorStatusHelper: '此操作将{0}Docker 服务是否继续',
dockerStatus: 'Docker 服务',
daemonJsonPathHelper: '请保证配置路径与 docker.service 中指定的配置路径保持一致',

View file

@ -13,10 +13,21 @@ const containerRouter = {
{
path: '/containers',
name: 'Containers',
redirect: '/containers/container',
redirect: '/containers/dashboard',
component: () => import('@/views/container/index.vue'),
meta: {},
children: [
{
path: 'dashboard',
name: 'ContainerDashboard',
component: () => import('@/views/container/dashboard/index.vue'),
props: true,
hidden: true,
meta: {
activeMenu: '/containers',
requiresAuth: false,
},
},
{
path: 'container',
name: 'Container',

View file

@ -0,0 +1,190 @@
<template>
<div v-loading="loading">
<CardWithHeader :header="$t('container.container')" class="mt-5">
<template #body>
<span class="count" @click="goRouter('Container')">{{ countItem.containerCount }}</span>
<div class="float-right">
<el-tag v-if="countItem.all" effect="plain">
{{ $t('commons.table.all') }} * {{ countItem.all }}
</el-tag>
<el-tag v-if="countItem.running" effect="plain" class="ml-2">
{{ $t('commons.status.running') }} * {{ countItem.running }}
</el-tag>
<el-tag v-if="countItem.created" effect="plain" class="ml-2">
{{ $t('commons.status.created') }} * {{ countItem.created }}
</el-tag>
<el-tag v-if="countItem.paused" effect="plain" class="ml-2">
{{ $t('commons.status.paused') }} * {{ countItem.paused }}
</el-tag>
<el-tag v-if="countItem.restarting" effect="plain" class="ml-2">
{{ $t('commons.status.restarting') }} * {{ countItem.restarting }}
</el-tag>
<el-tag v-if="countItem.removing" effect="plain" class="ml-2">
{{ $t('commons.status.removing') }} * {{ countItem.removing }}
</el-tag>
<el-tag v-if="countItem.exited" effect="plain" class="ml-2">
{{ $t('commons.status.exited') }} * {{ countItem.exited }}
</el-tag>
<el-tag v-if="countItem.dead" effect="plain" class="ml-2">
{{ $t('commons.status.dead') }} * {{ countItem.dead }}
</el-tag>
</div>
</template>
</CardWithHeader>
<el-row :gutter="20" class="mt-5">
<el-col :span="8">
<CardWithHeader :header="$t('container.compose')">
<template #body>
<span class="count" @click="goRouter('Compose')">{{ countItem.composeCount }}</span>
</template>
</CardWithHeader>
</el-col>
<el-col :span="8">
<CardWithHeader :header="$t('container.composeTemplate')">
<template #body>
<span class="count" @click="goRouter('ComposeTemplate')">
{{ countItem.composeTemplateCount }}
</span>
</template>
</CardWithHeader>
</el-col>
<el-col :span="8">
<CardWithHeader :header="$t('container.image')">
<template #body>
<span class="count" @click="goRouter('Image')">{{ countItem.imageCount }}</span>
<div class="float-right">
<el-tag v-if="countItem.imageSize" effect="plain" class="ml-2">
{{ $t('commons.status.used') }}: {{ computeSize(countItem.imageSize) }}
</el-tag>
</div>
</template>
</CardWithHeader>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-5">
<el-col :span="8">
<CardWithHeader :header="$t('container.imageRepo')">
<template #body>
<span class="count" @click="goRouter('Repo')">{{ countItem.repoCount }}</span>
</template>
</CardWithHeader>
</el-col>
<el-col :span="8">
<CardWithHeader :header="$t('container.network')">
<template #body>
<span class="count" @click="goRouter('Network')">{{ countItem.networkCount }}</span>
</template>
</CardWithHeader>
</el-col>
<el-col :span="8">
<CardWithHeader :header="$t('container.volume')">
<template #body>
<span class="count" @click="goRouter('Volume')">{{ countItem.volumeCount }}</span>
</template>
</CardWithHeader>
</el-col>
</el-row>
<CardWithHeader :header="$t('container.setting')" class="mt-5">
<template #body>
<el-descriptions :column="1" border>
<el-descriptions-item :label="$t('container.sockPath')">
{{ countItem.sockPath }}
</el-descriptions-item>
<el-descriptions-item :label="$t('container.mirrors')">
<div v-for="item in countItem.mirrors" :key="item" :value="item" :label="item">
<div class="mt-1">
<el-tag>{{ item }}</el-tag>
</div>
</div>
</el-descriptions-item>
</el-descriptions>
<el-button class="mt-2" type="primary" link @click="goRouter('ContainerSetting')">
<el-icon class="mr-1"><Position /></el-icon>
{{ $t('container.goSetting') }}
</el-button>
</template>
</CardWithHeader>
</div>
</template>
<script lang="ts" setup>
import { loadContainerStatus, loadDaemonJson } from '@/api/modules/container';
import { getSettingInfo } from '@/api/modules/setting';
import router from '@/routers';
import { computeSize } from '@/utils/util';
import { onMounted, reactive, ref } from 'vue';
const loading = ref();
const countItem = reactive({
all: 0,
created: 0,
running: 0,
paused: 0,
restarting: 0,
removing: 0,
exited: 0,
dead: 0,
containerCount: 0,
composeCount: 0,
composeTemplateCount: 0,
imageCount: 0,
networkCount: 0,
volumeCount: 0,
repoCount: 0,
imageSize: 0,
mirrors: [],
sockPath: '',
});
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;
countItem.containerCount = res.data.containerCount;
countItem.composeCount = res.data.composeCount;
countItem.composeTemplateCount = res.data.composeTemplateCount;
countItem.imageCount = res.data.imageCount;
countItem.networkCount = res.data.networkCount;
countItem.volumeCount = res.data.volumeCount;
countItem.repoCount = res.data.repoCount;
countItem.imageSize = res.data.imageSize;
});
};
const loadContainerSetting = async () => {
const res = await loadDaemonJson();
countItem.mirrors = res.data.registryMirrors || [];
const settingRes = await getSettingInfo();
countItem.sockPath = settingRes.data.dockerSockPath || 'unix:///var/run/docker-x.sock';
};
const goRouter = async (val: string) => {
router.push({ name: val });
};
onMounted(() => {
loadContainerCount();
loadContainerSetting();
});
</script>
<style scoped lang="scss">
.count {
margin-left: 20px;
font-size: 25px;
color: $primary-color;
font-weight: 500;
line-height: 32px;
cursor: pointer;
}
</style>

View file

@ -11,6 +11,10 @@
import i18n from '@/lang';
const buttons = [
{
label: i18n.global.t('home.overview'),
path: '/containers/dashboard',
},
{
label: i18n.global.t('container.container'),
path: '/containers/container',