mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-12 22:13:08 +02:00
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…
Reference in a new issue