Improve stats view
This commit is contained in:
		| @@ -1,26 +1,53 @@ | ||||
| <template> | ||||
|   <div class="stats"> | ||||
|     <h1>Stats!</h1> | ||||
|     <stats-username /> | ||||
|     <b-card no-body> | ||||
|       <b-tabs card> | ||||
|         <b-tab title="Totals"> | ||||
|           <b-card-body> | ||||
|             <stats-pie statType="total" /> | ||||
|           </b-card-body> | ||||
|         </b-tab> | ||||
|         <b-tab title="Usernames"> | ||||
|           <b-card-body> | ||||
|             <stats-pie statType="username" /> | ||||
|           </b-card-body> | ||||
|         </b-tab> | ||||
|         <b-tab title="Passwords"> | ||||
|           <b-card-body> | ||||
|             <stats-pie statType="password"></stats-pie> | ||||
|           </b-card-body> | ||||
|         </b-tab> | ||||
|         <b-tab title="Countries"> | ||||
|           <b-card-body> | ||||
|             <stats-pie statType="country"></stats-pie> | ||||
|           </b-card-body> | ||||
|         </b-tab> | ||||
|         <b-tab title="IPs"> | ||||
|           <b-card-body> | ||||
|             <stats-pie statType="ip"></stats-pie> | ||||
|           </b-card-body> | ||||
|         </b-tab> | ||||
|       </b-tabs> | ||||
|     </b-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { Component, Prop, Vue } from 'vue-property-decorator'; | ||||
| import StatsUsername from '@/components/StatsUsername.vue'; | ||||
| import StatsPie from '@/components/StatsPie.vue'; | ||||
|  | ||||
| @Component({ | ||||
|   components: { | ||||
|     StatsUsername, | ||||
|     StatsPie, | ||||
|   }, | ||||
| }) | ||||
| export default class Stats extends Vue { | ||||
|   @Prop() private msg!: string; | ||||
| } | ||||
| export default class Stats extends Vue {} | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .stats { | ||||
|   align-content: center; | ||||
|   width: 70%; | ||||
|   margin: 50px; | ||||
| } | ||||
| </style> | ||||
|   | ||||
							
								
								
									
										79
									
								
								web/frontend/src/components/StatsPie.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								web/frontend/src/components/StatsPie.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| <template> | ||||
|   <div class="stats-container" :class="containerClass"> | ||||
|     <h2>{{ title() }}</h2> | ||||
|     <div class="chart-container"> | ||||
|       <canvas id="chart" widht="400" height="400"></canvas> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { Component, Prop, Vue } from 'vue-property-decorator'; | ||||
| import { StatResult } from '@/apiary/apiary'; | ||||
| import axios from 'axios'; | ||||
| import Chart from 'chart.js/auto'; | ||||
| import randomColor from 'randomcolor'; | ||||
|  | ||||
| export type StatType = 'username' | 'password' | 'ip' | 'country' | 'total'; | ||||
|  | ||||
| @Component | ||||
| export default class StatsPie extends Vue { | ||||
|   @Prop() private statType!: StatType; | ||||
|  | ||||
|   stats: StatResult[]; | ||||
|  | ||||
|   constructor() { | ||||
|     super(); | ||||
|     this.stats = []; | ||||
|   } | ||||
|  | ||||
|   title(): string { | ||||
|     if (this.statType === 'total') { | ||||
|       return 'Totals'; | ||||
|     } | ||||
|     return `Top 10 ${this.statType}s`; | ||||
|   } | ||||
|  | ||||
|   containerClass(): string { | ||||
|     return `stats-container-${this.statType}`; | ||||
|   } | ||||
|  | ||||
|   mounted(): void { | ||||
|     const url = `/api/stats?type=${this.statType}&limit=10`; | ||||
|     axios.get<StatResult[]>(url).then((resp) => { | ||||
|       this.stats = resp.data; | ||||
|       this.renderPie(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   renderPie(): void { | ||||
|     const elem = document.getElementById('chart') as HTMLCanvasElement; | ||||
|     const ctx = elem.getContext('2d') as CanvasRenderingContext2D; | ||||
|     const sortedStats = this.stats.sort(); | ||||
|     const values = sortedStats.map((s) => s.count); | ||||
|     const headers = sortedStats.map((s) => s.name); | ||||
|     const colors = sortedStats.map(() => randomColor()); | ||||
|  | ||||
|     const chart = new Chart(ctx, { | ||||
|       type: 'doughnut', | ||||
|       data: { | ||||
|         labels: headers, | ||||
|         options: {}, | ||||
|         datasets: [ | ||||
|           { | ||||
|             data: values, | ||||
|             backgroundColor: colors, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .chart-container { | ||||
|   max-width: 500px; | ||||
|   max-height: 500px; | ||||
| } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user