From 57d680b3fba1208edf67f3fa7db40b421aed5ddf Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 12 Mar 2019 17:11:06 +0900
Subject: [PATCH] Add queue chart

---
 src/client/app/admin/views/queue.vue | 151 +++++++++++++++++++++++----
 1 file changed, 132 insertions(+), 19 deletions(-)

diff --git a/src/client/app/admin/views/queue.vue b/src/client/app/admin/views/queue.vue
index 02bb0a470..a2d1cfcd2 100644
--- a/src/client/app/admin/views/queue.vue
+++ b/src/client/app/admin/views/queue.vue
@@ -2,33 +2,35 @@
 <div>
 	<ui-card>
 		<template #title>{{ $t('operation') }}</template>
-		<section>
+		<section class="wptihjuy">
 			<header>Deliver</header>
-			<ui-horizon-group inputs v-if="stats">
-				<ui-input :value="stats.deliver.waiting | number" type="text" readonly>
+			<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
+				<ui-input :value="latestStats.deliver.waiting | number" type="text" readonly>
 					<span>Waiting</span>
 				</ui-input>
-				<ui-input :value="stats.deliver.delayed | number" type="text" readonly>
+				<ui-input :value="latestStats.deliver.delayed | number" type="text" readonly>
 					<span>Delayed</span>
 				</ui-input>
-				<ui-input :value="stats.deliver.active | number" type="text" readonly>
+				<ui-input :value="latestStats.deliver.active | number" type="text" readonly>
 					<span>Active</span>
 				</ui-input>
 			</ui-horizon-group>
+			<div ref="deliverChart" class="chart"></div>
 		</section>
-		<section>
+		<section class="wptihjuy">
 			<header>Inbox</header>
-			<ui-horizon-group inputs v-if="stats">
-				<ui-input :value="stats.inbox.waiting | number" type="text" readonly>
+			<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
+				<ui-input :value="latestStats.inbox.waiting | number" type="text" readonly>
 					<span>Waiting</span>
 				</ui-input>
-				<ui-input :value="stats.inbox.delayed | number" type="text" readonly>
+				<ui-input :value="latestStats.inbox.delayed | number" type="text" readonly>
 					<span>Delayed</span>
 				</ui-input>
-				<ui-input :value="stats.inbox.active | number" type="text" readonly>
+				<ui-input :value="latestStats.inbox.active | number" type="text" readonly>
 					<span>Active</span>
 				</ui-input>
 			</ui-horizon-group>
+			<div ref="inboxChart" class="chart"></div>
 		</section>
 		<section>
 			<ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button>
@@ -40,29 +42,122 @@
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../i18n';
+import ApexCharts from 'apexcharts';
+import * as tinycolor from 'tinycolor2';
 
 export default Vue.extend({
 	i18n: i18n('admin/views/queue.vue'),
 
 	data() {
 		return {
-			stats: null
+			stats: [],
+			deliverChart: null,
+			inboxChart: null,
 		};
 	},
 
-	created() {
-		const fetchStats = () => {
-			this.$root.api('admin/queue/stats', {}, true).then(stats => {
-				this.stats = stats;
-			});
+	computed: {
+		latestStats(): any {
+			return this.stats[this.stats.length - 1];
+		}
+	},
+
+	watch: {
+		stats(stats) {
+			this.inboxChart.updateSeries([{
+				name: 'Active',
+				data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick }))
+			}, {
+				name: 'Waiting',
+				data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting }))
+			}, {
+				name: 'Delayed',
+				data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
+			}]);
+			this.deliverChart.updateSeries([{
+				name: 'Active',
+				data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick }))
+			}, {
+				name: 'Waiting',
+				data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting }))
+			}, {
+				name: 'Delayed',
+				data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
+			}]);
+		}
+	},
+
+	mounted() {
+		const chartOpts = {
+			chart: {
+				type: 'area',
+				height: 200,
+				animations: {
+					dynamicAnimation: {
+						enabled: false
+					}
+				},
+				toolbar: {
+					show: false
+				},
+				zoom: {
+					enabled: false
+				}
+			},
+			dataLabels: {
+				enabled: false
+			},
+			grid: {
+				clipMarkers: false,
+				borderColor: 'rgba(0, 0, 0, 0.1)'
+			},
+			stroke: {
+				curve: 'straight',
+				width: 2
+			},
+			tooltip: {
+				enabled: false
+			},
+			legend: {
+				labels: {
+					colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
+				},
+			},
+			series: [] as any,
+			colors: ['#00BCD4', '#FFEB3B', '#e53935'],
+			xaxis: {
+				type: 'numeric',
+				labels: {
+					show: false
+				},
+				tooltip: {
+					enabled: false
+				}
+			},
+			yaxis: {
+				show: false,
+				min: 0,
+			}
 		};
 
-		fetchStats();
+		this.inboxChart = new ApexCharts(this.$refs.inboxChart, chartOpts);
+		this.deliverChart = new ApexCharts(this.$refs.deliverChart, chartOpts);
 
-		const clock = setInterval(fetchStats, 1000);
+		this.inboxChart.render();
+		this.deliverChart.render();
+
+		const connection = this.$root.stream.useSharedConnection('queueStats');
+		connection.on('stats', this.onStats);
+		connection.on('statsLog', this.onStatsLog);
+		connection.send('requestLog', {
+			id: Math.random().toString().substr(2, 8),
+			length: 100
+		});
 
 		this.$once('hook:beforeDestroy', () => {
-			clearInterval(clock);
+			connection.dispose();
+			this.inboxChart.destroy();
+			this.deliverChart.destroy();
 		});
 	},
 
@@ -83,6 +178,24 @@ export default Vue.extend({
 				});
 			});
 		},
+
+		onStats(stats) {
+			this.stats.push(stats);
+			if (this.stats.length > 100) this.stats.shift();
+		},
+
+		onStatsLog(statsLog) {
+			for (const stats of statsLog.reverse()) {
+				this.onStats(stats);
+			}
+		}
 	}
 });
 </script>
+
+<style lang="stylus" scoped>
+.wptihjuy
+	> .chart
+		min-height 200px !important
+
+</style>