mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
redesign Dashboard
This commit is contained in:
parent
d50bb154f1
commit
46e86d2a3d
17 changed files with 834 additions and 167 deletions
|
@ -9,11 +9,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ant-design-vue": "^1.7.3",
|
"ant-design-vue": "^1.7.3",
|
||||||
|
"apexcharts": "^3.33.1",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"core-js": "^3.9.0",
|
"core-js": "^3.9.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
|
"vue-apexcharts": "^1.6.2",
|
||||||
"vue-chartjs": "^3.5.1",
|
"vue-chartjs": "^3.5.1",
|
||||||
"vue-codemirror": "^4.0.6",
|
"vue-codemirror": "^4.0.6",
|
||||||
"vue-gettext": "^2.1.12",
|
"vue-gettext": "^2.1.12",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import http from '@/lib/http'
|
import http from '@/lib/http'
|
||||||
|
|
||||||
const analytic = {
|
const analytic = {
|
||||||
cpu_usage() {
|
init() {
|
||||||
return http.get('/analytic/cpu')
|
return http.get('/analytic/init')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
83
frontend/src/components/Chart/CPUChart.vue
Normal file
83
frontend/src/components/Chart/CPUChart.vue
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<apexchart type="area" height="200" :options="chartOptions" :series="series" ref="chart"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from 'vue-apexcharts'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
Vue.use(VueApexCharts)
|
||||||
|
Vue.component('apexchart', VueApexCharts)
|
||||||
|
export default {
|
||||||
|
name: 'CPUChart',
|
||||||
|
props: {
|
||||||
|
series: Array
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
series: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.$refs.chart.updateSeries(this.series)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartOptions: {
|
||||||
|
series: this.series,
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
zoom: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: ['#ff6385', '#36a3eb'],
|
||||||
|
fill: {
|
||||||
|
// type: ['solid', 'gradient'],
|
||||||
|
gradient: {
|
||||||
|
shade: 'light'
|
||||||
|
}
|
||||||
|
//colors: ['#ff6385', '#36a3eb'],
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 0,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {datetimeUTC: false},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
max: 100,
|
||||||
|
tickAmount: 4,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
onItemClick: {
|
||||||
|
toggleDataSeries: false
|
||||||
|
},
|
||||||
|
onItemHover: {
|
||||||
|
highlightDataSeries: false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
82
frontend/src/components/Chart/DiskChart.vue
Normal file
82
frontend/src/components/Chart/DiskChart.vue
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<apexchart type="area" height="200" :options="chartOptions" :series="series" ref="chart"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from 'vue-apexcharts'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
Vue.use(VueApexCharts)
|
||||||
|
Vue.component('apexchart', VueApexCharts)
|
||||||
|
export default {
|
||||||
|
name: 'DiskChart',
|
||||||
|
props: {
|
||||||
|
series: Array
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
series: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.$refs.chart.updateSeries(this.series)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartOptions: {
|
||||||
|
series: this.series,
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
zoom: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: ['#ff6385', '#36a3eb'],
|
||||||
|
fill: {
|
||||||
|
// type: ['solid', 'gradient'],
|
||||||
|
gradient: {
|
||||||
|
shade: 'light'
|
||||||
|
}
|
||||||
|
//colors: ['#ff6385', '#36a3eb'],
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 0,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {datetimeUTC: false},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 3,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
onItemClick: {
|
||||||
|
toggleDataSeries: false
|
||||||
|
},
|
||||||
|
onItemHover: {
|
||||||
|
highlightDataSeries: false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
87
frontend/src/components/Chart/NetChart.vue
Normal file
87
frontend/src/components/Chart/NetChart.vue
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<apexchart type="area" height="200" :options="chartOptions" :series="series" ref="chart"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from 'vue-apexcharts'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
Vue.use(VueApexCharts)
|
||||||
|
Vue.component('apexchart', VueApexCharts)
|
||||||
|
export default {
|
||||||
|
name: 'NetChart',
|
||||||
|
props: {
|
||||||
|
series: Array
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
series: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.$refs.chart.updateSeries(this.series)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartOptions: {
|
||||||
|
series: this.series,
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
zoom: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: ['#ff6385', '#36a3eb'],
|
||||||
|
fill: {
|
||||||
|
// type: ['solid', 'gradient'],
|
||||||
|
gradient: {
|
||||||
|
shade: 'light'
|
||||||
|
}
|
||||||
|
//colors: ['#ff6385', '#36a3eb'],
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 0,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {datetimeUTC: false},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 3,
|
||||||
|
min: 0,
|
||||||
|
labels: {
|
||||||
|
formatter: (bytes) => {
|
||||||
|
return this.bytesToSize(bytes) + '/s'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
onItemClick: {
|
||||||
|
toggleDataSeries: false
|
||||||
|
},
|
||||||
|
onItemHover: {
|
||||||
|
highlightDataSeries: false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
93
frontend/src/components/Chart/RadialBarChart.vue
Normal file
93
frontend/src/components/Chart/RadialBarChart.vue
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<p class="text">{{ centerText }}</p>
|
||||||
|
<apexchart type="radialBar" height="205" :options="chartOptions" :series="series" ref="chart"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from 'vue-apexcharts'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
Vue.use(VueApexCharts)
|
||||||
|
Vue.component('apexchart', VueApexCharts)
|
||||||
|
export default {
|
||||||
|
name: 'RadialBarChart',
|
||||||
|
props: {
|
||||||
|
series: Array,
|
||||||
|
centerText: String,
|
||||||
|
colors: String,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
series: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.$refs.chart.updateSeries(this.series)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartOptions: {
|
||||||
|
series: this.series,
|
||||||
|
chart: {
|
||||||
|
type: 'radialBar',
|
||||||
|
offsetY: -10
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
startAngle: -135,
|
||||||
|
endAngle: 135,
|
||||||
|
dataLabels: {
|
||||||
|
name: {
|
||||||
|
fontSize: '15px',
|
||||||
|
color: this.colors,
|
||||||
|
offsetY: 56
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
offsetY: 60,
|
||||||
|
fontSize: '14px',
|
||||||
|
color: undefined,
|
||||||
|
formatter: function (val) {
|
||||||
|
return val + "%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
colors: this.colors
|
||||||
|
},
|
||||||
|
labels: [this.name],
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
filter: {
|
||||||
|
type: 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
filter: {
|
||||||
|
type: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
.text {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(72px);
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -62,17 +62,16 @@ export const routes = [
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'file',
|
icon: 'file',
|
||||||
hideChildren: true
|
hideChildren: true
|
||||||
},
|
}
|
||||||
children: [{
|
},
|
||||||
path: 'config/:name',
|
{
|
||||||
name: $gettext('Edit Configuration'),
|
path: 'config/:name',
|
||||||
component: () => import('@/views/config/ConfigEdit.vue'),
|
name: $gettext('Edit Configuration'),
|
||||||
meta: {
|
component: () => import('@/views/config/ConfigEdit.vue'),
|
||||||
hiddenInSidebar: true
|
meta: {
|
||||||
},
|
hiddenInSidebar: true
|
||||||
},]
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
name: $gettext('About'),
|
name: $gettext('About'),
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<vue-itextarea v-model="configText"/>
|
<vue-itextarea v-model="configText"/>
|
||||||
<footer-tool-bar>
|
<footer-tool-bar>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="$router.go(-1)" v-translate>Cancel</a-button>
|
<a-button @click="$router.go(-1)"><translate>Cancel</translate></a-button>
|
||||||
<a-button type="primary" @click="save" v-translate>Save</a-button>
|
<a-button type="primary" @click="save"><translate>Save</translate></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</footer-tool-bar>
|
</footer-tool-bar>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
|
@ -1,64 +1,148 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-row class="row-two">
|
<a-row :gutter="[16,16]" class="first-row">
|
||||||
<a-col :lg="24" :sm="24">
|
<a-col :lg="7" :md="24">
|
||||||
<a-card style="min-height: 400px" :title="$gettext('Server Status')">
|
<a-card :title="$gettext('Server Info')">
|
||||||
|
<p>
|
||||||
|
<translate>Uptime:</translate>
|
||||||
|
{{ uptime }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<translate>Load Averages:</translate>
|
||||||
|
1min:{{ loadavg?.load1?.toFixed(2) }} |
|
||||||
|
5min:{{ loadavg?.load5?.toFixed(2) }} |
|
||||||
|
15min:{{ loadavg?.load15?.toFixed(2) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<translate>OS:</translate>
|
||||||
|
{{ host.platform }} ({{ host.platformVersion }}
|
||||||
|
{{ host.os }} {{ host.kernelVersion }}
|
||||||
|
{{ host.kernelArch }})
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<translate>CPU:</translate>
|
||||||
|
{{ cpu_info[0]?.modelName }} * {{ cpu_info.length }}
|
||||||
|
</p>
|
||||||
|
<!-- <p><translate>Memory</translate>: {{-->
|
||||||
|
<!-- $gettextInterpolate(-->
|
||||||
|
<!-- $gettext('Used: %{u}, Cached: %{c}, Free: %{f}, Physical Memory: %{p}'),-->
|
||||||
|
<!-- {u: memory_used, c: memory_cached, f: memory_free, p: memory_total})-->
|
||||||
|
<!-- }}</p>-->
|
||||||
|
<!-- <p><translate>Storage</translate>: {{-->
|
||||||
|
<!-- $gettextInterpolate($gettext('Used: %{used} / Total: %{total}'),-->
|
||||||
|
<!-- {used: disk_used, total: disk_total})-->
|
||||||
|
<!-- }}-->
|
||||||
|
<!-- </p>-->
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="24" class="chart_dashboard">
|
||||||
|
<a-card>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :lg="12" :sm="24" class="chart">
|
<a-col :xs="24" :sm="24" :md="8">
|
||||||
<a-statistic :value="cpu" style="margin: 0 50px 10px 0" title="CPU">
|
<radial-bar-chart :name="$gettext('Memory')" :series="[memory_pressure]"
|
||||||
<template v-slot:suffix>
|
:centerText="memory_used" colors="#36a3eb"/>
|
||||||
<span>%</span>
|
|
||||||
</template>
|
|
||||||
</a-statistic>
|
|
||||||
<p>
|
|
||||||
<translate>Uptime</translate>
|
|
||||||
{{ uptime }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<translate>Load Averages:</translate>
|
|
||||||
1min:{{ loadavg?.load1?.toFixed(2) }} |
|
|
||||||
5min:{{ loadavg?.load5?.toFixed(2) }} |
|
|
||||||
15min:{{ loadavg?.load15?.toFixed(2) }}
|
|
||||||
</p>
|
|
||||||
<line-chart :chart-data="cpu_analytic" :options="cpu_analytic.options" :height="150"/>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="6" :sm="8" :xs="12" class="chart_dashboard">
|
<a-col :xs="24" :sm="12" :md="8">
|
||||||
<div>
|
<radial-bar-chart :name="$gettext('Swap')" :series="[memory_swap_percent]"
|
||||||
<a-tooltip
|
:centerText="memory_swap_used" colors="#ff6385"/>
|
||||||
:title="$gettextInterpolate(
|
|
||||||
$gettext('Used: %{u}, Cached: %{c}, Free: %{f}, Physical Memory: %{p}'),
|
|
||||||
{u: memory_used, c: memory_cached, f:memory_free, p: memory_total})">
|
|
||||||
<a-progress :percent="memory_pressure" strokeColor="rgb(135, 208, 104)"
|
|
||||||
type="dashboard"/>
|
|
||||||
<p class="description" v-translate>Memory</p>
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="6" :sm="8" :xs="12" class="chart_dashboard">
|
<a-col :xs="24" :sm="12" :md="8">
|
||||||
<div>
|
<radial-bar-chart :name="$gettext('Storage')" :series="[disk_percentage]"
|
||||||
<a-tooltip
|
:centerText="disk_used" colors="#87d068"/>
|
||||||
:title="$gettextInterpolate($gettext('Used: %{used} / Total: %{total}'),
|
</a-col>
|
||||||
{used: disk_used, total: disk_total})">
|
</a-row>
|
||||||
<a-progress :percent="disk_percentage" type="dashboard"/>
|
</a-card>
|
||||||
<p class="description" v-translate>Storage</p>
|
</a-col>
|
||||||
</a-tooltip>
|
<a-col :lg="5" :sm="24" class="chart_dashboard">
|
||||||
</div>
|
<a-card>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-statistic :value="bytesToSize(net.last_recv)"
|
||||||
|
:title="$gettext('Network Total Receive')"/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-statistic :value="bytesToSize(net.last_sent)"
|
||||||
|
:title="$gettext('Network Total Send')" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-row class="row-two" :gutter="[16,32]">
|
||||||
|
<a-col :lg="8" :md="24" :sm="24">
|
||||||
|
<a-card :title="$gettext('CPU Status')">
|
||||||
|
<a-statistic :value="cpu" title="CPU">
|
||||||
|
<template v-slot:suffix>
|
||||||
|
<span>%</span>
|
||||||
|
</template>
|
||||||
|
</a-statistic>
|
||||||
|
<c-p-u-chart :series="cpu_analytic_series"/>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="24" :sm="24">
|
||||||
|
<a-card :title="$gettext('Network')">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-statistic :value="bytesToSize(net.recv)"
|
||||||
|
:title="$gettext('Receive')">
|
||||||
|
<template v-slot:suffix>
|
||||||
|
<span>/s</span>
|
||||||
|
</template>
|
||||||
|
</a-statistic>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-statistic :value="bytesToSize(net.sent)" :title="$gettext('Send')">
|
||||||
|
<template v-slot:suffix>
|
||||||
|
<span>/s</span>
|
||||||
|
</template>
|
||||||
|
</a-statistic>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<net-chart :series="net_analytic"/>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="24" :sm="24">
|
||||||
|
<a-card :title="$gettext('Disk IO')">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-statistic :value="diskIO.writes"
|
||||||
|
:title="$gettext('Writes')">
|
||||||
|
<template v-slot:suffix>
|
||||||
|
<span>/s</span>
|
||||||
|
</template>
|
||||||
|
</a-statistic>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-statistic :value="diskIO.reads" :title="$gettext('Reads')">
|
||||||
|
<template v-slot:suffix>
|
||||||
|
<span>/s</span>
|
||||||
|
</template>
|
||||||
|
</a-statistic>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<disk-chart :series="diskIO_analytic"/>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LineChart from '@/components/Chart/LineChart'
|
|
||||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||||
|
import CPUChart from '@/components/Chart/CPUChart'
|
||||||
|
import NetChart from '@/components/Chart/NetChart'
|
||||||
|
import $gettext from '@/lib/translate/gettext'
|
||||||
|
import RadialBarChart from '@/components/Chart/RadialBarChart'
|
||||||
|
import DiskChart from '@/components/Chart/DiskChart'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DashBoard',
|
name: 'DashBoard',
|
||||||
components: {
|
components: {
|
||||||
LineChart
|
DiskChart,
|
||||||
|
RadialBarChart,
|
||||||
|
NetChart,
|
||||||
|
CPUChart,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -70,55 +154,47 @@ export default {
|
||||||
memory_cached: '',
|
memory_cached: '',
|
||||||
memory_free: '',
|
memory_free: '',
|
||||||
memory_total: '',
|
memory_total: '',
|
||||||
cpu_analytic: {
|
cpu_analytic_series: [{
|
||||||
datasets: [{
|
name: 'CPU User',
|
||||||
label: 'cpu user',
|
data: []
|
||||||
borderColor: '#36a3eb',
|
}, {
|
||||||
backgroundColor: '#36a3eb',
|
name: 'CPU Total',
|
||||||
pointRadius: 0,
|
data: []
|
||||||
data: [],
|
}],
|
||||||
}, {
|
|
||||||
label: 'cpu total',
|
|
||||||
borderColor: '#ff6385',
|
|
||||||
backgroundColor: '#ff6385',
|
|
||||||
pointRadius: 0,
|
|
||||||
data: [],
|
|
||||||
}],
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
responsiveAnimationDuration: 0, // Duration of the animation after resizing 调整大小后的动画持续时间
|
|
||||||
elements: {
|
|
||||||
line: {
|
|
||||||
tension: 0 // Disable Bessel curves 禁用贝塞尔曲线
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
max: 100,
|
|
||||||
min: 0,
|
|
||||||
stepSize: 20,
|
|
||||||
display: true
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
xAxes: [
|
|
||||||
{
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'minute',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cpu: 0,
|
cpu: 0,
|
||||||
|
memory_swap_used: '',
|
||||||
|
memory_swap_percent: 0,
|
||||||
disk_percentage: 0,
|
disk_percentage: 0,
|
||||||
disk_total: '',
|
disk_total: '',
|
||||||
disk_used: '',
|
disk_used: '',
|
||||||
|
net: {
|
||||||
|
recv: 0,
|
||||||
|
sent: 0,
|
||||||
|
last_recv: 0,
|
||||||
|
last_sent: 0,
|
||||||
|
},
|
||||||
|
diskIO: {
|
||||||
|
writes: 0,
|
||||||
|
reads: 0,
|
||||||
|
},
|
||||||
|
net_analytic: [{
|
||||||
|
name: $gettext('Receive'),
|
||||||
|
data: []
|
||||||
|
}, {
|
||||||
|
name: $gettext('Send'),
|
||||||
|
data: []
|
||||||
|
}],
|
||||||
|
diskIO_analytic: [{
|
||||||
|
name: $gettext('Writes'),
|
||||||
|
data: []
|
||||||
|
}, {
|
||||||
|
name: $gettext('Reads'),
|
||||||
|
data: []
|
||||||
|
}],
|
||||||
uptime: '',
|
uptime: '',
|
||||||
loadavg: {}
|
loadavg: {},
|
||||||
|
cpu_info: [],
|
||||||
|
host: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -126,9 +202,25 @@ export default {
|
||||||
+ btoa(this.$store.state.user.token))
|
+ btoa(this.$store.state.user.token))
|
||||||
this.websocket.onmessage = this.wsOnMessage
|
this.websocket.onmessage = this.wsOnMessage
|
||||||
this.websocket.onopen = this.wsOpen
|
this.websocket.onopen = this.wsOpen
|
||||||
this.$api.analytic.cpu_usage().then(r => {
|
this.$api.analytic.init().then(r => {
|
||||||
this.cpu_analytic.datasets[0].data = this.cpu_analytic.datasets[0].data.concat(r.user)
|
this.cpu_info = r.cpu.info
|
||||||
this.cpu_analytic.datasets[1].data = this.cpu_analytic.datasets[1].data.concat(r.total)
|
this.net.last_recv = r.network.init.bytesRecv
|
||||||
|
this.net.last_sent = r.network.init.bytesSent
|
||||||
|
this.host = r.host
|
||||||
|
r.cpu.user.forEach(u => {
|
||||||
|
this.cpu_analytic_series[0].data.push([u.x, u.y.toFixed(2)])
|
||||||
|
})
|
||||||
|
r.cpu.total.forEach(u => {
|
||||||
|
this.cpu_analytic_series[1].data.push([u.x, u.y.toFixed(2)])
|
||||||
|
})
|
||||||
|
r.network.bytesRecv.forEach(u => {
|
||||||
|
this.net_analytic[0].data.push([u.x, u.y.toFixed(2)])
|
||||||
|
})
|
||||||
|
r.network.bytesSent.forEach(u => {
|
||||||
|
this.net_analytic[1].data.push([u.x, u.y.toFixed(2)])
|
||||||
|
})
|
||||||
|
this.diskIO_analytic[0].data = this.diskIO_analytic[0].data.concat(r.diskIO.writes)
|
||||||
|
this.diskIO_analytic[1].data = this.diskIO_analytic[1].data.concat(r.diskIO.reads)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
|
@ -142,24 +234,31 @@ export default {
|
||||||
const r = JSON.parse(m.data)
|
const r = JSON.parse(m.data)
|
||||||
// console.log(r)
|
// console.log(r)
|
||||||
this.cpu = r.cpu_system + r.cpu_user
|
this.cpu = r.cpu_system + r.cpu_user
|
||||||
const time = new Date()
|
|
||||||
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 > 200) {
|
|
||||||
this.cpu_analytic.datasets[0].data.shift()
|
|
||||||
this.cpu_analytic.datasets[1].data.shift()
|
|
||||||
}
|
|
||||||
this.cpu = this.cpu.toFixed(2)
|
this.cpu = this.cpu.toFixed(2)
|
||||||
|
const time = new Date().getTime()
|
||||||
|
|
||||||
|
this.cpu_analytic_series[0].data.push([time, r.cpu_user.toFixed(2)])
|
||||||
|
this.cpu_analytic_series[1].data.push([time, this.cpu])
|
||||||
|
|
||||||
|
if (this.cpu_analytic_series[0].data.length > 100) {
|
||||||
|
this.cpu_analytic_series[0].data.shift()
|
||||||
|
this.cpu_analytic_series[1].data.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
// mem
|
||||||
this.memory_pressure = r.memory_pressure
|
this.memory_pressure = r.memory_pressure
|
||||||
this.memory_used = r.memory_used
|
this.memory_used = r.memory_used
|
||||||
this.memory_cached = r.memory_cached
|
this.memory_cached = r.memory_cached
|
||||||
this.memory_free = r.memory_free
|
this.memory_free = r.memory_free
|
||||||
this.memory_total = r.memory_total
|
this.memory_total = r.memory_total
|
||||||
|
this.memory_swap_percent = r.memory_swap_percent
|
||||||
|
this.memory_swap_used = r.memory_swap_used
|
||||||
|
|
||||||
|
// disk
|
||||||
this.disk_percentage = r.disk_percentage
|
this.disk_percentage = r.disk_percentage
|
||||||
this.disk_used = r.disk_used
|
this.disk_used = r.disk_used
|
||||||
this.disk_total = r.disk_total
|
this.disk_total = r.disk_total
|
||||||
|
|
||||||
let uptime = Math.floor(r.uptime)
|
let uptime = Math.floor(r.uptime)
|
||||||
let uptime_days = Math.floor(uptime / 86400)
|
let uptime_days = Math.floor(uptime / 86400)
|
||||||
uptime -= uptime_days * 86400
|
uptime -= uptime_days * 86400
|
||||||
|
@ -167,16 +266,52 @@ export default {
|
||||||
uptime -= uptime_hours * 3600
|
uptime -= uptime_hours * 3600
|
||||||
this.uptime = uptime_days + 'd ' + uptime_hours + 'h ' + Math.floor(uptime / 60) + 'm'
|
this.uptime = uptime_days + 'd ' + uptime_hours + 'h ' + Math.floor(uptime / 60) + 'm'
|
||||||
this.loadavg = r.loadavg
|
this.loadavg = r.loadavg
|
||||||
|
|
||||||
|
// net
|
||||||
|
this.net.recv = r.network.bytesRecv - this.net.last_recv
|
||||||
|
this.net.sent = r.network.bytesSent - this.net.last_sent
|
||||||
|
this.net.last_recv = r.network.bytesRecv
|
||||||
|
this.net.last_sent = r.network.bytesSent
|
||||||
|
|
||||||
|
this.net_analytic[0].data.push([time, this.net.recv])
|
||||||
|
this.net_analytic[1].data.push([time, this.net.sent])
|
||||||
|
|
||||||
|
if (this.net_analytic[0].data.length > 100) {
|
||||||
|
this.net_analytic[1].data.shift()
|
||||||
|
this.net_analytic[0].data.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
// diskIO
|
||||||
|
this.diskIO.writes = r.diskIO.writes.y
|
||||||
|
this.diskIO.reads = r.diskIO.reads.y
|
||||||
|
|
||||||
|
this.diskIO_analytic[0].data.push(r.diskIO.writes)
|
||||||
|
this.diskIO_analytic[1].data.push(r.diskIO.reads)
|
||||||
|
|
||||||
|
if (this.diskIO_analytic[0].data.length > 100) {
|
||||||
|
this.diskIO_analytic[0].data.shift()
|
||||||
|
this.diskIO_analytic[1].data.shift()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.first-row {
|
||||||
|
.ant-card {
|
||||||
|
min-height: 227px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card {
|
.ant-card {
|
||||||
|
.ant-statistic {
|
||||||
|
margin: 0 50px 10px 10px
|
||||||
|
}
|
||||||
|
|
||||||
.chart {
|
.chart {
|
||||||
max-height: 300px;
|
max-width: 800px;
|
||||||
|
max-height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart_dashboard {
|
.chart_dashboard {
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
|
|
||||||
<footer-tool-bar>
|
<footer-tool-bar>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="$router.push('/domain/list')" v-translate>Cancel</a-button>
|
<a-button @click="$router.go(-1)"><translate>Cancel</translate></a-button>
|
||||||
<a-button type="primary" @click="save" v-translate>Save</a-button>
|
<a-button type="primary" @click="save"><translate>Save</translate></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</footer-tool-bar>
|
</footer-tool-bar>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2536,6 +2536,18 @@ anymatch@~3.1.1:
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
picomatch "^2.0.4"
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
|
apexcharts@^3.33.1:
|
||||||
|
version "3.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.33.1.tgz#7159f45e7d726a548e5135a327c03e7894d0bf13"
|
||||||
|
integrity sha512-5aVzrgJefd8EH4w7oRmuOhA3+cxJxQg27cYg3ANVGvPCOB4AY3mVVNtFHRFaIq7bv8ws4GRaA9MWfzoWQw3MPQ==
|
||||||
|
dependencies:
|
||||||
|
svg.draggable.js "^2.2.2"
|
||||||
|
svg.easing.js "^2.0.0"
|
||||||
|
svg.filter.js "^2.0.2"
|
||||||
|
svg.pathmorphing.js "^0.1.3"
|
||||||
|
svg.resize.js "^1.4.3"
|
||||||
|
svg.select.js "^3.0.1"
|
||||||
|
|
||||||
aproba@^1.0.3, aproba@^1.1.1:
|
aproba@^1.0.3, aproba@^1.1.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npm.taobao.org/aproba/download/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
resolved "https://registry.npm.taobao.org/aproba/download/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||||
|
@ -9739,6 +9751,61 @@ svg-tags@^1.0.0:
|
||||||
resolved "https://registry.npm.taobao.org/svg-tags/download/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
|
resolved "https://registry.npm.taobao.org/svg-tags/download/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
|
||||||
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
|
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
|
||||||
|
|
||||||
|
svg.draggable.js@^2.2.2:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba"
|
||||||
|
integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==
|
||||||
|
dependencies:
|
||||||
|
svg.js "^2.0.1"
|
||||||
|
|
||||||
|
svg.easing.js@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12"
|
||||||
|
integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=
|
||||||
|
dependencies:
|
||||||
|
svg.js ">=2.3.x"
|
||||||
|
|
||||||
|
svg.filter.js@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203"
|
||||||
|
integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=
|
||||||
|
dependencies:
|
||||||
|
svg.js "^2.2.5"
|
||||||
|
|
||||||
|
svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5:
|
||||||
|
version "2.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d"
|
||||||
|
integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==
|
||||||
|
|
||||||
|
svg.pathmorphing.js@^0.1.3:
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65"
|
||||||
|
integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==
|
||||||
|
dependencies:
|
||||||
|
svg.js "^2.4.0"
|
||||||
|
|
||||||
|
svg.resize.js@^1.4.3:
|
||||||
|
version "1.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332"
|
||||||
|
integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==
|
||||||
|
dependencies:
|
||||||
|
svg.js "^2.6.5"
|
||||||
|
svg.select.js "^2.1.2"
|
||||||
|
|
||||||
|
svg.select.js@^2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73"
|
||||||
|
integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==
|
||||||
|
dependencies:
|
||||||
|
svg.js "^2.2.5"
|
||||||
|
|
||||||
|
svg.select.js@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917"
|
||||||
|
integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==
|
||||||
|
dependencies:
|
||||||
|
svg.js "^2.6.5"
|
||||||
|
|
||||||
svgo@^1.0.0:
|
svgo@^1.0.0:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.npm.taobao.org/svgo/download/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
|
resolved "https://registry.npm.taobao.org/svgo/download/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
|
||||||
|
@ -10304,6 +10371,11 @@ void-elements@^3.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||||
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
||||||
|
|
||||||
|
vue-apexcharts@^1.6.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-apexcharts/-/vue-apexcharts-1.6.2.tgz#0547826067f97e8ea67ca9423e524eb6669746ad"
|
||||||
|
integrity sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==
|
||||||
|
|
||||||
vue-chartjs@^3.5.1:
|
vue-chartjs@^3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-3.5.1.tgz#d25e845708f7744ae51bed9d23a975f5f8fc6529"
|
resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-3.5.1.tgz#d25e845708f7744ae51bed9d23a975f5f8fc6529"
|
||||||
|
|
2
main.go
2
main.go
|
@ -36,7 +36,7 @@ func main() {
|
||||||
if "" != settings.ServerSettings.JwtSecret {
|
if "" != settings.ServerSettings.JwtSecret {
|
||||||
model.Init()
|
model.Init()
|
||||||
go tool.AutoCert()
|
go tool.AutoCert()
|
||||||
go tool.RecordCpuUsage()
|
go tool.RecordServerAnalytic()
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/shirou/gopsutil/v3/load"
|
"github.com/shirou/gopsutil/v3/load"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -54,6 +55,10 @@ func Analytic(c *gin.Context) {
|
||||||
response["memory_used"] = humanize.Bytes(memoryStat.Used)
|
response["memory_used"] = humanize.Bytes(memoryStat.Used)
|
||||||
response["memory_cached"] = humanize.Bytes(memoryStat.Cached)
|
response["memory_cached"] = humanize.Bytes(memoryStat.Cached)
|
||||||
response["memory_free"] = humanize.Bytes(memoryStat.Free)
|
response["memory_free"] = humanize.Bytes(memoryStat.Free)
|
||||||
|
response["memory_swap_used"] = humanize.Bytes(memoryStat.SwapTotal - memoryStat.SwapFree)
|
||||||
|
response["memory_swap_total"] = humanize.Bytes(memoryStat.SwapTotal)
|
||||||
|
response["memory_swap_cached"] = humanize.Bytes(memoryStat.SwapCached)
|
||||||
|
response["memory_swap_percent"] = float64(memoryStat.SwapFree) / math.Max(float64(memoryStat.SwapTotal), 1)
|
||||||
|
|
||||||
response["memory_pressure"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f", memoryStat.UsedPercent), 64)
|
response["memory_pressure"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f", memoryStat.UsedPercent), 64)
|
||||||
|
|
||||||
|
@ -83,8 +88,16 @@ func Analytic(c *gin.Context) {
|
||||||
response["disk_total"] = humanize.Bytes(diskUsage.Total)
|
response["disk_total"] = humanize.Bytes(diskUsage.Total)
|
||||||
response["disk_percentage"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f", diskUsage.UsedPercent), 64)
|
response["disk_percentage"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f", diskUsage.UsedPercent), 64)
|
||||||
|
|
||||||
|
response["diskIO"] = gin.H{
|
||||||
|
"writes": tool.DiskWriteBuffer[len(tool.DiskWriteBuffer)-1],
|
||||||
|
"reads": tool.DiskReadBuffer[len(tool.DiskReadBuffer)-1],
|
||||||
|
}
|
||||||
|
|
||||||
network, _ := net.IOCounters(false)
|
network, _ := net.IOCounters(false)
|
||||||
response["network"] = network
|
|
||||||
|
if len(network) > 0 {
|
||||||
|
response["network"] = network[0]
|
||||||
|
}
|
||||||
|
|
||||||
m, _ := json.Marshal(response)
|
m, _ := json.Marshal(response)
|
||||||
message = m
|
message = m
|
||||||
|
@ -99,9 +112,30 @@ func Analytic(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCpuUsageRecord(c *gin.Context) {
|
func GetAnalyticInit(c *gin.Context) {
|
||||||
|
cpuInfo, _ := cpu.Info()
|
||||||
|
network, _ := net.IOCounters(false)
|
||||||
|
var _net net.IOCountersStat
|
||||||
|
if len(network) > 0 {
|
||||||
|
_net = network[0]
|
||||||
|
}
|
||||||
|
hostInfo, _ := host.Info()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"user": tool.CpuUserBuffer,
|
"host": hostInfo,
|
||||||
"total": tool.CpuTotalBuffer,
|
"cpu": gin.H{
|
||||||
|
"info": cpuInfo,
|
||||||
|
"user": tool.CpuUserBuffer,
|
||||||
|
"total": tool.CpuTotalBuffer,
|
||||||
|
},
|
||||||
|
"network": gin.H{
|
||||||
|
"init": _net,
|
||||||
|
"bytesRecv": tool.NetRecvBuffer,
|
||||||
|
"bytesSent": tool.NetSentBuffer,
|
||||||
|
},
|
||||||
|
"diskIO": gin.H{
|
||||||
|
"writes": tool.DiskWriteBuffer,
|
||||||
|
"reads": tool.DiskReadBuffer,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func InitRouter() *gin.Engine {
|
||||||
g := g.Group("/", authRequired())
|
g := g.Group("/", authRequired())
|
||||||
{
|
{
|
||||||
g.GET("/analytic", api.Analytic)
|
g.GET("/analytic", api.Analytic)
|
||||||
g.GET("/analytic/cpu", api.GetCpuUsageRecord)
|
g.GET("/analytic/init", api.GetAnalyticInit)
|
||||||
|
|
||||||
g.GET("/users", api.GetUsers)
|
g.GET("/users", api.GetUsers)
|
||||||
g.GET("/user/:id", api.GetUser)
|
g.GET("/user/:id", api.GetUser)
|
||||||
|
|
|
@ -15,6 +15,7 @@ type Server struct {
|
||||||
HTTPChallengePort string
|
HTTPChallengePort string
|
||||||
Email string
|
Email string
|
||||||
Database string
|
Database string
|
||||||
|
DiskName string
|
||||||
}
|
}
|
||||||
|
|
||||||
var ServerSettings = &Server{
|
var ServerSettings = &Server{
|
||||||
|
@ -22,6 +23,7 @@ var ServerSettings = &Server{
|
||||||
RunMode: "debug",
|
RunMode: "debug",
|
||||||
HTTPChallengePort: "9180",
|
HTTPChallengePort: "9180",
|
||||||
Database: "database",
|
Database: "database",
|
||||||
|
DiskName: "vda",
|
||||||
}
|
}
|
||||||
|
|
||||||
var ConfPath string
|
var ConfPath string
|
||||||
|
|
124
server/tool/analytic.go
Normal file
124
server/tool/analytic.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package tool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type usage struct {
|
||||||
|
Time time.Time `json:"x"`
|
||||||
|
Usage float64 `json:"y"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var CpuUserBuffer []usage
|
||||||
|
var CpuTotalBuffer []usage
|
||||||
|
var NetRecvBuffer []usage
|
||||||
|
var NetSentBuffer []usage
|
||||||
|
var DiskWriteBuffer []usage
|
||||||
|
var DiskReadBuffer []usage
|
||||||
|
|
||||||
|
var LastDiskWrites uint64
|
||||||
|
var LastDiskReads uint64
|
||||||
|
|
||||||
|
var LastNetRecv uint64
|
||||||
|
var LastNetSent uint64
|
||||||
|
|
||||||
|
func RecordServerAnalytic() {
|
||||||
|
network, _ := net.IOCounters(false)
|
||||||
|
diskIOCounters, _ := disk.IOCounters(settings.ServerSettings.DiskName)
|
||||||
|
diskIO, ok := diskIOCounters[settings.ServerSettings.DiskName]
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
LastDiskWrites = diskIO.WriteCount
|
||||||
|
LastDiskReads = diskIO.ReadCount
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(network) > 0 {
|
||||||
|
LastNetRecv = network[0].BytesRecv
|
||||||
|
LastNetSent = network[0].BytesSent
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
// 初始化记录数组
|
||||||
|
for i := 100; i > 0; i-- {
|
||||||
|
u := usage{Time: now.Add(time.Duration(-i) * time.Second)}
|
||||||
|
CpuUserBuffer = append(CpuUserBuffer, u)
|
||||||
|
CpuTotalBuffer = append(CpuTotalBuffer, u)
|
||||||
|
NetRecvBuffer = append(NetRecvBuffer, u)
|
||||||
|
NetSentBuffer = append(NetSentBuffer, u)
|
||||||
|
DiskWriteBuffer = append(DiskWriteBuffer, u)
|
||||||
|
DiskReadBuffer = append(DiskReadBuffer, u)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
cpuTimesBefore, _ := cpu.Times(false)
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
cpuTimesAfter, _ := cpu.Times(false)
|
||||||
|
threadNum := runtime.GOMAXPROCS(0)
|
||||||
|
|
||||||
|
cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
|
||||||
|
cpuUserUsage *= 100
|
||||||
|
cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
|
||||||
|
cpuSystemUsage *= 100
|
||||||
|
now := time.Now()
|
||||||
|
u := usage{
|
||||||
|
Time: now,
|
||||||
|
Usage: cpuUserUsage,
|
||||||
|
}
|
||||||
|
CpuUserBuffer = append(CpuUserBuffer, u)
|
||||||
|
s := usage{
|
||||||
|
Time: now,
|
||||||
|
Usage: cpuUserUsage + cpuSystemUsage,
|
||||||
|
}
|
||||||
|
CpuTotalBuffer = append(CpuTotalBuffer, s)
|
||||||
|
if len(CpuUserBuffer) > 100 {
|
||||||
|
CpuUserBuffer = CpuUserBuffer[1:]
|
||||||
|
}
|
||||||
|
if len(CpuTotalBuffer) > 100 {
|
||||||
|
CpuTotalBuffer = CpuTotalBuffer[1:]
|
||||||
|
}
|
||||||
|
network, _ = net.IOCounters(false)
|
||||||
|
if len(network) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
NetRecvBuffer = append(NetRecvBuffer, usage{
|
||||||
|
Time: now,
|
||||||
|
Usage: float64(network[0].BytesRecv - LastNetRecv),
|
||||||
|
})
|
||||||
|
NetSentBuffer = append(NetRecvBuffer, usage{
|
||||||
|
Time: now,
|
||||||
|
Usage: float64(network[0].BytesSent - LastNetSent),
|
||||||
|
})
|
||||||
|
LastNetRecv = network[0].BytesRecv
|
||||||
|
LastNetSent = network[0].BytesSent
|
||||||
|
if len(NetRecvBuffer) > 100 {
|
||||||
|
NetRecvBuffer = NetRecvBuffer[1:]
|
||||||
|
}
|
||||||
|
if len(NetSentBuffer) > 100 {
|
||||||
|
NetSentBuffer = NetSentBuffer[1:]
|
||||||
|
}
|
||||||
|
diskIOCounters, _ = disk.IOCounters(settings.ServerSettings.DiskName)
|
||||||
|
diskIO, ok = diskIOCounters[settings.ServerSettings.DiskName]
|
||||||
|
if ok {
|
||||||
|
DiskReadBuffer = append(DiskReadBuffer, usage{
|
||||||
|
Time: now,
|
||||||
|
Usage: float64(diskIO.ReadCount - LastDiskReads),
|
||||||
|
})
|
||||||
|
DiskWriteBuffer = append(DiskWriteBuffer, usage{
|
||||||
|
Time: now,
|
||||||
|
Usage: float64(diskIO.WriteCount - LastDiskWrites),
|
||||||
|
})
|
||||||
|
if len(DiskReadBuffer) > 100 {
|
||||||
|
DiskReadBuffer = DiskReadBuffer[1:]
|
||||||
|
}
|
||||||
|
if len(DiskWriteBuffer) > 100 {
|
||||||
|
DiskWriteBuffer = DiskWriteBuffer[1:]
|
||||||
|
}
|
||||||
|
LastDiskWrites = diskIO.WriteCount
|
||||||
|
LastDiskReads = diskIO.ReadCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
package tool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cpuUsage struct {
|
|
||||||
Time time.Time `json:"x"`
|
|
||||||
Usage float64 `json:"y"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var CpuUserBuffer []cpuUsage
|
|
||||||
var CpuTotalBuffer []cpuUsage
|
|
||||||
|
|
||||||
func RecordCpuUsage() {
|
|
||||||
for {
|
|
||||||
cpuTimesBefore, _ := cpu.Times(false)
|
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
cpuTimesAfter, _ := cpu.Times(false)
|
|
||||||
threadNum := runtime.GOMAXPROCS(0)
|
|
||||||
|
|
||||||
cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
|
|
||||||
cpuUserUsage *= 100
|
|
||||||
cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
|
|
||||||
cpuSystemUsage *= 100
|
|
||||||
now := time.Now()
|
|
||||||
u := cpuUsage{
|
|
||||||
Time: now,
|
|
||||||
Usage: cpuUserUsage,
|
|
||||||
}
|
|
||||||
CpuUserBuffer = append(CpuUserBuffer, u)
|
|
||||||
s := cpuUsage{
|
|
||||||
Time: now,
|
|
||||||
Usage: cpuUserUsage + cpuSystemUsage,
|
|
||||||
}
|
|
||||||
CpuTotalBuffer = append(CpuTotalBuffer, s)
|
|
||||||
if len(CpuUserBuffer) > 200 {
|
|
||||||
CpuUserBuffer = CpuUserBuffer[1:]
|
|
||||||
}
|
|
||||||
if len(CpuTotalBuffer) > 200 {
|
|
||||||
CpuTotalBuffer = CpuTotalBuffer[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue