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> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<section class="_card"> | 	<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="_content" style="margin-top: -8px;"> | ||||||
| 			<div class="selects" style="display: flex;"> | 			<div class="selects" style="display: flex;"> | ||||||
| 				<mk-select v-model="chartSrc" style="margin: 0; flex: 1;"> | 				<mk-select v-model="chartSrc" style="margin: 0; flex: 1;"> | ||||||
|  | @ -123,7 +123,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | 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 Chart from 'chart.js'; | ||||||
| import MkSelect from './ui/select.vue'; | import MkSelect from './ui/select.vue'; | ||||||
| 
 | 
 | ||||||
|  | @ -171,7 +171,7 @@ export default Vue.extend({ | ||||||
| 			chartInstance: null, | 			chartInstance: null, | ||||||
| 			chartSrc: 'notes', | 			chartSrc: 'notes', | ||||||
| 			chartSpan: 'hour', | 			chartSpan: 'hour', | ||||||
| 			faChartBar, faUser, faPencilAlt | 			faChartBar, faUser, faPencilAlt, faSync | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | @ -220,52 +220,56 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 		this.now = new Date(); | 		this.now = new Date(); | ||||||
| 
 | 
 | ||||||
| 		const [perHour, perDay] = await Promise.all([Promise.all([ | 		this.fetchChart(); | ||||||
| 			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(); |  | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	methods: { | 	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() { | 		renderChart() { | ||||||
| 			if (this.chartInstance) { | 			if (this.chartInstance) { | ||||||
| 				this.chartInstance.destroy(); | 				this.chartInstance.destroy(); | ||||||
|  |  | ||||||
|  | @ -2,11 +2,13 @@ | ||||||
| <div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380], el: resizeBaseEl }"> | <div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380], el: resizeBaseEl }"> | ||||||
| 	<header v-if="showHeader" ref="header"> | 	<header v-if="showHeader" ref="header"> | ||||||
| 		<div class="title"><slot name="header"></slot></div> | 		<div class="title"><slot name="header"></slot></div> | ||||||
| 		<slot name="func"></slot> | 		<div class="sub"> | ||||||
| 		<button class="_button" v-if="bodyTogglable" @click="() => showBody = !showBody"> | 			<slot name="func"></slot> | ||||||
| 			<template v-if="showBody"><fa :icon="faAngleUp"/></template> | 			<button class="_button" v-if="bodyTogglable" @click="() => showBody = !showBody"> | ||||||
| 			<template v-else><fa :icon="faAngleDown"/></template> | 				<template v-if="showBody"><fa :icon="faAngleUp"/></template> | ||||||
| 		</button> | 				<template v-else><fa :icon="faAngleDown"/></template> | ||||||
|  | 			</button> | ||||||
|  | 		</div> | ||||||
| 	</header> | 	</header> | ||||||
| 	<transition name="container-toggle" | 	<transition name="container-toggle" | ||||||
| 		@enter="enter" | 		@enter="enter" | ||||||
|  | @ -153,14 +155,17 @@ export default Vue.extend({ | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		> button { | 		> .sub { | ||||||
| 			position: absolute; | 			position: absolute; | ||||||
| 			z-index: 2; | 			z-index: 2; | ||||||
| 			top: 0; | 			top: 0; | ||||||
| 			right: 0; | 			right: 0; | ||||||
| 			padding: 0; |  | ||||||
| 			width: 42px; |  | ||||||
| 			height: 100%; | 			height: 100%; | ||||||
|  | 
 | ||||||
|  | 			> button { | ||||||
|  | 				width: 42px; | ||||||
|  | 				height: 100%; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| <template> | <template> | ||||||
| <mk-container :body-togglable="false"> | <mk-container :body-togglable="false"> | ||||||
| 	<template #header><slot name="title"></slot></template> | 	<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="_content _table"> | ||||||
| 		<div class="_row"> | 		<div class="_row"> | ||||||
| 			<div class="_cell"><div class="_label">Process</div>{{ activeSincePrevTick | number }}</div> | 			<div class="_cell"><div class="_label">Process</div>{{ activeSincePrevTick | number }}</div> | ||||||
|  | @ -18,6 +20,7 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import Chart from 'chart.js'; | import Chart from 'chart.js'; | ||||||
|  | import { faPlay, faPause } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import MkContainer from '../../components/ui/container.vue'; | import MkContainer from '../../components/ui/container.vue'; | ||||||
| 
 | 
 | ||||||
| const alpha = (hex, a) => { | const alpha = (hex, a) => { | ||||||
|  | @ -49,6 +52,8 @@ export default Vue.extend({ | ||||||
| 			active: 0, | 			active: 0, | ||||||
| 			waiting: 0, | 			waiting: 0, | ||||||
| 			delayed: 0, | 			delayed: 0, | ||||||
|  | 			paused: false, | ||||||
|  | 			faPlay, faPause | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | @ -155,6 +160,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 	methods: { | 	methods: { | ||||||
| 		onStats(stats) { | 		onStats(stats) { | ||||||
|  | 			if (this.paused) return; | ||||||
| 			this.activeSincePrevTick = stats[this.domain].activeSincePrevTick; | 			this.activeSincePrevTick = stats[this.domain].activeSincePrevTick; | ||||||
| 			this.active = stats[this.domain].active; | 			this.active = stats[this.domain].active; | ||||||
| 			this.waiting = stats[this.domain].waiting; | 			this.waiting = stats[this.domain].waiting; | ||||||
|  | @ -179,6 +185,14 @@ export default Vue.extend({ | ||||||
| 				this.onStats(stats); | 				this.onStats(stats); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 
 | ||||||
|  | 		pause() { | ||||||
|  | 			this.paused = true; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		resume() { | ||||||
|  | 			this.paused = false; | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -53,6 +53,7 @@ | ||||||
| 		<div class="segusily"> | 		<div class="segusily"> | ||||||
| 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | ||||||
| 				<template #header><fa :icon="faMicrochip"/>{{ $t('cpuAndMemory') }}</template> | 				<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;"> | 				<div class="_content" style="margin-top: -8px; margin-bottom: -12px;"> | ||||||
| 					<canvas ref="cpumem"></canvas> | 					<canvas ref="cpumem"></canvas> | ||||||
|  | @ -72,8 +73,10 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</mk-container> | 			</mk-container> | ||||||
|  | 
 | ||||||
| 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | ||||||
| 				<template #header><fa :icon="faHdd"/> {{ $t('disk') }}</template> | 				<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;"> | 				<div class="_content" style="margin-top: -8px; margin-bottom: -12px;"> | ||||||
| 					<canvas ref="disk"></canvas> | 					<canvas ref="disk"></canvas> | ||||||
|  | @ -88,8 +91,10 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</mk-container> | 			</mk-container> | ||||||
|  | 
 | ||||||
| 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | 			<mk-container :body-togglable="false" :resize-base-el="() => $el"> | ||||||
| 				<template #header><fa :icon="faExchangeAlt"/> {{ $t('network') }}</template> | 				<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;"> | 				<div class="_content" style="margin-top: -8px; margin-bottom: -12px;"> | ||||||
| 					<canvas ref="net"></canvas> | 					<canvas ref="net"></canvas> | ||||||
|  | @ -180,7 +185,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | 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 Chart from 'chart.js'; | ||||||
| import VueJsonPretty from 'vue-json-pretty'; | import VueJsonPretty from 'vue-json-pretty'; | ||||||
| import MkInstanceStats from '../../components/instance-stats.vue'; | import MkInstanceStats from '../../components/instance-stats.vue'; | ||||||
|  | @ -240,7 +245,8 @@ export default Vue.extend({ | ||||||
| 			dbInfo: null, | 			dbInfo: null, | ||||||
| 			overviewHeight: '1fr', | 			overviewHeight: '1fr', | ||||||
| 			queueHeight: '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) { | 		onStats(stats) { | ||||||
|  | 			if (this.paused) return; | ||||||
|  | 
 | ||||||
| 			const cpu = (stats.cpu * 100).toFixed(0); | 			const cpu = (stats.cpu * 100).toFixed(0); | ||||||
| 			const memActive = (stats.mem.active / this.serverInfo.mem.total * 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); | 			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()) { | 			for (const stats of [...statsLog].reverse()) { | ||||||
| 				this.onStats(stats); | 				this.onStats(stats); | ||||||
| 			} | 			} | ||||||
| 		} | 		}, | ||||||
|  | 
 | ||||||
|  | 		pause() { | ||||||
|  | 			this.paused = true; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		resume() { | ||||||
|  | 			this.paused = false; | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue