WIP: Improve admin dashboard
This commit is contained in:
		
							parent
							
								
									fd9c7d525a
								
							
						
					
					
						commit
						b5fe4ba9be
					
				
					 4 changed files with 96 additions and 57 deletions
				
			
		|  | @ -84,7 +84,7 @@ | |||
| 	</div> | ||||
| 
 | ||||
| 	<section class="_card"> | ||||
| 		<div class="_title"><fa :icon="faChartBar"/> {{ $t('statistics') }}</div> | ||||
| 		<div class="_title" style="position: relative;"><fa :icon="faChartBar"/> {{ $t('statistics') }}<button @click="fetchChart" class="_button" style="position: absolute; right: 0; bottom: 0; top: 0; padding: inherit;"><fa :icon="faSync"/></button></div> | ||||
| 		<div class="_content" style="margin-top: -8px;"> | ||||
| 			<div class="selects" style="display: flex;"> | ||||
| 				<mk-select v-model="chartSrc" style="margin: 0; flex: 1;"> | ||||
|  | @ -123,7 +123,7 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { faChartBar, faUser, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faChartBar, faUser, faPencilAlt, faSync } from '@fortawesome/free-solid-svg-icons'; | ||||
| import Chart from 'chart.js'; | ||||
| import MkSelect from './ui/select.vue'; | ||||
| 
 | ||||
|  | @ -171,7 +171,7 @@ export default Vue.extend({ | |||
| 			chartInstance: null, | ||||
| 			chartSrc: 'notes', | ||||
| 			chartSpan: 'hour', | ||||
| 			faChartBar, faUser, faPencilAlt | ||||
| 			faChartBar, faUser, faPencilAlt, faSync | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -220,52 +220,56 @@ export default Vue.extend({ | |||
| 
 | ||||
| 		this.now = new Date(); | ||||
| 
 | ||||
| 		const [perHour, perDay] = await Promise.all([Promise.all([ | ||||
| 			this.$root.api('charts/federation', { limit: this.chartLimit, span: 'hour' }), | ||||
| 			this.$root.api('charts/users', { limit: this.chartLimit, span: 'hour' }), | ||||
| 			this.$root.api('charts/active-users', { limit: this.chartLimit, span: 'hour' }), | ||||
| 			this.$root.api('charts/notes', { limit: this.chartLimit, span: 'hour' }), | ||||
| 			this.$root.api('charts/drive', { limit: this.chartLimit, span: 'hour' }), | ||||
| 		]), Promise.all([ | ||||
| 			this.$root.api('charts/federation', { limit: this.chartLimit, span: 'day' }), | ||||
| 			this.$root.api('charts/users', { limit: this.chartLimit, span: 'day' }), | ||||
| 			this.$root.api('charts/active-users', { limit: this.chartLimit, span: 'day' }), | ||||
| 			this.$root.api('charts/notes', { limit: this.chartLimit, span: 'day' }), | ||||
| 			this.$root.api('charts/drive', { limit: this.chartLimit, span: 'day' }), | ||||
| 		])]); | ||||
| 
 | ||||
| 		const chart = { | ||||
| 			perHour: { | ||||
| 				federation: perHour[0], | ||||
| 				users: perHour[1], | ||||
| 				activeUsers: perHour[2], | ||||
| 				notes: perHour[3], | ||||
| 				drive: perHour[4], | ||||
| 			}, | ||||
| 			perDay: { | ||||
| 				federation: perDay[0], | ||||
| 				users: perDay[1], | ||||
| 				activeUsers: perDay[2], | ||||
| 				notes: perDay[3], | ||||
| 				drive: perDay[4], | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		this.notesLocalWoW = this.info.originalNotesCount - chart.perDay.notes.local.total[7]; | ||||
| 		this.notesLocalDoD = this.info.originalNotesCount - chart.perDay.notes.local.total[1]; | ||||
| 		this.notesRemoteWoW = (this.info.notesCount - this.info.originalNotesCount) - chart.perDay.notes.remote.total[7]; | ||||
| 		this.notesRemoteDoD = (this.info.notesCount - this.info.originalNotesCount) - chart.perDay.notes.remote.total[1]; | ||||
| 		this.usersLocalWoW = this.info.originalUsersCount - chart.perDay.users.local.total[7]; | ||||
| 		this.usersLocalDoD = this.info.originalUsersCount - chart.perDay.users.local.total[1]; | ||||
| 		this.usersRemoteWoW = (this.info.usersCount - this.info.originalUsersCount) - chart.perDay.users.remote.total[7]; | ||||
| 		this.usersRemoteDoD = (this.info.usersCount - this.info.originalUsersCount) - chart.perDay.users.remote.total[1]; | ||||
| 
 | ||||
| 		this.chart = chart; | ||||
| 
 | ||||
| 		this.renderChart(); | ||||
| 		this.fetchChart(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		async fetchChart() { | ||||
| 			const [perHour, perDay] = await Promise.all([Promise.all([ | ||||
| 				this.$root.api('charts/federation', { limit: this.chartLimit, span: 'hour' }), | ||||
| 				this.$root.api('charts/users', { limit: this.chartLimit, span: 'hour' }), | ||||
| 				this.$root.api('charts/active-users', { limit: this.chartLimit, span: 'hour' }), | ||||
| 				this.$root.api('charts/notes', { limit: this.chartLimit, span: 'hour' }), | ||||
| 				this.$root.api('charts/drive', { limit: this.chartLimit, span: 'hour' }), | ||||
| 			]), Promise.all([ | ||||
| 				this.$root.api('charts/federation', { limit: this.chartLimit, span: 'day' }), | ||||
| 				this.$root.api('charts/users', { limit: this.chartLimit, span: 'day' }), | ||||
| 				this.$root.api('charts/active-users', { limit: this.chartLimit, span: 'day' }), | ||||
| 				this.$root.api('charts/notes', { limit: this.chartLimit, span: 'day' }), | ||||
| 				this.$root.api('charts/drive', { limit: this.chartLimit, span: 'day' }), | ||||
| 			])]); | ||||
| 
 | ||||
| 			const chart = { | ||||
| 				perHour: { | ||||
| 					federation: perHour[0], | ||||
| 					users: perHour[1], | ||||
| 					activeUsers: perHour[2], | ||||
| 					notes: perHour[3], | ||||
| 					drive: perHour[4], | ||||
| 				}, | ||||
| 				perDay: { | ||||
| 					federation: perDay[0], | ||||
| 					users: perDay[1], | ||||
| 					activeUsers: perDay[2], | ||||
| 					notes: perDay[3], | ||||
| 					drive: perDay[4], | ||||
| 				} | ||||
| 			}; | ||||
| 
 | ||||
| 			this.notesLocalWoW = this.info.originalNotesCount - chart.perDay.notes.local.total[7]; | ||||
| 			this.notesLocalDoD = this.info.originalNotesCount - chart.perDay.notes.local.total[1]; | ||||
| 			this.notesRemoteWoW = (this.info.notesCount - this.info.originalNotesCount) - chart.perDay.notes.remote.total[7]; | ||||
| 			this.notesRemoteDoD = (this.info.notesCount - this.info.originalNotesCount) - chart.perDay.notes.remote.total[1]; | ||||
| 			this.usersLocalWoW = this.info.originalUsersCount - chart.perDay.users.local.total[7]; | ||||
| 			this.usersLocalDoD = this.info.originalUsersCount - chart.perDay.users.local.total[1]; | ||||
| 			this.usersRemoteWoW = (this.info.usersCount - this.info.originalUsersCount) - chart.perDay.users.remote.total[7]; | ||||
| 			this.usersRemoteDoD = (this.info.usersCount - this.info.originalUsersCount) - chart.perDay.users.remote.total[1]; | ||||
| 
 | ||||
| 			this.chart = chart; | ||||
| 
 | ||||
| 			this.renderChart(); | ||||
| 		}, | ||||
| 
 | ||||
| 		renderChart() { | ||||
| 			if (this.chartInstance) { | ||||
| 				this.chartInstance.destroy(); | ||||
|  |  | |||
|  | @ -2,11 +2,13 @@ | |||
| <div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380], el: resizeBaseEl }"> | ||||
| 	<header v-if="showHeader" ref="header"> | ||||
| 		<div class="title"><slot name="header"></slot></div> | ||||
| 		<slot name="func"></slot> | ||||
| 		<button class="_button" v-if="bodyTogglable" @click="() => showBody = !showBody"> | ||||
| 			<template v-if="showBody"><fa :icon="faAngleUp"/></template> | ||||
| 			<template v-else><fa :icon="faAngleDown"/></template> | ||||
| 		</button> | ||||
| 		<div class="sub"> | ||||
| 			<slot name="func"></slot> | ||||
| 			<button class="_button" v-if="bodyTogglable" @click="() => showBody = !showBody"> | ||||
| 				<template v-if="showBody"><fa :icon="faAngleUp"/></template> | ||||
| 				<template v-else><fa :icon="faAngleDown"/></template> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 	</header> | ||||
| 	<transition name="container-toggle" | ||||
| 		@enter="enter" | ||||
|  | @ -153,14 +155,17 @@ export default Vue.extend({ | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> button { | ||||
| 		> .sub { | ||||
| 			position: absolute; | ||||
| 			z-index: 2; | ||||
| 			top: 0; | ||||
| 			right: 0; | ||||
| 			padding: 0; | ||||
| 			width: 42px; | ||||
| 			height: 100%; | ||||
| 
 | ||||
| 			> button { | ||||
| 				width: 42px; | ||||
| 				height: 100%; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| <template> | ||||
| <mk-container :body-togglable="false"> | ||||
| 	<template #header><slot name="title"></slot></template> | ||||
| 	<template #func><button class="_button" @click="resume" :disabled="!paused"><fa :icon="faPlay"/></button><button class="_button" @click="pause" :disabled="paused"><fa :icon="faPause"/></button></template> | ||||
| 
 | ||||
| 	<div class="_content _table"> | ||||
| 		<div class="_row"> | ||||
| 			<div class="_cell"><div class="_label">Process</div>{{ activeSincePrevTick | number }}</div> | ||||
|  | @ -18,6 +20,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import Chart from 'chart.js'; | ||||
| import { faPlay, faPause } from '@fortawesome/free-solid-svg-icons'; | ||||
| import MkContainer from '../../components/ui/container.vue'; | ||||
| 
 | ||||
| const alpha = (hex, a) => { | ||||
|  | @ -49,6 +52,8 @@ export default Vue.extend({ | |||
| 			active: 0, | ||||
| 			waiting: 0, | ||||
| 			delayed: 0, | ||||
| 			paused: false, | ||||
| 			faPlay, faPause | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -155,6 +160,7 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	methods: { | ||||
| 		onStats(stats) { | ||||
| 			if (this.paused) return; | ||||
| 			this.activeSincePrevTick = stats[this.domain].activeSincePrevTick; | ||||
| 			this.active = stats[this.domain].active; | ||||
| 			this.waiting = stats[this.domain].waiting; | ||||
|  | @ -179,6 +185,14 @@ export default Vue.extend({ | |||
| 				this.onStats(stats); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		pause() { | ||||
| 			this.paused = true; | ||||
| 		}, | ||||
| 
 | ||||
| 		resume() { | ||||
| 			this.paused = false; | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -53,6 +53,7 @@ | |||
| 		<div class="segusily"> | ||||
| 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | ||||
| 				<template #header><fa :icon="faMicrochip"/>{{ $t('cpuAndMemory') }}</template> | ||||
| 				<template #func><button class="_button" @click="resume" :disabled="!paused"><fa :icon="faPlay"/></button><button class="_button" @click="pause" :disabled="paused"><fa :icon="faPause"/></button></template> | ||||
| 
 | ||||
| 				<div class="_content" style="margin-top: -8px; margin-bottom: -12px;"> | ||||
| 					<canvas ref="cpumem"></canvas> | ||||
|  | @ -72,8 +73,10 @@ | |||
| 					</div> | ||||
| 				</div> | ||||
| 			</mk-container> | ||||
| 
 | ||||
| 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | ||||
| 				<template #header><fa :icon="faHdd"/> {{ $t('disk') }}</template> | ||||
| 				<template #func><button class="_button" @click="resume" :disabled="!paused"><fa :icon="faPlay"/></button><button class="_button" @click="pause" :disabled="paused"><fa :icon="faPause"/></button></template> | ||||
| 
 | ||||
| 				<div class="_content" style="margin-top: -8px; margin-bottom: -12px;"> | ||||
| 					<canvas ref="disk"></canvas> | ||||
|  | @ -88,8 +91,10 @@ | |||
| 					</div> | ||||
| 				</div> | ||||
| 			</mk-container> | ||||
| 
 | ||||
| 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | ||||
| 				<template #header><fa :icon="faExchangeAlt"/> {{ $t('network') }}</template> | ||||
| 				<template #func><button class="_button" @click="resume" :disabled="!paused"><fa :icon="faPlay"/></button><button class="_button" @click="pause" :disabled="paused"><fa :icon="faPause"/></button></template> | ||||
| 
 | ||||
| 				<div class="_content" style="margin-top: -8px; margin-bottom: -12px;"> | ||||
| 					<canvas ref="net"></canvas> | ||||
|  | @ -180,7 +185,7 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { faDatabase, faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faPlay, faPause, faDatabase, faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList } from '@fortawesome/free-solid-svg-icons'; | ||||
| import Chart from 'chart.js'; | ||||
| import VueJsonPretty from 'vue-json-pretty'; | ||||
| import MkInstanceStats from '../../components/instance-stats.vue'; | ||||
|  | @ -240,7 +245,8 @@ export default Vue.extend({ | |||
| 			dbInfo: null, | ||||
| 			overviewHeight: '1fr', | ||||
| 			queueHeight: '1fr', | ||||
| 			faDatabase, faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList, | ||||
| 			paused: false, | ||||
| 			faPlay, faPause, faDatabase, faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList, | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -580,6 +586,8 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 
 | ||||
| 		onStats(stats) { | ||||
| 			if (this.paused) return; | ||||
| 
 | ||||
| 			const cpu = (stats.cpu * 100).toFixed(0); | ||||
| 			const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0); | ||||
| 			const memUsed = (stats.mem.used / this.serverInfo.mem.total * 100).toFixed(0); | ||||
|  | @ -616,7 +624,15 @@ export default Vue.extend({ | |||
| 			for (const stats of [...statsLog].reverse()) { | ||||
| 				this.onStats(stats); | ||||
| 			} | ||||
| 		} | ||||
| 		}, | ||||
| 
 | ||||
| 		pause() { | ||||
| 			this.paused = true; | ||||
| 		}, | ||||
| 
 | ||||
| 		resume() { | ||||
| 			this.paused = false; | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue