@ -0,0 +1,26 @@
|
||||
package dto |
||||
|
||||
type DashboardBase struct { |
||||
HaloEnabled bool `json:"haloEnabled"` |
||||
DateeaseEnabled bool `json:"dateeaseEnabled"` |
||||
JumpServerEnabled bool `json:"jumpserverEnabled"` |
||||
MeterSphereEnabled bool `json:"metersphereEnabled"` |
||||
|
||||
WebsiteNumber int `json:"websiteNumber"` |
||||
DatabaseNumber int `json:"databaseNumber"` |
||||
CronjobNumber int `json:"cronjobNumber"` |
||||
AppInstalldNumber int `json:"appInstalldNumber"` |
||||
|
||||
HostName string `json:"hostname"` |
||||
Os string `json:"os"` |
||||
Platform string `json:"platform"` |
||||
PlatformFamily string `json:"platformFamily"` |
||||
PlatformVersion string `json:"platformVersion"` |
||||
KernelArch string `json:"kernelArch"` |
||||
VirtualizationSystem string `json:"virtualizationSystem"` |
||||
|
||||
CPUCores int `json:"cpuCores"` |
||||
CPULogicalCores int `json:"cpuLogicalCores"` |
||||
CPUModelName string `json:"cpuModelName"` |
||||
CPUPercent float64 `json:"cpuPercent"` |
||||
} |
@ -0,0 +1,71 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto" |
||||
"github.com/jinzhu/copier" |
||||
"github.com/shirou/gopsutil/cpu" |
||||
"github.com/shirou/gopsutil/host" |
||||
) |
||||
|
||||
type DashboardService struct{} |
||||
|
||||
type IDashboardService interface { |
||||
LoadBaseInfo() (*dto.DashboardBase, error) |
||||
} |
||||
|
||||
func NewIDashboardService() IDashboardService { |
||||
return &DashboardService{} |
||||
} |
||||
func (u *DashboardService) LoadBaseInfo() (*dto.DashboardBase, error) { |
||||
var baseInfo dto.DashboardBase |
||||
hostInfo, err := host.Info() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := copier.Copy(baseInfo, hostInfo); err != nil { |
||||
return nil, err |
||||
} |
||||
appInstall, err := appInstallRepo.GetBy() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
for _, app := range appInstall { |
||||
switch app.App.Key { |
||||
case "dateease": |
||||
baseInfo.DateeaseEnabled = true |
||||
case "halo": |
||||
baseInfo.HaloEnabled = true |
||||
case "metersphere": |
||||
baseInfo.MeterSphereEnabled = true |
||||
case "jumpserver": |
||||
baseInfo.JumpServerEnabled = true |
||||
} |
||||
} |
||||
baseInfo.AppInstalldNumber = len(appInstall) |
||||
dbs, err := mysqlRepo.List() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
baseInfo.DatabaseNumber = len(dbs) |
||||
cornjobs, err := cronjobRepo.List() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
baseInfo.DatabaseNumber = len(cornjobs) |
||||
|
||||
cpuInfo, err := cpu.Info() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
baseInfo.CPUModelName = cpuInfo[0].ModelName |
||||
baseInfo.CPUCores, _ = cpu.Counts(false) |
||||
baseInfo.CPULogicalCores, _ = cpu.Counts(true) |
||||
totalPercent, _ := cpu.Percent(1*time.Second, false) |
||||
if len(totalPercent) == 1 { |
||||
baseInfo.CPUPercent = totalPercent[0] |
||||
} |
||||
|
||||
return &baseInfo, nil |
||||
} |
@ -1,13 +0,0 @@
|
||||
package configs |
||||
|
||||
type GeneralDB struct { |
||||
Path string `mapstructure:"path"` // 服务器地址
|
||||
Port string `mapstructure:"port"` //:端口
|
||||
Config string `mapstructure:"config"` // 高级配置
|
||||
Dbname string `mapstructure:"db_name"` // 数据库名
|
||||
Username string `mapstructure:"username"` // 数据库用户名
|
||||
Password string `mapstructure:"password"` // 数据库密码
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"` // 空闲中的最大连接数
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"` // 打开到数据库的最大连接数
|
||||
DbFile string `mapstructure:"db_file"` |
||||
} |
@ -1,11 +0,0 @@
|
||||
package configs |
||||
|
||||
import "fmt" |
||||
|
||||
type Mysql struct { |
||||
GeneralDB `yaml:",inline" mapstructure:",squash"` |
||||
} |
||||
|
||||
func (m *Mysql) Dsn() string { |
||||
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true&loc=Asia%%2FShanghai", m.Username, m.Password, m.Path, m.Port, m.Dbname) |
||||
} |
@ -1,9 +1,25 @@
|
||||
package configs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
type Sqlite struct { |
||||
GeneralDB `yaml:",inline" mapstructure:",squash"` |
||||
Path string `mapstructure:"path"` |
||||
DbFile string `mapstructure:"db_file"` |
||||
} |
||||
|
||||
func (s *Sqlite) Dsn() string { |
||||
if _, err := os.Stat(s.Path); err != nil { |
||||
if err := os.MkdirAll(s.Path, os.ModePerm); err != nil { |
||||
panic(fmt.Errorf("init db dir falied, err: %v", err)) |
||||
} |
||||
} |
||||
if _, err := os.Stat(s.Path + "/" + s.DbFile); err != nil { |
||||
if _, err := os.Create(s.Path + "/" + s.DbFile); err != nil { |
||||
panic(fmt.Errorf("init db file falied, err: %v", err)) |
||||
} |
||||
} |
||||
return s.Path + "/" + s.DbFile |
||||
} |
||||
|
@ -1,14 +1,16 @@
|
||||
package db |
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/global" |
||||
import ( |
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
"gorm.io/driver/sqlite" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func Init() { |
||||
switch global.CONF.System.DbType { |
||||
case "mysql": |
||||
global.DB = MysqlGorm() |
||||
case "sqlite": |
||||
global.DB = SqliteGorm() |
||||
default: |
||||
global.DB = MysqlGorm() |
||||
s := global.CONF.Sqlite |
||||
db, err := gorm.Open(sqlite.Open(s.Dsn()), &gorm.Config{}) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
global.DB = db |
||||
} |
||||
|
@ -1,28 +0,0 @@
|
||||
package db |
||||
|
||||
import ( |
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
|
||||
"gorm.io/driver/mysql" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func MysqlGorm() *gorm.DB { |
||||
m := global.CONF.Mysql |
||||
if m.Dbname == "" { |
||||
return nil |
||||
} |
||||
mysqlConfig := mysql.Config{ |
||||
DSN: m.Dsn(), |
||||
DefaultStringSize: 191, |
||||
SkipInitializeWithVersion: false, |
||||
} |
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{}); err != nil { |
||||
panic(err) |
||||
} else { |
||||
sqlDB, _ := db.DB() |
||||
sqlDB.SetMaxIdleConns(m.MaxIdleConns) |
||||
sqlDB.SetMaxOpenConns(m.MaxOpenConns) |
||||
return db |
||||
} |
||||
} |
@ -1,17 +0,0 @@
|
||||
package db |
||||
|
||||
import ( |
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
|
||||
"gorm.io/driver/sqlite" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func SqliteGorm() *gorm.DB { |
||||
s := global.CONF.Sqlite |
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), &gorm.Config{}); err != nil { |
||||
panic(err) |
||||
} else { |
||||
return db |
||||
} |
||||
} |
@ -1,22 +0,0 @@
|
||||
import { Layout } from '@/routers/constant'; |
||||
|
||||
const toolBoxRouter = { |
||||
sort: 9, |
||||
path: '/toolbox', |
||||
component: Layout, |
||||
redirect: '/toolbox', |
||||
meta: { |
||||
icon: 'p-toolbox', |
||||
title: 'menu.toolbox', |
||||
}, |
||||
children: [ |
||||
{ |
||||
path: '/toolbox', |
||||
name: 'ToolBox', |
||||
component: () => import('@/views/toolbox/index.vue'), |
||||
meta: {}, |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
export default toolBoxRouter; |
@ -1,153 +0,0 @@
|
||||
<template> |
||||
<!-- 数据来源 --> |
||||
<div class="echarts" id="curve"></div> |
||||
</template> |
||||
<script setup lang="ts" name="cure"> |
||||
import { ECharts, init } from 'echarts'; |
||||
const initChart = (data: any): ECharts => { |
||||
const echartsBox = document.getElementById('curve') as HTMLElement; |
||||
const echarts: ECharts = init(echartsBox); |
||||
const option = { |
||||
tooltip: { |
||||
trigger: 'axis', |
||||
backgroundColor: 'transparent', |
||||
axisPointer: { |
||||
type: 'none', |
||||
}, |
||||
padding: 0, |
||||
formatter: (p: any) => { |
||||
let dom = `<div style="width:100%; height: 70px !important; display:flex;flex-direction: column;justify-content: space-between;padding:10px;box-sizing: border-box; |
||||
color:#fff; background: #6B9DFE;border-radius: 4px;font-size:14px; "> |
||||
<div style="display: flex; align-items: center;"> <div style="width:5px;height:5px;background:#ffffff;border-radius: 50%;margin-right:5px"></div>平台 : ${p[0].name}</div> |
||||
<div style="display: flex;align-items: center;"><div style="width:5px;height:5px;background:#ffffff;border-radius: 50%;margin-right:5px"></div>数据量 : ${p[0].value}</div> |
||||
</div>`; |
||||
return dom; |
||||
}, |
||||
}, |
||||
toolbox: { |
||||
show: true, |
||||
orient: 'horizontal', |
||||
}, |
||||
grid: { |
||||
left: '5%', |
||||
right: '6%', |
||||
}, |
||||
dataZoom: [ |
||||
{ |
||||
show: false, |
||||
height: 10, |
||||
xAxisIndex: [0], |
||||
bottom: 0, |
||||
startValue: 0, //数据窗口范围的起始数值 |
||||
endValue: 9, //数据窗口范围的结束数值 |
||||
handleStyle: { |
||||
color: '#6b9dfe', |
||||
}, |
||||
textStyle: { |
||||
color: 'transparent', |
||||
}, |
||||
}, |
||||
{ |
||||
type: 'inside', |
||||
show: true, |
||||
height: 0, |
||||
zoomLock: true, //控制伸缩 |
||||
}, |
||||
], |
||||
xAxis: [ |
||||
{ |
||||
type: 'category', |
||||
data: data.map((val: any) => { |
||||
return { |
||||
value: val.spotName, |
||||
}; |
||||
}), |
||||
axisTick: { |
||||
show: false, |
||||
}, |
||||
axisLabel: { |
||||
// interval: time > 4 ? 27 : 0, |
||||
margin: 20, |
||||
interval: 0, |
||||
color: '#a1a1a1', |
||||
fontSize: 14, |
||||
formatter: function (name: string) { |
||||
undefined; |
||||
return name.length > 8 ? name.slice(0, 8) + '...' : name; |
||||
}, |
||||
}, |
||||
axisLine: { |
||||
lineStyle: { |
||||
color: '#F6F6F7', |
||||
width: 2, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
yAxis: [ |
||||
{ |
||||
min: 0, |
||||
axisLine: { |
||||
show: false, |
||||
}, |
||||
axisTick: { |
||||
show: false, |
||||
}, |
||||
splitLine: { |
||||
show: true, |
||||
lineStyle: { |
||||
type: 'dashed', |
||||
color: '#edeff5', |
||||
width: 2, |
||||
}, |
||||
}, |
||||
axisLabel: { |
||||
color: '#a1a1a1', |
||||
fontSize: 16, |
||||
fontWeight: 400, |
||||
formatter: function (value: number) { |
||||
if (value === 0) { |
||||
return value; |
||||
} else if (value >= 10000) { |
||||
return value / 10000 + 'w'; |
||||
} |
||||
return value; |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
series: [ |
||||
{ |
||||
name: 'Direct', |
||||
type: 'bar', |
||||
data: data.map((val: any) => { |
||||
return { |
||||
value: val.value, |
||||
}; |
||||
}), |
||||
barWidth: '45px', |
||||
itemStyle: { |
||||
color: '#C5D8FF', |
||||
borderRadius: [12, 12, 0, 0], |
||||
}, |
||||
emphasis: { |
||||
itemStyle: { |
||||
color: '#6B9DFE', |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}; |
||||
echarts.setOption(option); |
||||
return echarts; |
||||
}; |
||||
defineExpose({ |
||||
initChart, |
||||
}); |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.echarts { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
</style> |
@ -1,130 +0,0 @@
|
||||
<template> |
||||
<!-- Gitee / GitHub 访问量占比 --> |
||||
<div class="echarts" id="pie"></div> |
||||
</template> |
||||
<script setup lang="ts" name="pie"> |
||||
import { ECharts, init } from 'echarts'; |
||||
const initChart = (data: any): ECharts => { |
||||
const echartsBox = document.getElementById('pie') as HTMLElement; |
||||
const echarts: ECharts = init(echartsBox); |
||||
const option = { |
||||
title: { |
||||
text: 'Gitee / GitHub', |
||||
subtext: '访问占比', |
||||
left: '56%', |
||||
top: '45%', |
||||
textAlign: 'center', |
||||
textStyle: { |
||||
fontSize: 18, |
||||
color: '#767676', |
||||
}, |
||||
subtextStyle: { |
||||
fontSize: 15, |
||||
color: '#a1a1a1', |
||||
}, |
||||
}, |
||||
tooltip: { |
||||
trigger: 'item', |
||||
}, |
||||
legend: { |
||||
top: '4%', |
||||
left: '2%', |
||||
orient: 'vertical', |
||||
icon: 'circle', //图例形状 |
||||
align: 'left', |
||||
itemGap: 20, |
||||
textStyle: { |
||||
fontSize: 13, |
||||
color: '#a1a1a1', |
||||
fontWeight: 500, |
||||
}, |
||||
formatter: function (name: string) { |
||||
let dataCopy = ''; |
||||
for (let i = 0; i < data.length; i++) { |
||||
if (data[i].name == name && data[i].value >= 10000) { |
||||
dataCopy = (data[i].value / 10000).toFixed(2); |
||||
return name + ' ' + dataCopy + 'w'; |
||||
} else if (data[i].name == name) { |
||||
dataCopy = data[i].value; |
||||
return name + ' ' + dataCopy; |
||||
} |
||||
} |
||||
}, |
||||
}, |
||||
series: [ |
||||
{ |
||||
type: 'pie', |
||||
radius: ['70%', '40%'], |
||||
center: ['57%', '52%'], |
||||
silent: true, |
||||
clockwise: true, |
||||
startAngle: 150, |
||||
data: data, |
||||
labelLine: { |
||||
length: 80, |
||||
length2: 30, |
||||
lineStyle: { |
||||
width: 1, |
||||
}, |
||||
}, |
||||
label: { |
||||
position: 'outside', |
||||
show: true, |
||||
formatter: '{d}%', |
||||
fontWeight: 400, |
||||
fontSize: 19, |
||||
color: '#a1a1a1', |
||||
}, |
||||
color: [ |
||||
{ |
||||
type: 'linear', |
||||
x: 0, |
||||
y: 0, |
||||
x2: 0.5, |
||||
y2: 1, |
||||
colorStops: [ |
||||
{ |
||||
offset: 0, |
||||
color: '#feb791', // 0% 处的颜色 |
||||
}, |
||||
{ |
||||
offset: 1, |
||||
color: '#fe8b4c', // 100% 处的颜色 |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: 'linear', |
||||
x: 0, |
||||
y: 0, |
||||
x2: 1, |
||||
y2: 0.5, |
||||
colorStops: [ |
||||
{ |
||||
offset: 0, |
||||
color: '#b898fd', // 0% 处的颜色 |
||||
}, |
||||
{ |
||||
offset: 1, |
||||
color: '#8347fd', // 100% 处的颜色 |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}; |
||||
echarts.setOption(option); |
||||
return echarts; |
||||
}; |
||||
defineExpose({ |
||||
initChart, |
||||
}); |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.echarts { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
</style> |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 780 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 859 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 704 B |
@ -1,181 +0,0 @@
|
||||
.dataVisualize-box { |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
width: 100%; |
||||
min-height: 650px; |
||||
padding: 10px 15px; |
||||
.top-box { |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
flex-direction: column; |
||||
width: 100%; |
||||
padding: 20px 50px 30px; |
||||
margin-bottom: 40px; |
||||
border-radius: 35px; |
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); |
||||
.top-title { |
||||
margin-bottom: 10px; |
||||
font-family: 'PingFang SC'; |
||||
font-size: 18px; |
||||
font-weight: 600; |
||||
} |
||||
.top-content { |
||||
display: flex; |
||||
flex: 1; |
||||
justify-content: space-between; |
||||
margin-top: 10px; |
||||
.item-left { |
||||
box-sizing: border-box; |
||||
width: 22%; |
||||
padding: 40px 0 120px 30px; |
||||
overflow: hidden; |
||||
color: #ffffff; |
||||
background: url('./images/book-bg.png'); |
||||
background-size: 100% 100%; |
||||
border-radius: 20px; |
||||
.left-title { |
||||
font-family: 'PingFang SC'; |
||||
font-size: 20px; |
||||
font-weight: 500; |
||||
} |
||||
.img-box { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
width: 90px; |
||||
height: 90px; |
||||
margin: 40px 0 20px; |
||||
background: #ffffff; |
||||
background-color: #ffffff; |
||||
border-radius: 20px; |
||||
box-shadow: 0 10px 20px rgb(0 0 0 / 14%); |
||||
img { |
||||
width: 60px; |
||||
height: 65px; |
||||
} |
||||
} |
||||
.left-number { |
||||
overflow: hidden; |
||||
font-family: DIN; |
||||
font-size: 62px; |
||||
font-weight: 500; |
||||
} |
||||
} |
||||
.item-center { |
||||
display: flex; |
||||
flex: 1; |
||||
flex-wrap: wrap; |
||||
align-content: space-between; |
||||
justify-content: space-between; |
||||
min-width: 250px; |
||||
padding: 0 50px; |
||||
.traffic-box { |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
flex-direction: column; |
||||
width: 47%; |
||||
height: 48%; |
||||
padding: 25px; |
||||
border-radius: 30px; |
||||
.traffic-img { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
width: 70px; |
||||
height: 70px; |
||||
margin-bottom: 10px; |
||||
background-color: #ffffff; |
||||
border-radius: 19px; |
||||
} |
||||
} |
||||
img { |
||||
width: 33px; |
||||
height: 33px; |
||||
} |
||||
.item-value { |
||||
margin-bottom: 4px; |
||||
font-family: DIN; |
||||
font-size: 28px; |
||||
font-weight: bold; |
||||
color: #1a1a37; |
||||
} |
||||
.traffic-name { |
||||
overflow: hidden; |
||||
font-family: DIN; |
||||
font-size: 15px; |
||||
font-weight: 400; |
||||
color: #1a1a37; |
||||
white-space: nowrap; |
||||
} |
||||
.gitee-traffic { |
||||
background: url('./images/1-bg.png'); |
||||
background-color: #e8faea; |
||||
background-size: 100% 100%; |
||||
} |
||||
.gitHub-traffic { |
||||
background: url('./images/2-bg.png'); |
||||
background-color: #e7e1fb; |
||||
background-size: 100% 100%; |
||||
} |
||||
.today-traffic { |
||||
background: url('./images/3-bg.png'); |
||||
background-color: #fdf3e9; |
||||
background-size: 100% 100%; |
||||
} |
||||
.yesterday-traffic { |
||||
background: url('./images/4-bg.png'); |
||||
background-color: #f0f5fb; |
||||
background-size: 100% 100%; |
||||
} |
||||
} |
||||
.item-right { |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
flex-direction: column; |
||||
width: 40%; |
||||
height: 430px; |
||||
margin-right: 20px; |
||||
border: 1px solid #e5e7eb; |
||||
border-radius: 25px; |
||||
.echarts-title { |
||||
padding: 15px 20px; |
||||
font-family: 'PingFang SC'; |
||||
font-size: 18px; |
||||
font-weight: 600; |
||||
border-bottom: 1px solid #e5e7eb; |
||||
} |
||||
.book-echarts { |
||||
flex: 1; |
||||
width: 100%; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.bottom-box { |
||||
position: relative; |
||||
box-sizing: border-box; |
||||
width: 100%; |
||||
height: 480px; |
||||
padding: 20px 0; |
||||
border-radius: 35px; |
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); |
||||
.bottom-title { |
||||
position: absolute; |
||||
top: 16%; |
||||
left: 3%; |
||||
font-family: 'PingFang SC'; |
||||
font-size: 18px; |
||||
font-weight: 600; |
||||
} |
||||
.bottom-tabs { |
||||
padding: 0 50px; |
||||
} |
||||
.curve-echarts { |
||||
width: 100%; |
||||
height: 90%; |
||||
padding-left: 10px; |
||||
} |
||||
} |
||||
} |
@ -1,151 +1,425 @@
|
||||
<template> |
||||
<div class="dataVisualize-box"> |
||||
<div class="top-box"> |
||||
<div class="top-title">概览</div> |
||||
<el-tabs v-model="data.activeName" class="demo-tabs" @tab-click="handleClick"> |
||||
<el-tab-pane v-for="item in tab" :key="item.name" :label="item.label" :name="item.name"></el-tab-pane> |
||||
</el-tabs> |
||||
<div class="top-content"> |
||||
<div class="item-left sle"> |
||||
<span class="left-title">访问总数</span> |
||||
<div class="img-box"> |
||||
<img src="./images/book-sum.png" alt="" /> |
||||
</div> |
||||
<span class="left-number">{{ data.bookSum }}</span> |
||||
</div> |
||||
<div class="item-center"> |
||||
<div class="gitee-traffic traffic-box"> |
||||
<div class="traffic-img"> |
||||
<img src="./images/add_person.png" alt="" /> |
||||
<div> |
||||
<el-row :gutter="20" class="row-box"> |
||||
<el-col :span="8"> |
||||
<el-card class="el-card"> |
||||
<template #header> |
||||
<div class="card-header"> |
||||
<span>概 览</span> |
||||
</div> |
||||
<span class="item-value">2222</span> |
||||
<span class="traffic-name sle">Gitee 访问量</span> |
||||
</div> |
||||
<div class="gitHub-traffic traffic-box"> |
||||
<div class="traffic-img"> |
||||
<img src="./images/add_team.png" alt="" /> |
||||
</template> |
||||
<el-row :gutter="20"> |
||||
<el-col :span="12"> |
||||
<el-card style="font-size: 12px; height: 80px; border-radius: 10px"> |
||||
<svg-icon style="float: left; margin-left: 5px" iconName="p-website"></svg-icon> |
||||
<span style="float: left; margin-left: 5px; margin-top: 10px">网站</span> |
||||
<el-link |
||||
style="float: right; font-size: 24px; margin-right: 5px" |
||||
@click="goRouter('/websites')" |
||||
type="primary" |
||||
> |
||||
2 |
||||
</el-link> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-card style="font-size: 12px; height: 80px; border-radius: 10px"> |
||||
<svg-icon style="float: left; margin-left: 5px" iconName="p-database"></svg-icon> |
||||
<span style="float: left; margin-left: 5px; margin-top: 10px">数据库</span> |
||||
<el-link |
||||
style="float: right; font-size: 24px; margin-right: 5px" |
||||
@click="goRouter('/databases')" |
||||
type="primary" |
||||
> |
||||
5 |
||||
</el-link> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row :gutter="20" style="margin-top: 20px; margin-top: 30px"> |
||||
<el-col :span="12"> |
||||
<el-card style="font-size: 12px; height: 80px; border-radius: 10px"> |
||||
<svg-icon style="float: left; margin-left: 5px" iconName="p-plan"></svg-icon> |
||||
<span style="float: left; margin-left: 5px; margin-top: 10px">定时任务</span> |
||||
<el-link |
||||
style="float: right; font-size: 24px; margin-right: 5px" |
||||
@click="goRouter('/cronjobs')" |
||||
type="primary" |
||||
> |
||||
7 |
||||
</el-link> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-card style="font-size: 12px; height: 80px; border-radius: 10px"> |
||||
<svg-icon style="float: left; margin-left: 5px" iconName="p-appstore"></svg-icon> |
||||
<span style="float: left; margin-left: 5px; margin-top: 10px">已安装应用</span> |
||||
<el-link |
||||
style="float: right; font-size: 24px; margin-right: 5px" |
||||
@click="goRouter('/apps')" |
||||
type="primary" |
||||
> |
||||
3 |
||||
</el-link> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="8"> |
||||
<el-card class="el-card"> |
||||
<template #header> |
||||
<div class="card-header"> |
||||
<span>状态</span> |
||||
</div> |
||||
<span class="item-value">2222</span> |
||||
<span class="traffic-name sle">GitHub 访问量</span> |
||||
</div> |
||||
<div class="today-traffic traffic-box"> |
||||
<div class="traffic-img"> |
||||
<img src="./images/today.png" alt="" /> |
||||
</template> |
||||
<el-row :gutter="10"> |
||||
<el-col :span="12" align="center"> |
||||
<el-progress type="dashboard" :width="80" :percentage="80"> |
||||
<template #default="{ percentage }"> |
||||
<span class="percentage-value">{{ percentage }}%</span> |
||||
<span class="percentage-label">CPU</span> |
||||
</template> |
||||
</el-progress> |
||||
<br /> |
||||
<span>(0.56 / 8.00) Core</span> |
||||
</el-col> |
||||
<el-col :span="12" align="center"> |
||||
<el-progress type="dashboard" :width="80" :percentage="30"> |
||||
<template #default="{ percentage }"> |
||||
<span class="percentage-value">{{ percentage }}%</span> |
||||
<span class="percentage-label">内存</span> |
||||
</template> |
||||
</el-progress> |
||||
<br /> |
||||
<span>(851 / 7812) MB</span> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row :gutter="10" style="margin-top: 30px"> |
||||
<el-col :span="12" align="center"> |
||||
<el-progress type="dashboard" :width="80" :percentage="50"> |
||||
<template #default="{ percentage }"> |
||||
<span class="percentage-value">{{ percentage }}%</span> |
||||
<span class="percentage-label">负载</span> |
||||
</template> |
||||
</el-progress> |
||||
<br /> |
||||
<span>(0.36 / 8.00) Core</span> |
||||
</el-col> |
||||
<el-col :span="12" align="center"> |
||||
<el-progress type="dashboard" :width="80" :percentage="40"> |
||||
<template #default="{ percentage }"> |
||||
<span class="percentage-value">{{ percentage }}%</span> |
||||
<span class="percentage-label">磁盘</span> |
||||
</template> |
||||
</el-progress> |
||||
<br /> |
||||
<span>(6.23 / 46.97) GB</span> |
||||
</el-col> |
||||
</el-row> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="8"> |
||||
<el-card class="el-card"> |
||||
<template #header> |
||||
<div class="card-header"> |
||||
<span>系统信息</span> |
||||
</div> |
||||
<span class="item-value">4567</span> |
||||
<span class="traffic-name sle">今日访问量</span> |
||||
</div> |
||||
<div class="yesterday-traffic traffic-box"> |
||||
<div class="traffic-img"> |
||||
<img src="./images/book_sum.png" alt="" /> |
||||
</template> |
||||
<el-form> |
||||
<el-form-item label="主机名称">ko-deploy</el-form-item> |
||||
<el-form-item label="发行版本">centos-7.6.1810</el-form-item> |
||||
<el-form-item label="内核版本"> |
||||
Linux version 3.10.0-957.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 |
||||
20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Thu Nov 8 23:39:32 UTC 2018 |
||||
</el-form-item> |
||||
<el-form-item label="系统类型">x86_64</el-form-item> |
||||
</el-form> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row :gutter="20" style="margin-top: 20px" class="row-box"> |
||||
<el-col :span="12"> |
||||
<el-card class="el-card"> |
||||
<template #header> |
||||
<div class="card-header"> |
||||
<span>应用</span> |
||||
</div> |
||||
<span class="item-value">1234</span> |
||||
<span class="traffic-name sle">昨日访问量</span> |
||||
</div> |
||||
</div> |
||||
<div class="item-right"> |
||||
<div class="echarts-title">Gitee / GitHub 访问量占比</div> |
||||
<div class="book-echarts"> |
||||
<Pie ref="pie" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="bottom-box"> |
||||
<div class="bottom-title">数据来源</div> |
||||
<div class="bottom-tabs"> |
||||
<el-tabs v-model="data.activeName" class="demo-tabs" @tab-click="handleClick"> |
||||
<el-tab-pane |
||||
v-for="item in tab" |
||||
:key="item.name" |
||||
:label="item.label" |
||||
:name="item.name" |
||||
></el-tab-pane> |
||||
</el-tabs> |
||||
</div> |
||||
<div class="curve-echarts"> |
||||
<Curve ref="curve" /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<el-row :gutter="20"> |
||||
<el-col :span="12"> |
||||
<el-card @mouseover="hoverApp = 'halo'" @mouseleave="hoverApp = ''" style="height: 110px"> |
||||
<el-row> |
||||
<el-col :span="4"> |
||||
<img style="width: 40px; height: 40px" src="./images/halo.jpg" alt="" /> |
||||
</el-col> |
||||
<el-col :span="2"><br /></el-col> |
||||
<el-col :span="18"> |
||||
<span style="font-size: 20px; color: #214bc8">Halo</span> |
||||
<div><span class="input-help">现代化开源建站 / CMS系统</span></div> |
||||
</el-col> |
||||
</el-row> |
||||
<div v-show="hoverApp === 'halo'" style="float: right; margin-top: 10px"> |
||||
<el-button>停止</el-button> |
||||
<el-button>重启</el-button> |
||||
</div> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-card @mouseover="hoverApp = 'de'" @mouseleave="hoverApp = ''" style="height: 110px"> |
||||
<el-row> |
||||
<el-col :span="4"> |
||||
<img style="width: 40px; height: 40px" src="./images/de.jpg" alt="" /> |
||||
</el-col> |
||||
<el-col :span="2"><br /></el-col> |
||||
<el-col :span="18"> |
||||
<span style="font-size: 20px; color: #0070d6">Dataease</span> |
||||
<div><span class="input-help">开源数据可视化分析工具</span></div> |
||||
</el-col> |
||||
</el-row> |
||||
<div v-show="hoverApp === 'de'" style="float: right; margin-top: 10px"> |
||||
<el-button>启动</el-button> |
||||
</div> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row :gutter="20" style="margin-top: 20px"> |
||||
<el-col :span="12"> |
||||
<el-card @mouseover="hoverApp = 'js'" @mouseleave="hoverApp = ''" style="height: 110px"> |
||||
<el-row> |
||||
<el-col :span="4"> |
||||
<img style="width: 40px; height: 40px" src="./images/js.jpg" alt="" /> |
||||
</el-col> |
||||
<el-col :span="2"><br /></el-col> |
||||
<el-col :span="18"> |
||||
<span style="font-size: 16px; color: #008d75">JumpServer</span> |
||||
<div><span class="input-help">广受欢迎的开源堡垒机</span></div> |
||||
</el-col> |
||||
</el-row> |
||||
<div v-show="hoverApp === 'js'" style="float: right; margin-top: 10px"> |
||||
<el-button>停止</el-button> |
||||
<el-button>重启</el-button> |
||||
</div> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-card @mouseover="hoverApp = 'ms'" @mouseleave="hoverApp = ''" style="height: 110px"> |
||||
<el-row> |
||||
<el-col :span="4"> |
||||
<img style="width: 40px; height: 40px" src="./images/ms.jpg" alt="" /> |
||||
</el-col> |
||||
<el-col :span="2"><br /></el-col> |
||||
<el-col :span="18"> |
||||
<span style="font-size: 16px; color: #723279">MeterSphere</span> |
||||
<div><span class="input-help">一站式开源持续测试平台</span></div> |
||||
</el-col> |
||||
</el-row> |
||||
<div v-show="hoverApp === 'ms'" style="float: right; margin-top: 10px"> |
||||
<el-button>启动</el-button> |
||||
</div> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
</el-card> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-card class="el-card"> |
||||
<el-radio-group v-model="chartOption" @change="changeOption"> |
||||
<el-radio-button label="io">流量</el-radio-button> |
||||
<el-radio-button label="network">网络</el-radio-button> |
||||
</el-radio-group> |
||||
<div v-if="chartOption === 'io'" id="ioChart" style="width: 100%; height: 320px"></div> |
||||
<div v-if="chartOption === 'network'" id="networkChart" style="width: 100%; height: 320px"></div> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts" name="dataVisualize"> |
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; |
||||
import Pie from './components/pie.vue'; |
||||
import Curve from './components/curve.vue'; |
||||
import { ECharts } from 'echarts'; |
||||
/* 声明echarts实例 */ |
||||
interface ChartProps { |
||||
[key: string]: ECharts | null; |
||||
} |
||||
/* 获取子组件的ref */ |
||||
interface ChartExpose { |
||||
initChart: (params: any) => ECharts; |
||||
} |
||||
const pie = ref<ChartExpose>(); |
||||
const curve = ref<ChartExpose>(); |
||||
const data = reactive({ |
||||
activeName: 1, |
||||
bookSum: '848.132w', |
||||
}); |
||||
const dataScreen: ChartProps = reactive({ |
||||
chart1: null, |
||||
chart2: null, |
||||
}); |
||||
const handleClick = (): void => {}; |
||||
let tab = [ |
||||
{ label: '未来7日', name: 1 }, |
||||
{ label: '近七日', name: 2 }, |
||||
{ label: '近一月', name: 3 }, |
||||
{ label: '近三月', name: 4 }, |
||||
{ label: '近半年', name: 5 }, |
||||
{ label: '近一年', name: 6 }, |
||||
]; |
||||
// 模拟数据 |
||||
let pieData = [ |
||||
{ value: 5000, name: 'Gitee 访问量' }, |
||||
{ value: 5000, name: 'GitHub 访问量' }, |
||||
]; |
||||
let curveData = [ |
||||
{ value: 30, spotName: '掘金' }, |
||||
{ value: 90, spotName: 'CSDN' }, |
||||
{ value: 10, spotName: 'Gitee' }, |
||||
{ value: 70, spotName: 'GitHub' }, |
||||
{ value: 20, spotName: '知乎' }, |
||||
{ value: 60, spotName: 'MyBlog' }, |
||||
{ value: 55, spotName: '简书' }, |
||||
{ value: 80, spotName: 'StackOverFlow' }, |
||||
{ value: 50, spotName: '博客园' }, |
||||
]; |
||||
import { onMounted, onBeforeUnmount, ref } from 'vue'; |
||||
import * as echarts from 'echarts'; |
||||
import i18n from '@/lang'; |
||||
import { ContainerStats } from '@/api/modules/container'; |
||||
import { dateFromatForSecond } from '@/utils/util'; |
||||
import { useRouter } from 'vue-router'; |
||||
const router = useRouter(); |
||||
|
||||
const hoverApp = ref(); |
||||
const chartOption = ref('io'); |
||||
let timer: NodeJS.Timer | null = null; |
||||
let isInit = ref<boolean>(true); |
||||
|
||||
/* 初始化 echarts */ |
||||
const initCharts = (): void => { |
||||
dataScreen.chart1 = pie.value?.initChart(pieData) as ECharts; |
||||
dataScreen.chart2 = curve.value?.initChart(curveData) as ECharts; |
||||
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 changeOption = async () => { |
||||
isInit.value = true; |
||||
loadData(); |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
/* 初始化echarts */ |
||||
initCharts(); |
||||
// 为浏览器绑定事件 |
||||
window.addEventListener('resize', resize); |
||||
}); |
||||
const goRouter = async (path: string) => { |
||||
router.push({ path: path }); |
||||
}; |
||||
|
||||
/* 浏览器监听 resize 事件 */ |
||||
const resize = () => { |
||||
// 使用了 scale 的echarts其实不需要需要重新计算缩放比例 |
||||
Object.values(dataScreen).forEach((chart) => { |
||||
chart && chart.resize(); |
||||
}); |
||||
const loadData = async () => { |
||||
try { |
||||
const res = await ContainerStats('kubeoperator_nginx'); |
||||
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); |
||||
} |
||||
if (chartOption.value === 'io') { |
||||
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], |
||||
'流量', |
||||
'MB', |
||||
); |
||||
} else { |
||||
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', |
||||
); |
||||
} |
||||
} catch { |
||||
clearInterval(Number(timer)); |
||||
timer = null; |
||||
} |
||||
}; |
||||
|
||||
/* 销毁时触发 */ |
||||
function freshChart(chartName: string, legendDatas: any, xDatas: any, yDatas: any, yTitle: string, formatStr: string) { |
||||
console.log(chartName, echarts.getInstanceByDom(document.getElementById(chartName) as HTMLElement)); |
||||
if (isInit.value) { |
||||
echarts.init(document.getElementById(chartName) as HTMLElement); |
||||
isInit.value = false; |
||||
} |
||||
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); |
||||
} |
||||
|
||||
function changeChartSize() { |
||||
echarts.getInstanceByDom(document.getElementById('ioChart') as HTMLElement)?.resize(); |
||||
echarts.getInstanceByDom(document.getElementById('networkChart') as HTMLElement)?.resize(); |
||||
} |
||||
|
||||
onMounted(() => { |
||||
loadData(); |
||||
window.addEventListener('resize', changeChartSize); |
||||
timer = setInterval(async () => { |
||||
loadData(); |
||||
}, 3000); |
||||
}); |
||||
|
||||
onBeforeUnmount(() => { |
||||
window.removeEventListener('resize', resize); |
||||
clearInterval(Number(timer)); |
||||
timer = null; |
||||
window.removeEventListener('resize', changeChartSize); |
||||
}); |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
@import './index.scss'; |
||||
<style scoped> |
||||
.percentage-value { |
||||
display: block; |
||||
font-size: 16px; |
||||
} |
||||
.percentage-label { |
||||
display: block; |
||||
margin-top: 10px; |
||||
font-size: 12px; |
||||
} |
||||
.card-header { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
} |
||||
</style> |
||||
|