dashboard

This commit is contained in:
Jacky 2021-03-22 11:19:00 +08:00
parent 3bf1655db7
commit edc412a8e5
28 changed files with 187 additions and 954 deletions

2
.gitignore vendored
View file

@ -5,3 +5,5 @@ database.db
server/tmp/main
node_modules
dist
nginx-ui-frontend/.env.development
nginx-ui-frontend/.env.production

View file

@ -10,9 +10,10 @@
"dependencies": {
"ant-design-vue": "^1.7.3",
"axios": "^0.21.1",
"chart.js": "^2.9.4",
"core-js": "^3.9.0",
"viser-vue": "^2.4.8",
"vue": "^2.6.11",
"vue-chartjs": "^3.5.1",
"vue-itextarea": "^1.0.9",
"vue-router": "^3.5.1",
"vuex": "^3.6.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,36 @@
<script>
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
name: "LineChart",
extends: Line,
mixins: [reactiveProp],
props: ['options'],
data() {
return {
updating: false
}
},
mounted () {
this.renderChart(this.chartData, this.options)
},
watch: {
chartData: {
deep: true,
handler () {
if (!this.updating && this.$data && this.$data._chart) {
// Update the chart
this.updating = true
this.$data._chart.update()
this.$nextTick(() => this.updating = false)
}
}
}
}
}
</script>
<style lang="less" scoped>
</style>

View file

@ -1,62 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="data"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
},
data: {
type: Array,
default: () => {
return []
}
},
scale: {
type: Array,
default: () => {
return [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
}
},
tooltip: {
type: Array,
default: () => {
return [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
}
}
},
data () {
return {
}
}
}
</script>

View file

@ -1,110 +0,0 @@
<template>
<a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
<div class="chart-card-header">
<a-statistic :title="title" :precision="precision" :value="total" />
</div>
<div class="chart-card-content">
<div class="content-fix">
<slot></slot>
</div>
</div>
<div class="chart-card-footer">
<div class="field">
<slot name="footer"></slot>
</div>
</div>
</a-card>
</template>
<script>
export default {
name: 'ChartCard',
props: {
title: {
type: String,
default: ''
},
total: {
type: [Function, Number, String],
required: false,
default: null
},
loading: {
type: Boolean,
default: false
},
precision: {
type: Number,
default: 0
}
}
}
</script>
<style lang="less" scoped>
.chart-card-header {
position: relative;
overflow: hidden;
width: 100%;
.meta {
position: relative;
overflow: hidden;
width: 100%;
color: rgba(0, 0, 0, .45);
font-size: 14px;
line-height: 22px;
}
}
.chart-card-action {
cursor: pointer;
position: absolute;
top: 0;
right: 0;
}
.chart-card-footer {
border-top: 1px solid #e8e8e8;
padding-top: 9px;
margin-top: 8px;
> * {
position: relative;
}
.field {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
}
}
.chart-card-content {
margin-bottom: 12px;
position: relative;
height: 46px;
width: 100%;
.content-fix {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
}
.total {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
color: #000;
margin-top: 4px;
margin-bottom: 0;
font-size: 30px;
line-height: 38px;
height: 38px;
}
</style>

View file

@ -1,67 +0,0 @@
<template>
<div>
<v-chart
:forceFit="true"
:height="height"
:width="width"
:data="data"
:scale="scale"
:padding="0">
<v-tooltip />
<v-interval
:shape="['liquid-fill-gauge']"
position="transfer*value"
color=""
:v-style="{
lineWidth: 10,
opacity: 0.75
}"
:tooltip="[
'transfer*value',
(transfer, value) => {
return {
name: transfer,
value,
};
},
]"
></v-interval>
<v-guide
v-for="(row, index) in data"
:key="index"
type="text"
:top="true"
:position="{
gender: row.transfer,
value: 45
}"
:content="row.value + '%'"
:v-style="{
fontSize: 100,
textAlign: 'center',
opacity: 0.75,
}"
/>
</v-chart>
</div>
</template>
<script>
export default {
name: 'Liquid',
props: {
height: {
type: Number,
default: 0
},
width: {
type: Number,
default: 0
}
}
}
</script>
<style scoped>
</style>

View file

@ -1,51 +0,0 @@
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 0, 18, 0]">
<v-tooltip/>
<v-smooth-area position="x*y"/>
</v-chart>
</div>
</div>
</template>
<script>
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: 'MiniArea',
props: {
data: {
type: Array,
default: null
},
},
data() {
return {
dataConfig: this.data,
tooltip,
scale,
height: 100
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>

View file

@ -1,37 +0,0 @@
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]">
<v-tooltip/>
<v-bar position="x*y"/>
</v-chart>
</div>
</div>
</template>
<script>
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
export default {
name: 'MiniBar',
props: {
data: Array,
},
data() {
return {
tooltip,
height: 100
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>

View file

@ -1,75 +0,0 @@
<template>
<div class="chart-mini-progress">
<div class="target" :style="{ left: target + '%'}">
<span :style="{ backgroundColor: color }" />
<span :style="{ backgroundColor: color }"/>
</div>
<div class="progress-wrapper">
<div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height }"></div>
</div>
</div>
</template>
<script>
export default {
name: 'MiniProgress',
props: {
target: {
type: Number,
default: 0
},
height: {
type: String,
default: '10px'
},
color: {
type: String,
default: '#13C2C2'
},
percentage: {
type: Number,
default: 0
}
}
}
</script>
<style lang="less" scoped>
.chart-mini-progress {
padding: 5px 0;
position: relative;
width: 100%;
.target {
position: absolute;
top: 0;
bottom: 0;
span {
border-radius: 100px;
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 2px;
&:last-child {
top: auto;
bottom: 0;
}
}
}
.progress-wrapper {
background-color: #f5f5f5;
position: relative;
.progress {
transition: all .4s cubic-bezier(.08,.82,.17,1) 0s;
border-radius: 1px 0 0 1px;
background-color: #1890ff;
width: 0;
height: 100%;
}
}
}
</style>

View file

@ -1,39 +0,0 @@
<template>
<div :class="prefixCls">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="100" :data="dataSource" :scale="scale" :padding="[36, 0, 18, 0]">
<v-tooltip />
<v-smooth-line position="x*y" :size="1" />
<v-smooth-area position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
export default {
name: 'MiniSmoothArea',
props: {
prefixCls: {
type: String,
default: 'ant-pro-smooth-area'
},
scale: {
type: [Object, Array],
},
dataSource: {
type: Array,
required: true
}
},
data () {
return {
height: 100
}
}
}
</script>
<style lang="less" scoped>
@import "smooth.area.less";
</style>

View file

@ -1,68 +0,0 @@
<template>
<v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
<v-tooltip></v-tooltip>
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" />
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" />
<v-legend dataKey="user" marker="circle" :offset="30" />
<v-coord type="polar" radius="0.8" />
<v-line position="item*score" color="user" :size="2" />
<v-point position="item*score" color="user" :size="4" shape="circle" />
</v-chart>
</template>
<script>
const axis1Opts = {
dataKey: 'item',
line: null,
tickLine: null,
grid: {
lineStyle: {
lineDash: null
},
hideFirstLine: false
}
}
const axis2Opts = {
dataKey: 'score',
line: null,
tickLine: null,
grid: {
type: 'polygon',
lineStyle: {
lineDash: null
}
}
}
const scale = [
{
dataKey: 'score',
min: 0,
max: 80
}, {
dataKey: 'user',
alias: '类型'
}
]
export default {
name: 'Radar',
props: {
data: {
type: Array,
default: null
}
},
data () {
return {
axis1Opts,
axis2Opts,
scale
}
}
}
</script>
<style scoped>
</style>

View file

@ -1,77 +0,0 @@
<template>
<div class="rank">
<h4 class="title">{{ title }}</h4>
<ul class="list">
<li :key="index" v-for="(item, index) in list">
<span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
<span>{{ item.name }}</span>
<span>{{ item.total }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'RankList',
// ['title', 'list']
props: {
title: {
type: String,
default: ''
},
list: {
type: Array,
default: null
}
}
}
</script>
<style lang="less" scoped>
.rank {
padding: 0 32px 32px 72px;
.list {
margin: 25px 0 0;
padding: 0;
list-style: none;
li {
margin-top: 16px;
span {
color: rgba(0, 0, 0, .65);
font-size: 14px;
line-height: 22px;
&:first-child {
background-color: #f5f5f5;
border-radius: 20px;
display: inline-block;
font-size: 12px;
font-weight: 600;
margin-right: 24px;
height: 20px;
line-height: 20px;
width: 20px;
text-align: center;
}
&.active {
background-color: #314659;
color: #fff;
}
&:last-child {
float: right;
}
}
}
}
}
.mobile .rank {
padding: 0 32px 32px 32px;
}
</style>

View file

@ -1,113 +0,0 @@
<template>
<v-chart :width="width" :height="height" :padding="[0]" :data="data" :scale="scale">
<v-tooltip :show-title="false" />
<v-coord type="rect" direction="TL" />
<v-point position="x*y" color="category" shape="cloud" tooltip="value*category" />
</v-chart>
</template>
<script>
import { registerShape } from 'viser-vue'
const DataSet = require('@antv/data-set')
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'
const scale = [
{ dataKey: 'x', nice: false },
{ dataKey: 'y', nice: false }
]
registerShape('point', 'cloud', {
draw (cfg, container) {
return container.addShape('text', {
attrs: {
fillOpacity: cfg.opacity,
fontSize: cfg.origin._origin.size,
rotate: cfg.origin._origin.rotate,
text: cfg.origin._origin.text,
textAlign: 'center',
fontFamily: cfg.origin._origin.font,
fill: cfg.color,
textBaseline: 'Alphabetic',
...cfg.style,
x: cfg.x,
y: cfg.y
}
})
}
})
export default {
name: 'TagCloud',
props: {
tagList: {
type: Array,
required: true
},
height: {
type: Number,
default: 400
},
width: {
type: Number,
default: 640
}
},
data () {
return {
data: [],
scale
}
},
watch: {
tagList: function (val) {
if (val.length > 0) {
this.initTagCloud(val)
}
}
},
mounted () {
if (this.tagList.length > 0) {
this.initTagCloud(this.tagList)
}
},
methods: {
initTagCloud (dataSource) {
const { height, width } = this
const dv = new DataSet.View().source(dataSource)
const range = dv.range('value')
const min = range[0]
const max = range[1]
const imageMask = new Image()
imageMask.crossOrigin = ''
imageMask.src = imgUrl
imageMask.onload = () => {
dv.transform({
type: 'tag-cloud',
fields: ['name', 'value'],
size: [width, height],
imageMask,
font: 'Verdana',
padding: 0,
timeInterval: 5000, // max execute time
rotate () {
let random = ~~(Math.random() * 4) % 4
if (random === 2) {
random = 0
}
return random * 90 // 0, 90, 270
},
fontSize (d) {
if (d.value) {
return ((d.value - min) / (max - min)) * (32 - 8) + 8
}
return 0
}
})
this.data = dv.rows
}
}
}
}
</script>

View file

@ -1,64 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="data"
:scale="scale"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
title: '日期(天)',
alias: '日期(天)',
min: 2
}, {
dataKey: 'y',
title: '流量(Gb)',
alias: '流量(Gb)',
min: 1
}]
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
data: [],
scale,
tooltip
}
},
created () {
this.getMonthBar()
},
methods: {
getMonthBar () {
this.$http.get('/analysis/month-bar')
.then(res => {
this.data = res.result
})
}
}
}
</script>

View file

@ -1,87 +0,0 @@
<template>
<div class="chart-trend">
{{ term }}
<span>{{ rate }}%</span>
<span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
</div>
</template>
<script>
export default {
name: 'Trend',
props: {
term: {
type: String,
default: '',
required: true
},
percentage: {
type: Number,
default: null
},
type: {
type: Boolean,
default: null
},
target: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
},
fixed: {
type: Number,
default: 2
}
},
data () {
return {
trend: this.type && 'up' || 'down',
rate: this.percentage
}
},
created () {
const type = this.type === null ? this.value >= this.target : this.type
this.trend = type ? 'up' : 'down'
this.rate = Math.abs(this.percentage)
},
watch: {
percentage() {
this.rate = Math.abs(this.percentage)
}
}
}
</script>
<style lang="less" scoped>
.chart-trend {
display: inline-block;
font-size: 14px;
line-height: 22px;
.trend-icon {
font-size: 12px;
&.up, &.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(.83);
}
}
&.up {
color: #f5222d;
}
&.down {
color: #52c41a;
top: -1px;
}
}
}
</style>

View file

@ -1,13 +0,0 @@
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
/* margin: 0 -5px;
overflow: hidden;*/
}
}

View file

@ -1,28 +0,0 @@
import Bar from './Bar'
import ChartCard from './ChartCard'
import Liquid from './Liquid'
import MiniArea from './MiniArea'
import MiniBar from './MiniBar'
import MiniProgress from './MiniProgress'
import MiniSelect from 'ant-design-vue/lib/pagination/MiniSelect'
import MiniSmoothArea from './MiniSmoothArea'
import Radar from './Radar'
import RankList from './RankList'
import TransferBar from './TransferBar'
import Trend from './Trend'
export {
Bar,
ChartCard,
Liquid,
MiniSmoothArea,
MiniSelect,
MiniProgress,
MiniArea,
MiniBar,
Radar,
RankList,
TransferBar,
Trend
}

View file

@ -1,4 +0,0 @@
.ant-pro-smooth-area {
position: relative;
width: 100%;
}

View file

@ -1,19 +1,13 @@
<template>
<div class="logo">
<img :src="logo"/>
<div class="text">Nginx UI</div>
<p class="text">Nginx UI</p>
<div class="clear"></div>
</div>
</template>
<script>
export default {
name: 'Logo',
data() {
return {
logo: require('@/assets/img/logo.png')
}
}
name: 'Logo'
}
</script>
@ -33,13 +27,11 @@ export default {
}
.text {
float: left;
font-weight: 300;
font-size: 22px;
font-size: 23px;
line-height: 48px;
height: 48px;
display: inline-block;
margin-left: 13px;
text-align: center;
}
}
</style>

View file

@ -1,5 +1,4 @@
import axios from 'axios'
import store from '../store'
/* 创建 axios 实例 */
let http = axios.create({
@ -33,13 +32,6 @@ http.interceptors.response.use(
},
async error => {
console.log(error)
switch (error.response.status) {
case 401:
case 403:
// 无权访问时,直接登出
await store.dispatch('logout')
break
}
return Promise.reject(error.response.data)
}
)

View file

@ -9,7 +9,7 @@ export const routes = [
path: '/',
name: '首页',
component: () => import('@/layouts/BaseLayout'),
redirect: '/domain',
redirect: '/dashboard',
children: [
{
path: 'dashboard',

View file

@ -4,13 +4,13 @@
<p>Yet another WebUI for Nginx</p>
<p>Version: {{ version }}-{{ build_id }}</p>
<h3>项目组</h3>
<p>前端0xJacky</p>
<p>后端0xJacky</p>
<p>Designer<a href="https://jackyu.cn/">@0xJacky</a></p>
<h3>技术栈</h3>
<p>Go</p>
<p>Gin</p>
<p>Vue</p>
<h3>开源协议</h3>
<p>GNU General Public License v2.0</p>
<p>Copyright © 2020 - {{ this_year }} 0xJacky </p>
</a-card>
</template>

View file

@ -2,7 +2,7 @@
<div>
<a-row class="row-two">
<a-col :lg="24" :sm="24">
<a-card style="min-height: 250px" title="后端服务器实时数据">
<a-card style="min-height: 400px" title="服务器状态">
<a-row>
<a-col :lg="12" :sm="24" class="chart">
<a-statistic :value="cpu" style="margin: 0 50px 10px 0" title="CPU">
@ -10,23 +10,27 @@
<span>%</span>
</template>
</a-statistic>
<mini-smooth-area :data-source="cpu_analytic"/>
<p>运行时间 {{ uptime }}</p>
<p>系统负载 1min:{{ loadavg.Loadavg1 }} 5min:{{ loadavg.Loadavg5 }}
15min:{{ loadavg.Loadavg15 }}</p>
<line-chart :chart-data="cpu_analytic" :options="cpu_analytic.options" :height="150"/>
</a-col>
<a-col :lg="6" :sm="10" class="chart">
<span>实际内存占用</span>
<a-col :lg="6" :sm="8" :xs="12" class="chart_dashboard">
<div>
<a-tooltip
:title="'已使用: '+ memory_used + ' / 总共: ' + memory_total">
<a-progress :percent="memory_pressure" strokeColor="rgb(135, 208, 104)" type="circle"/>
:title="'已使用: '+ memory_used + ' 缓存: ' + memory_cached + ' 空闲:' + memory_free +
' 物理内存: ' + memory_total">
<a-progress :percent="memory_pressure" strokeColor="rgb(135, 208, 104)" type="dashboard" />
<p class="description">实际内存占用</p>
</a-tooltip>
</div>
</a-col>
<a-col :lg="6" :sm="10" class="chart">
<span>存储空间</span>
<a-col :lg="6" :sm="8" :xs="12" class="chart_dashboard">
<div>
<a-tooltip
:title="'已使用: '+ disk_used + ' / 总共: ' + disk_total">
<a-progress :percent="disk_percentage" type="circle"/>
<a-progress :percent="disk_percentage" type="dashboard" />
<p class="description">存储空间</p>
</a-tooltip>
</div>
</a-col>
@ -38,16 +42,12 @@
</template>
<script>
import MiniSmoothArea from '@/components/Charts/MiniSmoothArea'
import Vue from 'vue'
import Viser from 'viser-vue'
Vue.use(Viser)
import LineChart from "@/components/Chart/LineChart"
export default {
name: "DashBoard",
components: {
MiniSmoothArea
LineChart
},
data() {
return {
@ -56,12 +56,58 @@ export default {
stat: {},
memory_pressure: 0,
memory_used: "",
memory_cached: "",
memory_free: "",
memory_total: "",
cpu_analytic: [],
cpu_analytic: {
datasets: [{
label: 'cpu user',
borderColor: '#36a3eb',
backgroundColor: '#36a3eb',
pointRadius: 0,
data: [],
}, {
label: 'cpu total',
borderColor: '#ff6385',
backgroundColor: '#ff6385',
pointRadius: 0,
data: [],
}],
options: {
responsive: true,
maintainAspectRatio:false,
responsiveAnimationDuration: 0, //
elements: {
line: {
tension: 0 // 线
}
},
scales: {
yAxes: [{
ticks: {
max: 100,
min: 0,
stepSize: 20,
display: true
}
}],
xAxes: [
{
type: "time",
time: {
unit: 'minute',
}
}
]
}
},
},
cpu: 0,
disk_percentage: 0,
disk_total: "",
disk_used: "",
uptime: "",
loadavg: {}
}
},
created() {
@ -87,17 +133,32 @@ export default {
const r = JSON.parse(m.data)
console.log(r)
this.cpu = r.cpu_system + r.cpu_user
this.cpu_analytic.push({x: new Date(), y: this.cpu})
if (this.cpu_analytic.length > 30) {
this.cpu_analytic.shift()
const time = new Date()
//this.cpu_analytic.labels.push(time)
this.cpu_analytic.datasets[0].data
.push({x: time, y: r.cpu_user})
this.cpu_analytic.datasets[1].data
.push({x: time, y: this.cpu})
if (this.cpu_analytic.datasets[0].data.length > 30) {
this.cpu_analytic.datasets[0].data.shift()
this.cpu_analytic.datasets[1].data.shift()
}
this.cpu = this.cpu.toFixed(2)
this.memory_pressure = r.memory_pressure
this.memory_used = r.memory_used
this.memory_cached = r.memory_cached
this.memory_free = r.memory_free
this.memory_total = r.memory_total
this.disk_percentage = r.disk_percentage
this.disk_used = r.disk_used
this.disk_total = r.disk_total
let uptime = Math.floor(r.uptime)
let uptime_days = Math.floor(uptime / 86400)
uptime -= uptime_days * 86400
let uptime_hours = Math.floor(uptime / 3600)
uptime -= uptime_hours * 3600
this.uptime = uptime_days + 'd ' + uptime_hours + 'h ' + Math.floor(uptime/60) + 'm'
this.loadavg = r.loadavg
}
}
}
@ -110,20 +171,16 @@ export default {
margin: 10px 0;
}
.chart-card-content, .chart-wrapper, .chart {
overflow: hidden;
.chart {
max-height: 300px;
}
}
.row-two {
.ant-card-body {
min-height: 255px;
}
}
.row-three {
.ant-card {
min-height: 377px;
.chart_dashboard {
padding: 50px;
.description {
width: 120px;
text-align: center
}
}
}
</style>

View file

@ -18,7 +18,7 @@ module.exports = {
},
},
devServer: {
proxy: 'http://localhost:9000'
proxy: 'https://nginx.jackyu.cn/'
},
productionSourceMap: false,

View file

@ -1069,6 +1069,13 @@
"@types/connect" "*"
"@types/node" "*"
"@types/chart.js@^2.7.55":
version "2.9.31"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.31.tgz#e8ebc7ed18eb0e5114c69bd46ef8e0037c89d39d"
integrity sha512-hzS6phN/kx3jClk3iYqEHNnYIRSi4RZrIGJ8CDLjgatpHoftCezvC44uqB3o3OUm9ftU1m7sHG8+RLyPTlACrA==
dependencies:
moment "^2.10.2"
"@types/connect-history-api-fallback@*":
version "1.3.3"
resolved "https://registry.npm.taobao.org/@types/connect-history-api-fallback/download/@types/connect-history-api-fallback-1.3.3.tgz#4772b79b8b53185f0f4c9deab09236baf76ee3b4"
@ -2571,6 +2578,29 @@ chardet@^0.7.0:
resolved "https://registry.npm.taobao.org/chardet/download/chardet-0.7.0.tgz?cache=0&sync_timestamp=1601032454247&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchardet%2Fdownload%2Fchardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=
chart.js@^2.9.4:
version "2.9.4"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684"
integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==
dependencies:
chartjs-color "^2.1.0"
moment "^2.10.2"
chartjs-color-string@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
dependencies:
color-name "^1.0.0"
chartjs-color@^2.1.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
dependencies:
chartjs-color-string "^0.6.0"
color-convert "^1.9.3"
check-types@^8.0.3:
version "8.0.3"
resolved "https://registry.npm.taobao.org/check-types/download/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
@ -2770,7 +2800,7 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
color-convert@^1.9.0, color-convert@^1.9.1:
color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3:
version "1.9.3"
resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=
@ -6021,7 +6051,12 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
moment@^2.21.0, moment@^2.24.0:
moment-duration-format@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212"
integrity sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ==
moment@^2.10.2, moment@^2.21.0, moment@^2.24.0:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
@ -8871,6 +8906,13 @@ vm-browserify@^1.0.1:
resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA=
vue-chartjs@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-3.5.1.tgz#d25e845708f7744ae51bed9d23a975f5f8fc6529"
integrity sha512-foocQbJ7FtveICxb4EV5QuVpo6d8CmZFmAopBppDIGKY+esJV8IJgwmEW0RexQhxqXaL/E1xNURsgFFYyKzS/g==
dependencies:
"@types/chart.js" "^2.7.55"
vue-cli-plugin-generate-build-id@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/vue-cli-plugin-generate-build-id/-/vue-cli-plugin-generate-build-id-0.1.0.tgz#164108d3e7ba72041965dbd530c2c30efb6ce09f"

View file

@ -9,6 +9,8 @@ import (
"github.com/gorilla/websocket"
"github.com/mackerelio/go-osstat/cpu"
"github.com/mackerelio/go-osstat/memory"
"github.com/mackerelio/go-osstat/uptime"
"github.com/mackerelio/go-osstat/loadavg"
"net/http"
"strconv"
"time"
@ -72,6 +74,10 @@ func Analytic(c *gin.Context) {
response["cpu_idle"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
float64(after.Idle-before.Idle)/total*100), 64)
response["uptime"], _ = uptime.Get()
response["uptime"] = response["uptime"].(time.Duration) / time.Second
response["loadavg"], _ = loadavg.Get()
used, _total, percentage, err := tool.DiskUsage(".")
response["disk_used"] = used