ServerAnalytic.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <script setup lang="ts">
  2. import AreaChart from '@/components/Chart/AreaChart.vue'
  3. import RadialBarChart from '@/components/Chart/RadialBarChart.vue'
  4. import {useGettext} from 'vue3-gettext'
  5. import {onMounted, onUnmounted, reactive, ref} from 'vue'
  6. import analytic from '@/api/analytic'
  7. import ws from '@/lib/websocket'
  8. import {bytesToSize} from '@/lib/helper'
  9. import ReconnectingWebSocket from 'reconnecting-websocket'
  10. const {$gettext} = useGettext()
  11. let websocket: ReconnectingWebSocket | WebSocket
  12. const host = reactive({})
  13. const cpu = ref('0.0')
  14. const cpu_info = reactive([])
  15. const cpu_analytic_series = reactive([{name: 'User', data: <any>[]}, {name: 'Total', data: <any>[]}])
  16. const net_analytic = reactive([{name: $gettext('Receive'), data: <any>[]},
  17. {name: $gettext('Send'), data: <any>[]}])
  18. const disk_io_analytic = reactive([{name: $gettext('Writes'), data: <any>[]},
  19. {name: $gettext('Reads'), data: <any>[]}])
  20. const memory = reactive({})
  21. const disk = reactive({})
  22. const disk_io = reactive({writes: 0, reads: 0})
  23. const uptime = ref('')
  24. const loadavg = reactive({})
  25. const net = reactive({recv: 0, sent: 0, last_recv: 0, last_sent: 0})
  26. const net_formatter = (bytes: number) => {
  27. return bytesToSize(bytes) + '/s'
  28. }
  29. interface Usage {
  30. x: number
  31. y: number
  32. }
  33. onMounted(() => {
  34. analytic.init().then(r => {
  35. Object.assign(host, r.host)
  36. Object.assign(cpu_info, r.cpu.info)
  37. Object.assign(memory, r.memory)
  38. Object.assign(disk, r.disk)
  39. // uptime
  40. handle_uptime(r.host?.uptime)
  41. // load_avg
  42. Object.assign(loadavg, r.loadavg)
  43. net.last_recv = r.network.init.bytesRecv
  44. net.last_sent = r.network.init.bytesSent
  45. r.cpu.user.forEach((u: Usage) => {
  46. cpu_analytic_series[0].data.push([u.x, u.y.toFixed(2)])
  47. })
  48. r.cpu.total.forEach((u: Usage) => {
  49. cpu_analytic_series[1].data.push([u.x, u.y.toFixed(2)])
  50. })
  51. r.network.bytesRecv.forEach((u: Usage) => {
  52. net_analytic[0].data.push([u.x, u.y.toFixed(2)])
  53. })
  54. r.network.bytesSent.forEach((u: Usage) => {
  55. net_analytic[1].data.push([u.x, u.y.toFixed(2)])
  56. })
  57. disk_io_analytic[0].data = disk_io_analytic[0].data.concat(r.disk_io.writes)
  58. disk_io_analytic[1].data = disk_io_analytic[1].data.concat(r.disk_io.reads)
  59. websocket = ws('/api/analytic')
  60. websocket.onmessage = wsOnMessage
  61. })
  62. })
  63. onUnmounted(() => {
  64. websocket.close()
  65. })
  66. function handle_uptime(t: number) {
  67. // uptime
  68. let _uptime = Math.floor(t)
  69. let uptime_days = Math.floor(_uptime / 86400)
  70. _uptime -= uptime_days * 86400
  71. let uptime_hours = Math.floor(_uptime / 3600)
  72. _uptime -= uptime_hours * 3600
  73. uptime.value = uptime_days + 'd ' + uptime_hours + 'h ' + Math.floor(_uptime / 60) + 'm'
  74. }
  75. function wsOnMessage(m: { data: any }) {
  76. const r = JSON.parse(m.data)
  77. const cpu_usage = r.cpu.system + r.cpu.user
  78. cpu.value = cpu_usage.toFixed(2)
  79. const time = new Date().getTime()
  80. cpu_analytic_series[0].data.push([time, r.cpu.user.toFixed(2)])
  81. cpu_analytic_series[1].data.push([time, cpu.value])
  82. if (cpu_analytic_series[0].data.length > 100) {
  83. cpu_analytic_series[0].data.shift()
  84. cpu_analytic_series[1].data.shift()
  85. }
  86. // mem
  87. Object.assign(memory, r.memory)
  88. // disk
  89. Object.assign(disk, r.disk)
  90. disk_io.writes = r.disk.writes.y
  91. disk_io.reads = r.disk.reads.y
  92. // uptime
  93. handle_uptime(r.uptime)
  94. // loadavg
  95. Object.assign(loadavg, r.loadavg)
  96. // network
  97. Object.assign(net, r.network)
  98. net.recv = r.network.bytesRecv - net.last_recv
  99. net.sent = r.network.bytesSent - net.last_sent
  100. net.last_recv = r.network.bytesRecv
  101. net.last_sent = r.network.bytesSent
  102. net_analytic[0].data.push([time, net.recv])
  103. net_analytic[1].data.push([time, net.sent])
  104. if (net_analytic[0].data.length > 100) {
  105. net_analytic[0].data.shift()
  106. net_analytic[1].data.shift()
  107. }
  108. disk_io_analytic[0].data.push(r.disk.writes)
  109. disk_io_analytic[1].data.push(r.disk.reads)
  110. if (disk_io_analytic[0].data.length > 100) {
  111. disk_io_analytic[0].data.shift()
  112. disk_io_analytic[1].data.shift()
  113. }
  114. }
  115. </script>
  116. <template>
  117. <div>
  118. <a-row :gutter="[{xs: 0, sm: 16}, 16]" class="first-row">
  119. <a-col :xl="7" :lg="24" :md="24" :xs="24">
  120. <a-card :title="$gettext('Server Info')" :bordered="false">
  121. <p>
  122. <translate>Uptime:</translate>
  123. {{ uptime }}
  124. </p>
  125. <p>
  126. <translate>Load Averages:</translate>
  127. <span class="load-avg-describe"> 1min:</span>{{ ' ' + loadavg?.load1?.toFixed(2) }}
  128. <span class="load-avg-describe"> | 5min:</span>{{ loadavg?.load5?.toFixed(2) }}
  129. <span class="load-avg-describe"> | 15min:</span>{{ loadavg?.load15?.toFixed(2) }}
  130. </p>
  131. <p>
  132. <translate>OS:</translate>
  133. <span class="os-platform">{{ ' ' + host.platform }}</span> {{ host.platformVersion }}
  134. <span class="os-info">({{ host.os }} {{ host.kernelVersion }}
  135. {{ host.kernelArch }})</span>
  136. </p>
  137. <p v-if="cpu_info">
  138. {{ $gettext('CPU:') + ' ' }}
  139. <span class="cpu-model">{{ cpu_info[0]?.modelName || 'core' }}</span>
  140. <span class="cpu-mhz">{{ (cpu_info[0]?.mhz / 1000).toFixed(2) + 'GHz' }}</span>
  141. * {{ cpu_info.length }}
  142. </p>
  143. </a-card>
  144. </a-col>
  145. <a-col :xl="10" :lg="16" :md="24" :xs="24" class="chart_dashboard">
  146. <a-card :title="$gettext('Memory and Storage')" :bordered="false">
  147. <a-row :gutter="[0,16]">
  148. <a-col :xs="24" :sm="24" :md="8">
  149. <radial-bar-chart :name="$gettext('Memory')" :series="[memory.pressure]"
  150. :centerText="memory.used" :bottom-text="memory.total" colors="#36a3eb"/>
  151. </a-col>
  152. <a-col :xs="24" :sm="12" :md="8">
  153. <radial-bar-chart :name="$gettext('Swap')" :series="[memory.swap_percent]"
  154. :centerText="memory.swap_used"
  155. :bottom-text="memory.swap_total" colors="#ff6385"/>
  156. </a-col>
  157. <a-col :xs="24" :sm="12" :md="8">
  158. <radial-bar-chart :name="$gettext('Storage')" :series="[disk.percentage]"
  159. :centerText="disk.used" :bottom-text="disk.total" colors="#87d068"/>
  160. </a-col>
  161. </a-row>
  162. </a-card>
  163. </a-col>
  164. <a-col :xl="7" :lg="8" :sm="24" :xs="24" class="chart_dashboard network-total">
  165. <a-card :title="$gettext('Network Statistics')" :bordered="false">
  166. <a-row :gutter="16">
  167. <a-col :span="12">
  168. <a-statistic :value="bytesToSize(net.last_recv)"
  169. :title="$gettext('Network Total Receive')"/>
  170. </a-col>
  171. <a-col :span="12">
  172. <a-statistic :value="bytesToSize(net.last_sent)"
  173. :title="$gettext('Network Total Send')"/>
  174. </a-col>
  175. </a-row>
  176. </a-card>
  177. </a-col>
  178. </a-row>
  179. <a-row :gutter="[{xs: 0, sm: 16}, 16]" class="row-two">
  180. <a-col :xl="8" :lg="24" :md="24" :sm="24" :xs="24">
  181. <a-card :title="$gettext('CPU Status')" :bordered="false">
  182. <a-statistic :value="cpu" title="CPU">
  183. <template v-slot:suffix>
  184. <span>%</span>
  185. </template>
  186. </a-statistic>
  187. <area-chart :series="cpu_analytic_series" :max="100"/>
  188. </a-card>
  189. </a-col>
  190. <a-col :xl="8" :lg="12" :md="24" :sm="24" :xs="24">
  191. <a-card :title="$gettext('Network')" :bordered="false">
  192. <a-row :gutter="16">
  193. <a-col :span="12">
  194. <a-statistic :value="bytesToSize(net.recv)"
  195. :title="$gettext('Receive')">
  196. <template v-slot:suffix>
  197. <span>/s</span>
  198. </template>
  199. </a-statistic>
  200. </a-col>
  201. <a-col :span="12">
  202. <a-statistic :value="bytesToSize(net.sent)" :title="$gettext('Send')">
  203. <template v-slot:suffix>
  204. <span>/s</span>
  205. </template>
  206. </a-statistic>
  207. </a-col>
  208. </a-row>
  209. <area-chart :series="net_analytic" :y_formatter="net_formatter"/>
  210. </a-card>
  211. </a-col>
  212. <a-col :xl="8" :lg="12" :md="24" :sm="24" :xs="24">
  213. <a-card :title="$gettext('Disk IO')" :bordered="false">
  214. <a-row :gutter="16">
  215. <a-col :span="12">
  216. <a-statistic :value="disk_io.writes"
  217. :title="$gettext('Writes')">
  218. <template v-slot:suffix>
  219. <span>/s</span>
  220. </template>
  221. </a-statistic>
  222. </a-col>
  223. <a-col :span="12">
  224. <a-statistic :value="disk_io.reads" :title="$gettext('Reads')">
  225. <template v-slot:suffix>
  226. <span>/s</span>
  227. </template>
  228. </a-statistic>
  229. </a-col>
  230. </a-row>
  231. <area-chart :series="disk_io_analytic"/>
  232. </a-card>
  233. </a-col>
  234. </a-row>
  235. </div>
  236. </template>
  237. <style lang="less" scoped>
  238. .first-row {
  239. .ant-card {
  240. min-height: 227px;
  241. p {
  242. margin-bottom: 8px;
  243. }
  244. }
  245. margin-bottom: 20px;
  246. }
  247. .ant-card {
  248. .ant-statistic {
  249. margin: 0 0 10px 10px
  250. }
  251. .chart {
  252. max-width: 800px;
  253. max-height: 350px;
  254. }
  255. .chart_dashboard {
  256. padding: 60px;
  257. .description {
  258. width: 120px;
  259. text-align: center
  260. }
  261. }
  262. @media (max-width: 512px) {
  263. margin: 10px 0;
  264. .chart_dashboard {
  265. padding: 20px;
  266. }
  267. }
  268. }
  269. .load-avg-describe {
  270. @media (max-width: 1600px) and (min-width: 1200px) {
  271. display: none;
  272. }
  273. }
  274. .os-info {
  275. @media (max-width: 1600px) and (min-width: 1200px) {
  276. display: none;
  277. }
  278. }
  279. .cpu-model {
  280. @media (max-width: 1790px) and (min-width: 1200px) {
  281. display: none;
  282. }
  283. }
  284. .cpu-mhz {
  285. @media (min-width: 1790px) or (max-width: 1200px) {
  286. display: none;
  287. }
  288. }
  289. </style>