WIP: Improve admin dashboard

This commit is contained in:
syuilo 2020-08-13 23:02:43 +09:00
parent fd9c7d525a
commit b5fe4ba9be
4 changed files with 96 additions and 57 deletions

View file

@ -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();

View file

@ -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%;
}
} }
} }

View file

@ -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>

View file

@ -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>