summaryrefslogtreecommitdiff
path: root/src/client/app/admin
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2019-02-08 16:58:57 +0900
committerGitHub <noreply@github.com>2019-02-08 16:58:57 +0900
commit56275bcfcbd1366b0e96b79915e810baed0548bb (patch)
treeb35c8a6bd15ef8046c00119f0271808970f4f939 /src/client/app/admin
parentSupress logs during test (diff)
downloadsharkey-56275bcfcbd1366b0e96b79915e810baed0548bb.tar.gz
sharkey-56275bcfcbd1366b0e96b79915e810baed0548bb.tar.bz2
sharkey-56275bcfcbd1366b0e96b79915e810baed0548bb.zip
Introduce per-instance chart (#4183)
* Introduce per-instance chart * Implement chart view in client * Handle note deleting * More chart srcs * Add drive stats * Improve drive stats * Fix bug * Add icon
Diffstat (limited to 'src/client/app/admin')
-rw-r--r--src/client/app/admin/views/federation.vue275
1 files changed, 272 insertions, 3 deletions
diff --git a/src/client/app/admin/views/federation.vue b/src/client/app/admin/views/federation.vue
index dd8567243a..8b0e9ba45a 100644
--- a/src/client/app/admin/views/federation.vue
+++ b/src/client/app/admin/views/federation.vue
@@ -41,6 +41,29 @@
</ui-input>
<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
<details>
+ <summary>{{ $t('charts') }}</summary>
+ <ui-horizon-group inputs>
+ <ui-select v-model="chartSrc">
+ <option value="requests">{{ $t('chart-srcs.requests') }}</option>
+ <option value="users">{{ $t('chart-srcs.users') }}</option>
+ <option value="users-total">{{ $t('chart-srcs.users-total') }}</option>
+ <option value="notes">{{ $t('chart-srcs.notes') }}</option>
+ <option value="notes-total">{{ $t('chart-srcs.notes-total') }}</option>
+ <option value="ff">{{ $t('chart-srcs.ff') }}</option>
+ <option value="ff-total">{{ $t('chart-srcs.ff-total') }}</option>
+ <option value="drive-usage">{{ $t('chart-srcs.drive-usage') }}</option>
+ <option value="drive-usage-total">{{ $t('chart-srcs.drive-usage-total') }}</option>
+ <option value="drive-files">{{ $t('chart-srcs.drive-files') }}</option>
+ <option value="drive-files-total">{{ $t('chart-srcs.drive-files-total') }}</option>
+ </ui-select>
+ <ui-select v-model="chartSpan">
+ <option value="hour">{{ $t('chart-spans.hour') }}</option>
+ <option value="day">{{ $t('chart-spans.day') }}</option>
+ </ui-select>
+ </ui-horizon-group>
+ <div ref="chart"></div>
+ </details>
+ <details>
<summary>{{ $t('remove-all-following') }}</summary>
<ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
<ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info>
@@ -50,7 +73,7 @@
</ui-card>
<ui-card>
- <div slot="title"><fa :icon="faUsers"/> {{ $t('instances') }}</div>
+ <div slot="title"><fa :icon="faServer"/> {{ $t('instances') }}</div>
<section class="fit-top">
<ui-horizon-group inputs>
<ui-select v-model="sort">
@@ -65,6 +88,10 @@
<option value="+following">{{ $t('sorts.followingDesc') }}</option>
<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
<option value="+followers">{{ $t('sorts.followersDesc') }}</option>
+ <option value="-driveUsage">{{ $t('sorts.driveUsageAsc') }}</option>
+ <option value="+driveUsage">{{ $t('sorts.driveUsageDesc') }}</option>
+ <option value="-driveFiles">{{ $t('sorts.driveFilesAsc') }}</option>
+ <option value="+driveFiles">{{ $t('sorts.driveFilesDesc') }}</option>
</ui-select>
<ui-select v-model="state">
<span slot="label">{{ $t('state') }}</span>
@@ -101,7 +128,13 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../i18n';
-import { faGlobe, faTerminal, faSearch, faMinusCircle } from '@fortawesome/free-solid-svg-icons';
+import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer } from '@fortawesome/free-solid-svg-icons';
+import ApexCharts from 'apexcharts';
+import * as tinycolor from 'tinycolor2';
+
+const chartLimit = 90;
+const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
+const negate = arr => arr.map(x => -x);
export default Vue.extend({
i18n: i18n('admin/views/federation.vue'),
@@ -114,10 +147,42 @@ export default Vue.extend({
state: 'all',
limit: 50,
instances: [],
- faGlobe, faTerminal, faSearch, faMinusCircle
+ chart: null,
+ chartSrc: 'requests',
+ chartSpan: 'hour',
+ chartInstance: null,
+ faGlobe, faTerminal, faSearch, faMinusCircle, faServer
};
},
+ computed: {
+ data(): any {
+ if (this.chart == null) return null;
+ switch (this.chartSrc) {
+ case 'requests': return this.requestsChart();
+ case 'users': return this.usersChart(false);
+ case 'users-total': return this.usersChart(true);
+ case 'notes': return this.notesChart(false);
+ case 'notes-total': return this.notesChart(true);
+ case 'ff': return this.ffChart(false);
+ case 'ff-total': return this.ffChart(true);
+ case 'drive-usage': return this.driveUsageChart(false);
+ case 'drive-usage-total': return this.driveUsageChart(true);
+ case 'drive-files': return this.driveFilesChart(false);
+ case 'drive-files-total': return this.driveFilesChart(true);
+ }
+ },
+
+ stats(): any[] {
+ const stats =
+ this.chartSpan == 'day' ? this.chart.perDay :
+ this.chartSpan == 'hour' ? this.chart.perHour :
+ null;
+
+ return stats;
+ }
+ },
+
watch: {
sort() {
this.fetchInstances();
@@ -126,12 +191,42 @@ export default Vue.extend({
state() {
this.fetchInstances();
},
+
+ async instance() {
+ this.now = new Date();
+
+ const [perHour, perDay] = await Promise.all([
+ this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'hour' }),
+ this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'day' }),
+ ]);
+
+ const chart = {
+ perHour: perHour,
+ perDay: perDay
+ };
+
+ this.chart = chart;
+
+ this.renderChart();
+ },
+
+ chartSrc() {
+ this.renderChart();
+ },
+
+ chartSpan() {
+ this.renderChart();
+ }
},
mounted() {
this.fetchInstances();
},
+ beforeDestroy() {
+ this.chartInstance.destroy();
+ },
+
methods: {
showInstance() {
this.$root.api('federation/show-instance', {
@@ -177,6 +272,180 @@ export default Vue.extend({
isBlocked: this.instance.isBlocked,
});
},
+
+ setSrc(src) {
+ this.chartSrc = src;
+ },
+
+ renderChart() {
+ if (this.chartInstance) {
+ this.chartInstance.destroy();
+ }
+
+ this.chartInstance = new ApexCharts(this.$refs.chart, {
+ chart: {
+ type: 'area',
+ height: 300,
+ animations: {
+ dynamicAnimation: {
+ enabled: false
+ }
+ },
+ toolbar: {
+ show: false
+ },
+ zoom: {
+ enabled: false
+ }
+ },
+ dataLabels: {
+ enabled: false
+ },
+ grid: {
+ clipMarkers: false,
+ borderColor: 'rgba(0, 0, 0, 0.1)'
+ },
+ stroke: {
+ curve: 'straight',
+ width: 2
+ },
+ legend: {
+ labels: {
+ colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
+ },
+ },
+ xaxis: {
+ type: 'datetime',
+ labels: {
+ style: {
+ colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
+ }
+ },
+ axisBorder: {
+ color: 'rgba(0, 0, 0, 0.1)'
+ },
+ axisTicks: {
+ color: 'rgba(0, 0, 0, 0.1)'
+ },
+ },
+ yaxis: {
+ labels: {
+ formatter: this.data.bytes ? v => Vue.filter('bytes')(v, 0) : v => Vue.filter('number')(v),
+ style: {
+ color: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
+ }
+ }
+ },
+ series: this.data.series
+ });
+
+ this.chartInstance.render();
+ },
+
+ getDate(i: number) {
+ const y = this.now.getFullYear();
+ const m = this.now.getMonth();
+ const d = this.now.getDate();
+ const h = this.now.getHours();
+
+ return (
+ this.chartSpan == 'day' ? new Date(y, m, d - i) :
+ this.chartSpan == 'hour' ? new Date(y, m, d, h - i) :
+ null
+ );
+ },
+
+ format(arr) {
+ return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v }));
+ },
+
+ requestsChart(): any {
+ return {
+ series: [{
+ name: 'Incoming',
+ data: this.format(this.stats.requests.received)
+ }, {
+ name: 'Outgoing (succeeded)',
+ data: this.format(this.stats.requests.succeeded)
+ }, {
+ name: 'Outgoing (failed)',
+ data: this.format(this.stats.requests.failed)
+ }]
+ };
+ },
+
+ usersChart(total: boolean): any {
+ return {
+ series: [{
+ name: 'Users',
+ type: 'area',
+ data: this.format(total
+ ? this.stats.users.total
+ : sum(this.stats.users.inc, negate(this.stats.users.dec))
+ )
+ }]
+ };
+ },
+
+ notesChart(total: boolean): any {
+ return {
+ series: [{
+ name: 'Notes',
+ type: 'area',
+ data: this.format(total
+ ? this.stats.notes.total
+ : sum(this.stats.notes.inc, negate(this.stats.notes.dec))
+ )
+ }]
+ };
+ },
+
+ ffChart(total: boolean): any {
+ return {
+ series: [{
+ name: 'Following',
+ type: 'area',
+ data: this.format(total
+ ? this.stats.following.total
+ : sum(this.stats.following.inc, negate(this.stats.following.dec))
+ )
+ }, {
+ name: 'Followers',
+ type: 'area',
+ data: this.format(total
+ ? this.stats.followers.total
+ : sum(this.stats.followers.inc, negate(this.stats.followers.dec))
+ )
+ }]
+ };
+ },
+
+ driveUsageChart(total: boolean): any {
+ return {
+ bytes: true,
+ series: [{
+ name: 'Drive usage',
+ type: 'area',
+ data: this.format(total
+ ? this.stats.drive.totalUsage
+ : sum(this.stats.drive.incUsage, negate(this.stats.drive.decUsage))
+ )
+ }]
+ };
+ },
+
+ driveFilesChart(total: boolean): any {
+ return {
+ series: [{
+ name: 'Drive files',
+ type: 'area',
+ data: this.format(total
+ ? this.stats.drive.totalFiles
+ : sum(this.stats.drive.incFiles, negate(this.stats.drive.decFiles))
+ )
+ }]
+ };
+ },
}
});
</script>