mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-14 01:34:47 +08:00
feat: 完成容器监控功能
This commit is contained in:
parent
53845e60b6
commit
e50c4c39c1
@ -1,6 +1,8 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
@ -64,6 +66,21 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ContainerStats(c *gin.Context) {
|
||||||
|
containerID, ok := c.Params.Get("id")
|
||||||
|
if !ok {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error container id in path"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := containerService.ContainerStats(containerID)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseApi) Inspect(c *gin.Context) {
|
func (b *BaseApi) Inspect(c *gin.Context) {
|
||||||
var req dto.InspectReq
|
var req dto.InspectReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
@ -37,6 +37,18 @@ type ContainerCreate struct {
|
|||||||
RestartPolicy string `json:"restartPolicy"`
|
RestartPolicy string `json:"restartPolicy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainterStats struct {
|
||||||
|
CPUPercent float64 `json:"cpuPercent"`
|
||||||
|
Memory float64 `json:"memory"`
|
||||||
|
Cache float64 `json:"cache"`
|
||||||
|
IORead float64 `json:"ioRead"`
|
||||||
|
IOWrite float64 `json:"ioWrite"`
|
||||||
|
NetworkRX float64 `json:"networkRX"`
|
||||||
|
NetworkTX float64 `json:"networkTX"`
|
||||||
|
|
||||||
|
ShotTime time.Time `json:"shotTime"`
|
||||||
|
}
|
||||||
|
|
||||||
type VolumeHelper struct {
|
type VolumeHelper struct {
|
||||||
SourceDir string `json:"sourceDir"`
|
SourceDir string `json:"sourceDir"`
|
||||||
ContainerDir string `json:"containerDir"`
|
ContainerDir string `json:"containerDir"`
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -33,6 +34,7 @@ type IContainerService interface {
|
|||||||
ContainerCreate(req dto.ContainerCreate) error
|
ContainerCreate(req dto.ContainerCreate) error
|
||||||
ContainerOperation(req dto.ContainerOperation) error
|
ContainerOperation(req dto.ContainerOperation) error
|
||||||
ContainerLogs(param dto.ContainerLog) (string, error)
|
ContainerLogs(param dto.ContainerLog) (string, error)
|
||||||
|
ContainerStats(id string) (*dto.ContainterStats, error)
|
||||||
Inspect(req dto.InspectReq) (string, error)
|
Inspect(req dto.InspectReq) (string, error)
|
||||||
DeleteNetwork(req dto.BatchDelete) error
|
DeleteNetwork(req dto.BatchDelete) error
|
||||||
CreateNetwork(req dto.NetworkCreat) error
|
CreateNetwork(req dto.NetworkCreat) error
|
||||||
@ -159,27 +161,27 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
|||||||
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
dc, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch req.Operation {
|
switch req.Operation {
|
||||||
case constant.ContainerOpStart:
|
case constant.ContainerOpStart:
|
||||||
err = dc.ContainerStart(ctx, req.ContainerID, types.ContainerStartOptions{})
|
err = client.ContainerStart(ctx, req.ContainerID, types.ContainerStartOptions{})
|
||||||
case constant.ContainerOpStop:
|
case constant.ContainerOpStop:
|
||||||
err = dc.ContainerStop(ctx, req.ContainerID, nil)
|
err = client.ContainerStop(ctx, req.ContainerID, nil)
|
||||||
case constant.ContainerOpRestart:
|
case constant.ContainerOpRestart:
|
||||||
err = dc.ContainerRestart(ctx, req.ContainerID, nil)
|
err = client.ContainerRestart(ctx, req.ContainerID, nil)
|
||||||
case constant.ContainerOpKill:
|
case constant.ContainerOpKill:
|
||||||
err = dc.ContainerKill(ctx, req.ContainerID, "SIGKILL")
|
err = client.ContainerKill(ctx, req.ContainerID, "SIGKILL")
|
||||||
case constant.ContainerOpPause:
|
case constant.ContainerOpPause:
|
||||||
err = dc.ContainerPause(ctx, req.ContainerID)
|
err = client.ContainerPause(ctx, req.ContainerID)
|
||||||
case constant.ContainerOpUnpause:
|
case constant.ContainerOpUnpause:
|
||||||
err = dc.ContainerUnpause(ctx, req.ContainerID)
|
err = client.ContainerUnpause(ctx, req.ContainerID)
|
||||||
case constant.ContainerOpRename:
|
case constant.ContainerOpRename:
|
||||||
err = dc.ContainerRename(ctx, req.ContainerID, req.NewName)
|
err = client.ContainerRename(ctx, req.ContainerID, req.NewName)
|
||||||
case constant.ContainerOpRemove:
|
case constant.ContainerOpRemove:
|
||||||
err = dc.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: true, Force: true})
|
err = client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: true, Force: true})
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -213,6 +215,40 @@ func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
|
|||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, error) {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := client.ContainerStats(context.TODO(), id, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var stats *types.StatsJSON
|
||||||
|
if err := json.Unmarshal(body, &stats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data dto.ContainterStats
|
||||||
|
previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage
|
||||||
|
previousSystem := stats.PreCPUStats.SystemUsage
|
||||||
|
data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, stats)
|
||||||
|
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
|
||||||
|
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
|
||||||
|
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
|
||||||
|
data.Cache = float64(cache) / 1024 / 1024
|
||||||
|
}
|
||||||
|
data.Memory = data.Memory - data.Cache
|
||||||
|
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
|
||||||
|
data.ShotTime = stats.Read
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) {
|
func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -404,3 +440,35 @@ func stringsToMap(list []string) map[string]string {
|
|||||||
}
|
}
|
||||||
return lableMap
|
return lableMap
|
||||||
}
|
}
|
||||||
|
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||||
|
var (
|
||||||
|
cpuPercent = 0.0
|
||||||
|
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
|
||||||
|
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
|
||||||
|
)
|
||||||
|
|
||||||
|
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||||
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||||
|
}
|
||||||
|
return cpuPercent
|
||||||
|
}
|
||||||
|
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
|
||||||
|
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||||
|
switch strings.ToLower(bioEntry.Op) {
|
||||||
|
case "read":
|
||||||
|
blkRead = (blkRead + float64(bioEntry.Value)) / 1024 / 1024
|
||||||
|
case "write":
|
||||||
|
blkWrite = (blkWrite + float64(bioEntry.Value)) / 1024 / 1024
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
||||||
|
var rx, tx float64
|
||||||
|
|
||||||
|
for _, v := range network {
|
||||||
|
rx += float64(v.RxBytes) / 1024
|
||||||
|
tx += float64(v.TxBytes) / 1024
|
||||||
|
}
|
||||||
|
return rx, tx
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
"github.com/1Panel-dev/1Panel/utils/docker"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@ -95,10 +96,31 @@ func TestNetwork(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
_, err = client.NetworkCreate(context.TODO(), "test", types.NetworkCreate{})
|
res, err := client.ContainerStatsOneShot(context.TODO(), "30e4d3395b87")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
var state *types.StatsJSON
|
||||||
|
if err := json.Unmarshal(body, &state); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(body))
|
||||||
|
|
||||||
|
var data dto.ContainterStats
|
||||||
|
previousCPU := state.PreCPUStats.CPUUsage.TotalUsage
|
||||||
|
previousSystem := state.PreCPUStats.SystemUsage
|
||||||
|
data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, state)
|
||||||
|
data.IORead, data.IOWrite = calculateBlockIO(state.BlkioStats)
|
||||||
|
data.Memory = float64(state.MemoryStats.Usage)
|
||||||
|
data.NetworkRX, data.NetworkTX = calculateNetwork(state.Networks)
|
||||||
|
fmt.Println(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
|
@ -25,6 +25,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.POST("", baseApi.ContainerCreate)
|
baRouter.POST("", baseApi.ContainerCreate)
|
||||||
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
||||||
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
||||||
|
withRecordRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||||
|
|
||||||
baRouter.POST("/repo/search", baseApi.SearchRepo)
|
baRouter.POST("/repo/search", baseApi.SearchRepo)
|
||||||
baRouter.PUT("/repo/:id", baseApi.UpdateRepo)
|
baRouter.PUT("/repo/:id", baseApi.UpdateRepo)
|
||||||
|
@ -37,6 +37,16 @@ export namespace Container {
|
|||||||
state: string;
|
state: string;
|
||||||
runTime: string;
|
runTime: string;
|
||||||
}
|
}
|
||||||
|
export interface ContainerStats {
|
||||||
|
cpuPercent: number;
|
||||||
|
memory: number;
|
||||||
|
cache: number;
|
||||||
|
ioRead: number;
|
||||||
|
ioWrite: number;
|
||||||
|
networkRX: number;
|
||||||
|
networkTX: number;
|
||||||
|
shotTime: Date;
|
||||||
|
}
|
||||||
export interface ContainerLogSearch {
|
export interface ContainerLogSearch {
|
||||||
containerID: string;
|
containerID: string;
|
||||||
mode: string;
|
mode: string;
|
||||||
|
@ -12,6 +12,9 @@ export const createContainer = (params: Container.ContainerCreate) => {
|
|||||||
export const getContainerLog = (params: Container.ContainerLogSearch) => {
|
export const getContainerLog = (params: Container.ContainerLogSearch) => {
|
||||||
return http.post<string>(`/containers/log`, params);
|
return http.post<string>(`/containers/log`, params);
|
||||||
};
|
};
|
||||||
|
export const ContainerStats = (id: string) => {
|
||||||
|
return http.get<Container.ContainerStats>(`/containers/stats/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
export const ContainerOperator = (params: Container.ContainerOperate) => {
|
export const ContainerOperator = (params: Container.ContainerOperate) => {
|
||||||
return http.post(`/containers/operate`, params);
|
return http.post(`/containers/operate`, params);
|
||||||
|
@ -226,6 +226,10 @@ export default {
|
|||||||
scope: 'IP Scope',
|
scope: 'IP Scope',
|
||||||
gateway: 'Gateway',
|
gateway: 'Gateway',
|
||||||
|
|
||||||
|
monitor: 'Monitor',
|
||||||
|
refreshTime: 'Refresh time',
|
||||||
|
cache: 'Cache',
|
||||||
|
|
||||||
volume: 'Volume',
|
volume: 'Volume',
|
||||||
volumeName: 'Name',
|
volumeName: 'Name',
|
||||||
mountpoint: 'Mountpoint',
|
mountpoint: 'Mountpoint',
|
||||||
|
@ -190,6 +190,10 @@ export default {
|
|||||||
onFailure: '失败后重启(默认重启 5 次)',
|
onFailure: '失败后重启(默认重启 5 次)',
|
||||||
no: '不重启',
|
no: '不重启',
|
||||||
|
|
||||||
|
monitor: '监控',
|
||||||
|
refreshTime: '刷新间隔',
|
||||||
|
cache: '缓存',
|
||||||
|
|
||||||
image: '镜像',
|
image: '镜像',
|
||||||
imagePull: '拉取镜像',
|
imagePull: '拉取镜像',
|
||||||
imagePush: '推送镜像',
|
imagePush: '推送镜像',
|
||||||
|
@ -49,6 +49,7 @@ export function dateFromat(row: number, col: number, dataStr: any) {
|
|||||||
return `${String(y)}-${String(m)}-${String(d)} ${String(h)}:${String(minute)}:${String(second)}`;
|
return `${String(y)}-${String(m)}-${String(d)} ${String(h)}:${String(minute)}:${String(second)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 20221013151302
|
||||||
export function dateFromatForName(dataStr: any) {
|
export function dateFromatForName(dataStr: any) {
|
||||||
const date = new Date(dataStr);
|
const date = new Date(dataStr);
|
||||||
const y = date.getFullYear();
|
const y = date.getFullYear();
|
||||||
@ -65,6 +66,7 @@ export function dateFromatForName(dataStr: any) {
|
|||||||
return `${String(y)}${String(m)}${String(d)}${String(h)}${String(minute)}${String(second)}`;
|
return `${String(y)}${String(m)}${String(d)}${String(h)}${String(minute)}${String(second)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10-13 \n 15:13
|
||||||
export function dateFromatWithoutYear(dataStr: any) {
|
export function dateFromatWithoutYear(dataStr: any) {
|
||||||
const date = new Date(dataStr);
|
const date = new Date(dataStr);
|
||||||
let m: string | number = date.getMonth() + 1;
|
let m: string | number = date.getMonth() + 1;
|
||||||
@ -78,6 +80,18 @@ export function dateFromatWithoutYear(dataStr: any) {
|
|||||||
return `${String(m)}-${String(d)}\n${String(h)}:${String(minute)}`;
|
return `${String(m)}-${String(d)}\n${String(h)}:${String(minute)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 20221013151302
|
||||||
|
export function dateFromatForSecond(dataStr: any) {
|
||||||
|
const date = new Date(dataStr);
|
||||||
|
let h: string | number = date.getHours();
|
||||||
|
h = h < 10 ? `0${String(h)}` : h;
|
||||||
|
let minute: string | number = date.getMinutes();
|
||||||
|
minute = minute < 10 ? `0${String(minute)}` : minute;
|
||||||
|
let second: string | number = date.getSeconds();
|
||||||
|
second = second < 10 ? `0${String(second)}` : second;
|
||||||
|
return `${String(h)}:${String(minute)}:${String(second)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function getRandomStr(e: number): string {
|
export function getRandomStr(e: number): string {
|
||||||
const t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
|
const t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
|
||||||
const a = t.length;
|
const a = t.length;
|
||||||
|
@ -157,12 +157,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<CreateDialog @search="search" ref="dialogCreateRef" />
|
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||||
|
<MonitorDialog ref="dialogMonitorRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ComplexTable from '@/components/complex-table/index.vue';
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
import CreateDialog from '@/views/container/container/create/index.vue';
|
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||||
|
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||||
import { Codemirror } from 'vue-codemirror';
|
import { Codemirror } from 'vue-codemirror';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
@ -239,15 +241,16 @@ const search = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogCreateRef = ref<DialogExpose>();
|
const dialogCreateRef = ref();
|
||||||
|
const onCreate = () => {
|
||||||
interface DialogExpose {
|
|
||||||
acceptParams: () => void;
|
|
||||||
}
|
|
||||||
const onCreate = async () => {
|
|
||||||
dialogCreateRef.value!.acceptParams();
|
dialogCreateRef.value!.acceptParams();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dialogMonitorRef = ref();
|
||||||
|
const onMonitor = (containerID: string) => {
|
||||||
|
dialogMonitorRef.value!.acceptParams({ containerID: containerID });
|
||||||
|
};
|
||||||
|
|
||||||
const onInspect = async (id: string) => {
|
const onInspect = async (id: string) => {
|
||||||
const res = await inspect({ id: id, type: 'container' });
|
const res = await inspect({ id: id, type: 'container' });
|
||||||
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
||||||
@ -364,6 +367,12 @@ const onOperate = async (operation: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.monitor'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onMonitor(row.containerID);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('container.reName'),
|
label: i18n.global.t('container.reName'),
|
||||||
click: (row: Container.ContainerInfo) => {
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
288
frontend/src/views/container/container/monitor/index.vue
Normal file
288
frontend/src/views/container/container/monitor/index.vue
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="monitorVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="onClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="70%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('container.monitor') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span>{{ $t('container.refreshTime') }}</span>
|
||||||
|
<el-select style="margin-left: 10px" v-model="timeInterval" @change="changeTimer">
|
||||||
|
<el-option label="1s" :value="1" />
|
||||||
|
<el-option label="3s" :value="3" />
|
||||||
|
<el-option label="5s" :value="5" />
|
||||||
|
<el-option label="10s" :value="10" />
|
||||||
|
<el-option label="30s" :value="30" />
|
||||||
|
<el-option label="60s" :value="60" />
|
||||||
|
</el-select>
|
||||||
|
<el-row :gutter="20" style="margin-top: 10px">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card style="overflow: inherit">
|
||||||
|
<div id="cpuChart" style="width: 100%; height: 230px"></div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card style="overflow: inherit">
|
||||||
|
<div id="memoryChart" style="width: 100%; height: 230px"></div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20" style="margin-top: 10px">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card style="overflow: inherit">
|
||||||
|
<div id="ioChart" style="width: 100%; height: 230px"></div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card style="overflow: inherit">
|
||||||
|
<div id="networkChart" style="width: 100%; height: 230px"></div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ContainerStats } from '@/api/modules/container';
|
||||||
|
import { dateFromatForSecond } from '@/utils/util';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
|
||||||
|
const monitorVisiable = ref(false);
|
||||||
|
const timeInterval = ref();
|
||||||
|
let timer: NodeJS.Timer | null = null;
|
||||||
|
let isInit = ref<boolean>(true);
|
||||||
|
interface DialogProps {
|
||||||
|
containerID: string;
|
||||||
|
}
|
||||||
|
const dialogData = ref<DialogProps>({
|
||||||
|
containerID: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
function changeChartSize() {
|
||||||
|
echarts.getInstanceByDom(document.getElementById('cpuChart') as HTMLElement)?.resize();
|
||||||
|
echarts.getInstanceByDom(document.getElementById('memoryChart') as HTMLElement)?.resize();
|
||||||
|
echarts.getInstanceByDom(document.getElementById('ioChart') as HTMLElement)?.resize();
|
||||||
|
echarts.getInstanceByDom(document.getElementById('networkChart') as HTMLElement)?.resize();
|
||||||
|
}
|
||||||
|
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||||
|
monitorVisiable.value = true;
|
||||||
|
dialogData.value.containerID = params.containerID;
|
||||||
|
cpuDatas.value = [];
|
||||||
|
memDatas.value = [];
|
||||||
|
cacheDatas.value = [];
|
||||||
|
ioReadDatas.value = [];
|
||||||
|
ioWriteDatas.value = [];
|
||||||
|
netTxDatas.value = [];
|
||||||
|
netRxDatas.value = [];
|
||||||
|
timeDatas.value = [];
|
||||||
|
timeInterval.value = 5;
|
||||||
|
isInit.value = true;
|
||||||
|
loadData();
|
||||||
|
window.addEventListener('resize', changeChartSize);
|
||||||
|
timer = setInterval(async () => {
|
||||||
|
if (monitorVisiable.value) {
|
||||||
|
isInit.value = false;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}, 1000 * timeInterval.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cpuDatas = ref<Array<string>>([]);
|
||||||
|
const memDatas = ref<Array<string>>([]);
|
||||||
|
const cacheDatas = ref<Array<string>>([]);
|
||||||
|
const ioReadDatas = ref<Array<string>>([]);
|
||||||
|
const ioWriteDatas = ref<Array<string>>([]);
|
||||||
|
const netTxDatas = ref<Array<string>>([]);
|
||||||
|
const netRxDatas = ref<Array<string>>([]);
|
||||||
|
const timeDatas = ref<Array<string>>([]);
|
||||||
|
|
||||||
|
const changeTimer = () => {
|
||||||
|
clearInterval(Number(timer));
|
||||||
|
timer = setInterval(async () => {
|
||||||
|
if (monitorVisiable.value) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}, 1000 * timeInterval.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
const res = await ContainerStats(dialogData.value.containerID);
|
||||||
|
cpuDatas.value.push(res.data.cpuPercent.toFixed(2));
|
||||||
|
if (cpuDatas.value.length > 20) {
|
||||||
|
cpuDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
memDatas.value.push(res.data.memory.toFixed(2));
|
||||||
|
if (memDatas.value.length > 20) {
|
||||||
|
memDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
cacheDatas.value.push(res.data.cache.toFixed(2));
|
||||||
|
if (cacheDatas.value.length > 20) {
|
||||||
|
cacheDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
ioReadDatas.value.push(res.data.ioRead.toFixed(2));
|
||||||
|
if (ioReadDatas.value.length > 20) {
|
||||||
|
ioReadDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
ioWriteDatas.value.push(res.data.ioWrite.toFixed(2));
|
||||||
|
if (ioWriteDatas.value.length > 20) {
|
||||||
|
ioWriteDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
netTxDatas.value.push(res.data.networkTX.toFixed(2));
|
||||||
|
if (netTxDatas.value.length > 20) {
|
||||||
|
netTxDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
netRxDatas.value.push(res.data.networkRX.toFixed(2));
|
||||||
|
if (netRxDatas.value.length > 20) {
|
||||||
|
netRxDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
timeDatas.value.push(dateFromatForSecond(res.data.shotTime));
|
||||||
|
if (timeDatas.value.length > 20) {
|
||||||
|
timeDatas.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cpuYDatas = {
|
||||||
|
name: 'CPU',
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: cpuDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
freshChart('cpuChart', ['CPU'], timeDatas.value, [cpuYDatas], 'CPU', '%');
|
||||||
|
|
||||||
|
let memoryYDatas = {
|
||||||
|
name: i18n.global.t('monitor.memory'),
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: memDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
let cacheYDatas = {
|
||||||
|
name: i18n.global.t('container.cache'),
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: cacheDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
freshChart(
|
||||||
|
'memoryChart',
|
||||||
|
[i18n.global.t('monitor.memory'), i18n.global.t('monitor.cache')],
|
||||||
|
timeDatas.value,
|
||||||
|
[memoryYDatas, cacheYDatas],
|
||||||
|
i18n.global.t('monitor.memory'),
|
||||||
|
' MB',
|
||||||
|
);
|
||||||
|
|
||||||
|
let ioReadYDatas = {
|
||||||
|
name: i18n.global.t('monitor.read'),
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: ioReadDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
let ioWriteYDatas = {
|
||||||
|
name: i18n.global.t('monitor.write'),
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: ioWriteDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
freshChart(
|
||||||
|
'ioChart',
|
||||||
|
[i18n.global.t('monitor.read'), i18n.global.t('monitor.write')],
|
||||||
|
timeDatas.value,
|
||||||
|
[ioReadYDatas, ioWriteYDatas],
|
||||||
|
i18n.global.t('monitor.disk') + ' IO',
|
||||||
|
'MB',
|
||||||
|
);
|
||||||
|
|
||||||
|
let netTxYDatas = {
|
||||||
|
name: i18n.global.t('monitor.up'),
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: netTxDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
let netRxYDatas = {
|
||||||
|
name: i18n.global.t('monitor.down'),
|
||||||
|
type: 'line',
|
||||||
|
areaStyle: {
|
||||||
|
color: '#ebdee3',
|
||||||
|
},
|
||||||
|
data: netRxDatas.value,
|
||||||
|
showSymbol: false,
|
||||||
|
};
|
||||||
|
freshChart(
|
||||||
|
'networkChart',
|
||||||
|
[i18n.global.t('monitor.up'), i18n.global.t('monitor.down')],
|
||||||
|
timeDatas.value,
|
||||||
|
[netTxYDatas, netRxYDatas],
|
||||||
|
i18n.global.t('monitor.network'),
|
||||||
|
'KB/s',
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function freshChart(chartName: string, legendDatas: any, xDatas: any, yDatas: any, yTitle: string, formatStr: string) {
|
||||||
|
if (isInit.value) {
|
||||||
|
echarts.init(document.getElementById(chartName) as HTMLElement);
|
||||||
|
}
|
||||||
|
let itemChart = echarts.getInstanceByDom(document.getElementById(chartName) as HTMLElement);
|
||||||
|
const option = {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
left: 'center',
|
||||||
|
text: yTitle,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
zlevel: 1,
|
||||||
|
z: 1,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: function (datas: any) {
|
||||||
|
let res = datas[0].name + '<br/>';
|
||||||
|
for (const item of datas) {
|
||||||
|
res += item.marker + ' ' + item.seriesName + ':' + item.data + formatStr + '<br/>';
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: { left: '7%', right: '7%', bottom: '20%' },
|
||||||
|
legend: {
|
||||||
|
data: legendDatas,
|
||||||
|
right: 10,
|
||||||
|
},
|
||||||
|
xAxis: { data: xDatas, boundaryGap: false },
|
||||||
|
yAxis: { name: '( ' + formatStr + ' )' },
|
||||||
|
series: yDatas,
|
||||||
|
};
|
||||||
|
itemChart?.setOption(option, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = async () => {
|
||||||
|
clearInterval(Number(timer));
|
||||||
|
timer = null;
|
||||||
|
window.removeEventListener('resize', changeChartSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -131,6 +131,7 @@ const loadLogs = async (path: string) => {
|
|||||||
const onCloseLog = async () => {
|
const onCloseLog = async () => {
|
||||||
emit('search');
|
emit('search');
|
||||||
clearInterval(Number(timer));
|
clearInterval(Number(timer));
|
||||||
|
timer = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadBuildDir = async (path: string) => {
|
const loadBuildDir = async (path: string) => {
|
||||||
|
@ -406,6 +406,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
clearInterval(Number(timer));
|
clearInterval(Number(timer));
|
||||||
|
timer = null;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user