mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-10 18:05:48 +02:00
dashboard
This commit is contained in:
parent
3bf1655db7
commit
edc412a8e5
28 changed files with 187 additions and 954 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,3 +5,5 @@ database.db
|
|||
server/tmp/main
|
||||
node_modules
|
||||
dist
|
||||
nginx-ui-frontend/.env.development
|
||||
nginx-ui-frontend/.env.production
|
||||
|
|
|
@ -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 |
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
36
nginx-ui-frontend/src/components/Chart/LineChart.vue
Normal file
36
nginx-ui-frontend/src/components/Chart/LineChart.vue
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,13 +0,0 @@
|
|||
.antv-chart-mini {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.chart-wrapper {
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
width: 100%;
|
||||
|
||||
/* margin: 0 -5px;
|
||||
overflow: hidden;*/
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
.ant-pro-smooth-area {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ export const routes = [
|
|||
path: '/',
|
||||
name: '首页',
|
||||
component: () => import('@/layouts/BaseLayout'),
|
||||
redirect: '/domain',
|
||||
redirect: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -18,7 +18,7 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
devServer: {
|
||||
proxy: 'http://localhost:9000'
|
||||
proxy: 'https://nginx.jackyu.cn/'
|
||||
},
|
||||
|
||||
productionSourceMap: false,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue