summaryrefslogtreecommitdiff
path: root/src/client/pages/instance
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2020-02-17 02:21:27 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2020-02-17 02:21:27 +0900
commitf45fb56e15e2925aca192867db0ef4ebb15d1f02 (patch)
tree364e1f7dfceb3c37372f943219ab17dc8c6a63a4 /src/client/pages/instance
parent12.11.0 (diff)
downloadsharkey-f45fb56e15e2925aca192867db0ef4ebb15d1f02.tar.gz
sharkey-f45fb56e15e2925aca192867db0ef4ebb15d1f02.tar.bz2
sharkey-f45fb56e15e2925aca192867db0ef4ebb15d1f02.zip
Improve instance info page
Diffstat (limited to 'src/client/pages/instance')
-rw-r--r--src/client/pages/instance/index.vue674
-rw-r--r--src/client/pages/instance/monitor.vue381
-rw-r--r--src/client/pages/instance/settings.vue413
-rw-r--r--src/client/pages/instance/stats.vue491
4 files changed, 750 insertions, 1209 deletions
diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue
index 5a48232417..db88982330 100644
--- a/src/client/pages/instance/index.vue
+++ b/src/client/pages/instance/index.vue
@@ -1,169 +1,54 @@
<template>
-<div v-if="meta" class="mk-instance-page">
+<div v-if="meta" class="xhexznfu">
<portal to="icon"><fa :icon="faServer"/></portal>
<portal to="title">{{ $t('instance') }}</portal>
- <section class="_card info">
- <div class="_title"><fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
- <div class="_content">
- <mk-input v-model="name">{{ $t('instanceName') }}</mk-input>
- <mk-textarea v-model="description">{{ $t('instanceDescription') }}</mk-textarea>
- <mk-input v-model="iconUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('iconUrl') }}</mk-input>
- <mk-input v-model="bannerUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</mk-input>
- <mk-input v-model="tosUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('tosUrl') }}</mk-input>
- <mk-input v-model="maintainerName">{{ $t('maintainerName') }}</mk-input>
- <mk-input v-model="maintainerEmail" type="email"><template #icon><fa :icon="faEnvelope"/></template>{{ $t('maintainerEmail') }}</mk-input>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
- </div>
- </section>
-
- <section class="_card info">
- <div class="_content">
- <mk-input v-model="maxNoteTextLength" type="number" :save="() => save()" style="margin:0;"><template #icon><fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</mk-input>
- </div>
- <div class="_content">
- <mk-switch v-model="enableLocalTimeline" @change="save()">{{ $t('enableLocalTimeline') }}</mk-switch>
- <mk-switch v-model="enableGlobalTimeline" @change="save()">{{ $t('enableGlobalTimeline') }}</mk-switch>
- <mk-info>{{ $t('disablingTimelinesInfo') }}</mk-info>
- </div>
- </section>
-
- <section class="_card info">
- <div class="_title"><fa :icon="faUser"/> {{ $t('registration') }}</div>
- <div class="_content">
- <mk-switch v-model="enableRegistration" @change="save()">{{ $t('enableRegistration') }}</mk-switch>
- <mk-button v-if="!enableRegistration" @click="invite">{{ $t('invite') }}</mk-button>
- </div>
- </section>
-
- <section class="_card">
- <div class="_title"><fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div>
- <div class="_content">
- <mk-switch v-model="enableRecaptcha">{{ $t('enableRecaptcha') }}</mk-switch>
- <template v-if="enableRecaptcha">
- <mk-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSiteKey') }}</mk-input>
- <mk-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSecretKey') }}</mk-input>
- </template>
- </div>
- <div class="_content" v-if="enableRecaptcha && recaptchaSiteKey">
- <header>{{ $t('preview') }}</header>
- <div ref="recaptcha" style="margin: 16px 0 0 0;" :key="recaptchaSiteKey"></div>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
- </div>
- </section>
-
- <section class="_card">
- <div class="_title"><fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
- <div class="_content">
- <mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
- <template v-if="enableServiceWorker">
- <mk-horizon-group inputs class="fit-bottom">
- <mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
- <mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
- </mk-horizon-group>
- </template>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
- </div>
- </section>
-
- <section class="_card">
- <div class="_title"><fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
- <div class="_content">
- <mk-textarea v-model="pinnedUsers">
- <template #desc>{{ $t('pinnedUsersDescription') }} <button class="_textButton" @click="addPinUser">{{ $t('addUser') }}</button></template>
- </mk-textarea>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
- </div>
- </section>
-
- <section class="_card">
- <div class="_title"><fa :icon="faCloud"/> {{ $t('files') }}</div>
- <div class="_content">
- <mk-switch v-model="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></mk-switch>
- <mk-switch v-model="proxyRemoteFiles">{{ $t('proxyRemoteFiles') }}<template #desc>{{ $t('proxyRemoteFilesDescription') }}</template></mk-switch>
- <mk-input v-model="localDriveCapacityMb" type="number">{{ $t('driveCapacityPerLocalAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
- <mk-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" style="margin-bottom: 0;">{{ $t('driveCapacityPerRemoteAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
- </div>
- </section>
+ <mk-instance-stats style="margin-bottom: var(--margin);"/>
- <section class="_card">
- <div class="_title"><fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
- <div class="_content">
- <mk-input :value="proxyAccount ? proxyAccount.username : null" style="margin: 0;" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></mk-input>
- <mk-button primary @click="chooseProxyAccount">{{ $t('chooseProxyAccount') }}</mk-button>
+ <section class="_card chart">
+ <div class="_title"><fa :icon="faMicrochip"/> {{ $t('cpuAndMemory') }}</div>
+ <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
+ <canvas ref="cpumem"></canvas>
</div>
- </section>
-
- <section class="_card">
- <div class="_title"><fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
- <div class="_content">
- <mk-textarea v-model="blockedHosts">
- <template #desc>{{ $t('blockedInstancesDescription') }}</template>
- </mk-textarea>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ <div class="_content" v-if="serverInfo">
+ <div class="table">
+ <div class="row">
+ <div class="cell"><div class="label">CPU</div>{{ serverInfo.cpu.model }}</div>
+ </div>
+ <div class="row">
+ <div class="cell"><div class="label">MEM total</div>{{ serverInfo.mem.total | bytes }}</div>
+ <div class="cell"><div class="label">MEM used</div>{{ memUsage | bytes }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
+ <div class="cell"><div class="label">MEM free</div>{{ serverInfo.mem.total - memUsage | bytes }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
+ </div>
+ </div>
</div>
</section>
-
- <section class="_card">
- <div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
- <div class="_content">
- <header><fa :icon="faTwitter"/> Twitter</header>
- <mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
- <template v-if="enableTwitterIntegration">
- <mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
- <mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
- <mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
- </template>
- </div>
- <div class="_content">
- <header><fa :icon="faGithub"/> GitHub</header>
- <mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
- <template v-if="enableGithubIntegration">
- <mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
- <mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
- <mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
- </template>
+ <section class="_card chart">
+ <div class="_title"><fa :icon="faHdd"/> {{ $t('disk') }}</div>
+ <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
+ <canvas ref="disk"></canvas>
</div>
- <div class="_content">
- <header><fa :icon="faDiscord"/> Discord</header>
- <mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
- <template v-if="enableDiscordIntegration">
- <mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
- <mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
- <mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
- </template>
- </div>
- <div class="_footer">
- <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ <div class="_content" v-if="serverInfo">
+ <div class="table">
+ <div class="row">
+ <div class="cell"><div class="label">Disk total</div>{{ serverInfo.fs.total | bytes }}</div>
+ <div class="cell"><div class="label">Disk used</div>{{ serverInfo.fs.used | bytes }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
+ <div class="cell"><div class="label">Disk free</div>{{ serverInfo.fs.total - serverInfo.fs.used | bytes }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
+ </div>
+ </div>
</div>
</section>
-
- <section class="_card info">
- <div class="_title"><fa :icon="faInfoCircle"/> {{ $t('instanceInfo') }}</div>
- <div class="_content table" v-if="stats">
- <div><b>{{ $t('users') }}</b><span>{{ stats.originalUsersCount | number }}</span></div>
- <div><b>{{ $t('notes') }}</b><span>{{ stats.originalNotesCount | number }}</span></div>
+ <section class="_card chart">
+ <div class="_title"><fa :icon="faExchangeAlt"/> {{ $t('network') }}</div>
+ <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
+ <canvas ref="net"></canvas>
</div>
- <div class="_content table">
- <div><b>Misskey</b><span>v{{ version }}</span></div>
- </div>
- <div class="_content table" v-if="serverInfo">
- <div><b>Node.js</b><span>{{ serverInfo.node }}</span></div>
- <div><b>PostgreSQL</b><span>v{{ serverInfo.psql }}</span></div>
- <div><b>Redis</b><span>v{{ serverInfo.redis }}</span></div>
+ <div class="_content" v-if="serverInfo">
+ <div class="table">
+ <div class="row">
+ <div class="cell"><div class="label">Interface</div>{{ serverInfo.net.interface }}</div>
+ </div>
+ </div>
</div>
</section>
</div>
@@ -171,18 +56,19 @@
<script lang="ts">
import Vue from 'vue';
-import { faPencilAlt, faShareAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faThumbtack, faUser, faShieldAlt, faKey, faBolt } from '@fortawesome/free-solid-svg-icons';
-import { faTrashAlt, faEnvelope } from '@fortawesome/free-regular-svg-icons';
-import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
-import MkButton from '../../components/ui/button.vue';
-import MkInput from '../../components/ui/input.vue';
-import MkTextarea from '../../components/ui/textarea.vue';
-import MkSwitch from '../../components/ui/switch.vue';
-import MkInfo from '../../components/ui/info.vue';
-import MkUserSelect from '../../components/user-select.vue';
+import { faServer, faExchangeAlt, faMicrochip, faHdd } from '@fortawesome/free-solid-svg-icons';
+import Chart from 'chart.js';
+import MkInstanceStats from '../../components/instance-stats.vue';
import { version, url } from '../../config';
import i18n from '../../i18n';
-import getAcct from '../../../misc/acct/render';
+
+const alpha = (hex, a) => {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
+ const r = parseInt(result[1], 16);
+ const g = parseInt(result[2], 16);
+ const b = parseInt(result[3], 16);
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+};
export default Vue.extend({
i18n,
@@ -194,11 +80,7 @@ export default Vue.extend({
},
components: {
- MkButton,
- MkInput,
- MkTextarea,
- MkSwitch,
- MkInfo,
+ MkInstanceStats,
},
data() {
@@ -207,41 +89,11 @@ export default Vue.extend({
url,
stats: null,
serverInfo: null,
- proxyAccount: null,
- proxyAccountId: null,
- cacheRemoteFiles: false,
- proxyRemoteFiles: false,
- localDriveCapacityMb: 0,
- remoteDriveCapacityMb: 0,
- blockedHosts: '',
- pinnedUsers: '',
- maintainerName: null,
- maintainerEmail: null,
- name: null,
- description: null,
- tosUrl: null,
- bannerUrl: null,
- iconUrl: null,
- maxNoteTextLength: 0,
- enableRegistration: false,
- enableLocalTimeline: false,
- enableGlobalTimeline: false,
- enableRecaptcha: false,
- recaptchaSiteKey: null,
- recaptchaSecretKey: null,
- enableServiceWorker: false,
- swPublicKey: null,
- swPrivateKey: null,
- enableTwitterIntegration: false,
- twitterConsumerKey: null,
- twitterConsumerSecret: null,
- enableGithubIntegration: false,
- githubClientId: null,
- githubClientSecret: null,
- enableDiscordIntegration: false,
- discordClientId: null,
- discordClientSecret: null,
- faPencilAlt, faTwitter, faDiscord, faGithub, faShareAlt, faTrashAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faEnvelope, faThumbtack, faUser, faShieldAlt, faKey, faBolt
+ connection: null,
+ memUsage: 0,
+ chartCpuMem: null,
+ chartNet: null,
+ faServer, faExchangeAlt, faMicrochip, faHdd
}
},
@@ -251,160 +103,308 @@ export default Vue.extend({
},
},
- created() {
- this.name = this.meta.name;
- this.description = this.meta.description;
- this.tosUrl = this.meta.tosUrl;
- this.bannerUrl = this.meta.bannerUrl;
- this.iconUrl = this.meta.iconUrl;
- this.maintainerName = this.meta.maintainerName;
- this.maintainerEmail = this.meta.maintainerEmail;
- this.maxNoteTextLength = this.meta.maxNoteTextLength;
- this.enableRegistration = !this.meta.disableRegistration;
- this.enableLocalTimeline = !this.meta.disableLocalTimeline;
- this.enableGlobalTimeline = !this.meta.disableGlobalTimeline;
- this.enableRecaptcha = this.meta.enableRecaptcha;
- this.recaptchaSiteKey = this.meta.recaptchaSiteKey;
- this.recaptchaSecretKey = this.meta.recaptchaSecretKey;
- this.proxyAccountId = this.meta.proxyAccountId;
- this.cacheRemoteFiles = this.meta.cacheRemoteFiles;
- this.proxyRemoteFiles = this.meta.proxyRemoteFiles;
- this.localDriveCapacityMb = this.meta.driveCapacityPerLocalUserMb;
- this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
- this.blockedHosts = this.meta.blockedHosts.join('\n');
- this.pinnedUsers = this.meta.pinnedUsers.join('\n');
- this.enableServiceWorker = this.meta.enableServiceWorker;
- this.swPublicKey = this.meta.swPublickey;
- this.swPrivateKey = this.meta.swPrivateKey;
- this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
- this.twitterConsumerKey = this.meta.twitterConsumerKey;
- this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
- this.enableGithubIntegration = this.meta.enableGithubIntegration;
- this.githubClientId = this.meta.githubClientId;
- this.githubClientSecret = this.meta.githubClientSecret;
- this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
- this.discordClientId = this.meta.discordClientId;
- this.discordClientSecret = this.meta.discordClientSecret;
+ mounted() {
+ Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
- if (this.proxyAccountId) {
- this.$root.api('users/show', { userId: this.proxyAccountId }).then(proxyAccount => {
- this.proxyAccount = proxyAccount;
- });
- }
+ this.chartCpuMem = new Chart(this.$refs.cpumem, {
+ type: 'line',
+ data: {
+ labels: [],
+ datasets: [{
+ label: 'CPU',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#86b300',
+ backgroundColor: alpha('#86b300', 0.1),
+ data: []
+ }, {
+ label: 'MEM (active)',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#935dbf',
+ backgroundColor: alpha('#935dbf', 0.02),
+ data: []
+ }, {
+ label: 'MEM (used)',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#935dbf',
+ borderDash: [5, 5],
+ fill: false,
+ data: []
+ }]
+ },
+ options: {
+ aspectRatio: 3,
+ layout: {
+ padding: {
+ left: 0,
+ right: 0,
+ top: 8,
+ bottom: 0
+ }
+ },
+ legend: {
+ position: 'bottom',
+ labels: {
+ boxWidth: 16,
+ }
+ },
+ scales: {
+ xAxes: [{
+ gridLines: {
+ display: false
+ },
+ ticks: {
+ display: false
+ }
+ }],
+ yAxes: [{
+ position: 'right',
+ ticks: {
+ display: false,
+ max: 100
+ }
+ }]
+ },
+ tooltips: {
+ intersect: false,
+ mode: 'index',
+ }
+ }
+ });
- this.$root.api('admin/server-info').then(res => {
- this.serverInfo = res;
+ this.chartNet = new Chart(this.$refs.net, {
+ type: 'line',
+ data: {
+ labels: [],
+ datasets: [{
+ label: 'In',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#94a029',
+ backgroundColor: alpha('#94a029', 0.1),
+ data: []
+ }, {
+ label: 'Out',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#ff9156',
+ backgroundColor: alpha('#ff9156', 0.1),
+ data: []
+ }]
+ },
+ options: {
+ aspectRatio: 3,
+ layout: {
+ padding: {
+ left: 0,
+ right: 0,
+ top: 8,
+ bottom: 0
+ }
+ },
+ legend: {
+ position: 'bottom',
+ labels: {
+ boxWidth: 16,
+ }
+ },
+ scales: {
+ xAxes: [{
+ gridLines: {
+ display: false
+ },
+ ticks: {
+ display: false
+ }
+ }],
+ yAxes: [{
+ position: 'right',
+ ticks: {
+ display: false,
+ }
+ }]
+ },
+ tooltips: {
+ intersect: false,
+ mode: 'index',
+ }
+ }
});
- this.$root.api('stats').then(res => {
- this.stats = res;
+ this.chartDisk = new Chart(this.$refs.disk, {
+ type: 'line',
+ data: {
+ labels: [],
+ datasets: [{
+ label: 'Read',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#94a029',
+ backgroundColor: alpha('#94a029', 0.1),
+ data: []
+ }, {
+ label: 'Write',
+ pointRadius: 0,
+ lineTension: 0,
+ borderWidth: 2,
+ borderColor: '#ff9156',
+ backgroundColor: alpha('#ff9156', 0.1),
+ data: []
+ }]
+ },
+ options: {
+ aspectRatio: 3,
+ layout: {
+ padding: {
+ left: 0,
+ right: 0,
+ top: 8,
+ bottom: 0
+ }
+ },
+ legend: {
+ position: 'bottom',
+ labels: {
+ boxWidth: 16,
+ }
+ },
+ scales: {
+ xAxes: [{
+ gridLines: {
+ display: false
+ },
+ ticks: {
+ display: false
+ }
+ }],
+ yAxes: [{
+ position: 'right',
+ ticks: {
+ display: false,
+ }
+ }]
+ },
+ tooltips: {
+ intersect: false,
+ mode: 'index',
+ }
+ }
});
- },
- mounted() {
- const renderRecaptchaPreview = () => {
- if (!(window as any).grecaptcha) return;
- if (!this.$refs.recaptcha) return;
- if (!this.recaptchaSiteKey) return;
- (window as any).grecaptcha.render(this.$refs.recaptcha, {
- sitekey: this.recaptchaSiteKey
+ this.$root.api('admin/server-info', {}).then(res => {
+ this.serverInfo = res;
+
+ this.connection = this.$root.stream.useSharedConnection('serverStats');
+ this.connection.on('stats', this.onStats);
+ this.connection.on('statsLog', this.onStatsLog);
+ this.connection.send('requestLog', {
+ id: Math.random().toString().substr(2, 8),
+ length: 150
});
- };
- window.onRecaotchaLoad = () => {
- renderRecaptchaPreview();
- };
- const head = document.getElementsByTagName('head')[0];
- const script = document.createElement('script');
- script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?onload=onRecaotchaLoad');
- head.appendChild(script);
- this.$watch('enableRecaptcha', () => {
- renderRecaptchaPreview();
- });
- this.$watch('recaptchaSiteKey', () => {
- renderRecaptchaPreview();
});
},
+ beforeDestroy() {
+ this.connection.off('stats', this.onStats);
+ this.connection.off('statsLog', this.onStatsLog);
+ this.connection.dispose();
+ },
+
methods: {
- addPinUser() {
- this.$root.new(MkUserSelect, {}).$once('selected', user => {
- this.pinnedUsers = this.pinnedUsers.trim();
- this.pinnedUsers += '\n@' + getAcct(user);
- this.pinnedUsers = this.pinnedUsers.trim();
- });
- },
+ onStats(stats) {
+ 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);
+ this.memUsage = stats.mem.active;
- chooseProxyAccount() {
- this.$root.new(MkUserSelect, {}).$once('selected', user => {
- this.proxyAccount = user;
- this.proxyAccountId = user.id;
- this.save(true);
- });
+ this.chartCpuMem.data.labels.push('');
+ this.chartCpuMem.data.datasets[0].data.push(cpu);
+ this.chartCpuMem.data.datasets[1].data.push(memActive);
+ this.chartCpuMem.data.datasets[2].data.push(memUsed);
+ this.chartNet.data.labels.push('');
+ this.chartNet.data.datasets[0].data.push(stats.net.rx);
+ this.chartNet.data.datasets[1].data.push(stats.net.tx);
+ this.chartDisk.data.labels.push('');
+ this.chartDisk.data.datasets[0].data.push(stats.fs.r);
+ this.chartDisk.data.datasets[1].data.push(stats.fs.w);
+ if (this.chartCpuMem.data.datasets[0].data.length > 150) {
+ this.chartCpuMem.data.labels.shift();
+ this.chartCpuMem.data.datasets[0].data.shift();
+ this.chartCpuMem.data.datasets[1].data.shift();
+ this.chartCpuMem.data.datasets[2].data.shift();
+ this.chartNet.data.labels.shift();
+ this.chartNet.data.datasets[0].data.shift();
+ this.chartNet.data.datasets[1].data.shift();
+ this.chartDisk.data.labels.shift();
+ this.chartDisk.data.datasets[0].data.shift();
+ this.chartDisk.data.datasets[1].data.shift();
+ }
+ this.chartCpuMem.update();
+ this.chartNet.update();
+ this.chartDisk.update();
},
- save(withDialog = false) {
- this.$root.api('admin/update-meta', {
- name: this.name,
- description: this.description,
- tosUrl: this.tosUrl,
- bannerUrl: this.bannerUrl,
- iconUrl: this.iconUrl,
- maintainerName: this.maintainerName,
- maintainerEmail: this.maintainerEmail,
- maxNoteTextLength: this.maxNoteTextLength,
- disableRegistration: !this.enableRegistration,
- disableLocalTimeline: !this.enableLocalTimeline,
- disableGlobalTimeline: !this.enableGlobalTimeline,
- enableRecaptcha: this.enableRecaptcha,
- recaptchaSiteKey: this.recaptchaSiteKey,
- recaptchaSecretKey: this.recaptchaSecretKey,
- proxyAccountId: this.proxyAccountId,
- cacheRemoteFiles: this.cacheRemoteFiles,
- proxyRemoteFiles: this.proxyRemoteFiles,
- localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
- remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
- blockedHosts: this.blockedHosts.split('\n') || [],
- pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
- enableServiceWorker: this.enableServiceWorker,
- swPublicKey: this.swPublicKey,
- swPrivateKey: this.swPrivateKey,
- enableTwitterIntegration: this.enableTwitterIntegration,
- twitterConsumerKey: this.twitterConsumerKey,
- twitterConsumerSecret: this.twitterConsumerSecret,
- enableGithubIntegration: this.enableGithubIntegration,
- githubClientId: this.githubClientId,
- githubClientSecret: this.githubClientSecret,
- enableDiscordIntegration: this.enableDiscordIntegration,
- discordClientId: this.discordClientId,
- discordClientSecret: this.discordClientSecret,
- }).then(() => {
- this.$store.dispatch('instance/fetch');
- if (withDialog) {
- this.$root.dialog({
- type: 'success',
- iconOnly: true, autoClose: true
- });
- }
- }).catch(e => {
- this.$root.dialog({
- type: 'error',
- text: e
- });
- });
+ onStatsLog(statsLog) {
+ for (const stats of statsLog.reverse()) {
+ this.onStats(stats);
+ }
}
}
});
</script>
<style lang="scss" scoped>
-.mk-instance-page {
- > .info {
- > .table {
- > div {
- display: flex;
+.xhexznfu {
+ > .stats {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ margin: calc(0px - var(--margin) / 2);
+ margin-bottom: calc(var(--margin) / 2);
+
+ > div {
+ flex: 1 0 213px;
+ margin: calc(var(--margin) / 2);
+ box-sizing: border-box;
+ padding: 16px;
+ }
+ }
+
+ > .chart {
+ > ._content {
+ > .table {
+ > .row {
+ display: flex;
+
+ &:not(:last-child) {
+ margin-bottom: 16px;
+
+ @media (max-width: 500px) {
+ margin-bottom: 8px;
+ }
+ }
+
+ > .cell {
+ flex: 1;
+
+ > .label {
+ font-size: 80%;
+ opacity: 0.7;
- > * {
- flex: 1;
+ > .icon {
+ margin-right: 4px;
+ display: none;
+ }
+ }
+ }
}
}
}
diff --git a/src/client/pages/instance/monitor.vue b/src/client/pages/instance/monitor.vue
deleted file mode 100644
index b75755126b..0000000000
--- a/src/client/pages/instance/monitor.vue
+++ /dev/null
@@ -1,381 +0,0 @@
-<template>
-<div class="mk-instance-monitor">
- <section class="_card">
- <div class="_title"><fa :icon="faMicrochip"/> {{ $t('cpuAndMemory') }}</div>
- <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
- <canvas ref="cpumem"></canvas>
- </div>
- <div class="_content" v-if="serverInfo">
- <div class="table">
- <div class="row">
- <div class="cell"><div class="label">CPU</div>{{ serverInfo.cpu.model }}</div>
- </div>
- <div class="row">
- <div class="cell"><div class="label">MEM total</div>{{ serverInfo.mem.total | bytes }}</div>
- <div class="cell"><div class="label">MEM used</div>{{ memUsage | bytes }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
- <div class="cell"><div class="label">MEM free</div>{{ serverInfo.mem.total - memUsage | bytes }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
- </div>
- </div>
- </div>
- </section>
- <section class="_card">
- <div class="_title"><fa :icon="faHdd"/> {{ $t('disk') }}</div>
- <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
- <canvas ref="disk"></canvas>
- </div>
- <div class="_content" v-if="serverInfo">
- <div class="table">
- <div class="row">
- <div class="cell"><div class="label">Disk total</div>{{ serverInfo.fs.total | bytes }}</div>
- <div class="cell"><div class="label">Disk used</div>{{ serverInfo.fs.used | bytes }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
- <div class="cell"><div class="label">Disk free</div>{{ serverInfo.fs.total - serverInfo.fs.used | bytes }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
- </div>
- </div>
- </div>
- </section>
- <section class="_card">
- <div class="_title"><fa :icon="faExchangeAlt"/> {{ $t('network') }}</div>
- <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
- <canvas ref="net"></canvas>
- </div>
- <div class="_content" v-if="serverInfo">
- <div class="table">
- <div class="row">
- <div class="cell"><div class="label">Interface</div>{{ serverInfo.net.interface }}</div>
- </div>
- </div>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { faTachometerAlt, faExchangeAlt, faMicrochip, faHdd } from '@fortawesome/free-solid-svg-icons';
-import Chart from 'chart.js';
-import i18n from '../../i18n';
-
-const alpha = (hex, a) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
-
-export default Vue.extend({
- i18n,
-
- metaInfo() {
- return {
- title: `${this.$t('monitor')} | ${this.$t('instance')}`
- };
- },
-
- components: {
- },
-
- data() {
- return {
- connection: null,
- serverInfo: null,
- memUsage: 0,
- chartCpuMem: null,
- chartNet: null,
- faTachometerAlt, faExchangeAlt, faMicrochip, faHdd
- }
- },
-
- mounted() {
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
-
- this.chartCpuMem = new Chart(this.$refs.cpumem, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'CPU',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#86b300',
- backgroundColor: alpha('#86b300', 0.1),
- data: []
- }, {
- label: 'MEM (active)',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#935dbf',
- backgroundColor: alpha('#935dbf', 0.02),
- data: []
- }, {
- label: 'MEM (used)',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#935dbf',
- borderDash: [5, 5],
- fill: false,
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 0,
- right: 0,
- top: 8,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- ticks: {
- display: false,
- max: 100
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- });
-
- this.chartNet = new Chart(this.$refs.net, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'In',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#94a029',
- backgroundColor: alpha('#94a029', 0.1),
- data: []
- }, {
- label: 'Out',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#ff9156',
- backgroundColor: alpha('#ff9156', 0.1),
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 0,
- right: 0,
- top: 8,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- ticks: {
- display: false,
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- });
-
- this.chartDisk = new Chart(this.$refs.disk, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'Read',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#94a029',
- backgroundColor: alpha('#94a029', 0.1),
- data: []
- }, {
- label: 'Write',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#ff9156',
- backgroundColor: alpha('#ff9156', 0.1),
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 0,
- right: 0,
- top: 8,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- ticks: {
- display: false,
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- });
-
- this.$root.api('admin/server-info', {}).then(res => {
- this.serverInfo = res;
-
- this.connection = this.$root.stream.useSharedConnection('serverStats');
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 150
- });
- });
- },
-
- beforeDestroy() {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- this.connection.dispose();
- },
-
- methods: {
- onStats(stats) {
- 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);
- this.memUsage = stats.mem.active;
-
- this.chartCpuMem.data.labels.push('');
- this.chartCpuMem.data.datasets[0].data.push(cpu);
- this.chartCpuMem.data.datasets[1].data.push(memActive);
- this.chartCpuMem.data.datasets[2].data.push(memUsed);
- this.chartNet.data.labels.push('');
- this.chartNet.data.datasets[0].data.push(stats.net.rx);
- this.chartNet.data.datasets[1].data.push(stats.net.tx);
- this.chartDisk.data.labels.push('');
- this.chartDisk.data.datasets[0].data.push(stats.fs.r);
- this.chartDisk.data.datasets[1].data.push(stats.fs.w);
- if (this.chartCpuMem.data.datasets[0].data.length > 150) {
- this.chartCpuMem.data.labels.shift();
- this.chartCpuMem.data.datasets[0].data.shift();
- this.chartCpuMem.data.datasets[1].data.shift();
- this.chartCpuMem.data.datasets[2].data.shift();
- this.chartNet.data.labels.shift();
- this.chartNet.data.datasets[0].data.shift();
- this.chartNet.data.datasets[1].data.shift();
- this.chartDisk.data.labels.shift();
- this.chartDisk.data.datasets[0].data.shift();
- this.chartDisk.data.datasets[1].data.shift();
- }
- this.chartCpuMem.update();
- this.chartNet.update();
- this.chartDisk.update();
- },
-
- onStatsLog(statsLog) {
- for (const stats of statsLog.reverse()) {
- this.onStats(stats);
- }
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-instance-monitor {
- > section {
- > ._content {
- > .table {
- > .row {
- display: flex;
-
- &:not(:last-child) {
- margin-bottom: 16px;
-
- @media (max-width: 500px) {
- margin-bottom: 8px;
- }
- }
-
- > .cell {
- flex: 1;
-
- > .label {
- font-size: 80%;
- opacity: 0.7;
-
- > .icon {
- margin-right: 4px;
- display: none;
- }
- }
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/instance/settings.vue b/src/client/pages/instance/settings.vue
new file mode 100644
index 0000000000..914565298a
--- /dev/null
+++ b/src/client/pages/instance/settings.vue
@@ -0,0 +1,413 @@
+<template>
+<div v-if="meta" class="yihovjtf">
+ <portal to="icon"><fa :icon="faCog"/></portal>
+ <portal to="title">{{ $t('settings') }}</portal>
+
+ <section class="_card info">
+ <div class="_title"><fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
+ <div class="_content">
+ <mk-input v-model="name">{{ $t('instanceName') }}</mk-input>
+ <mk-textarea v-model="description">{{ $t('instanceDescription') }}</mk-textarea>
+ <mk-input v-model="iconUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('iconUrl') }}</mk-input>
+ <mk-input v-model="bannerUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</mk-input>
+ <mk-input v-model="tosUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('tosUrl') }}</mk-input>
+ <mk-input v-model="maintainerName">{{ $t('maintainerName') }}</mk-input>
+ <mk-input v-model="maintainerEmail" type="email"><template #icon><fa :icon="faEnvelope"/></template>{{ $t('maintainerEmail') }}</mk-input>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card info">
+ <div class="_content">
+ <mk-input v-model="maxNoteTextLength" type="number" :save="() => save()" style="margin:0;"><template #icon><fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</mk-input>
+ </div>
+ <div class="_content">
+ <mk-switch v-model="enableLocalTimeline" @change="save()">{{ $t('enableLocalTimeline') }}</mk-switch>
+ <mk-switch v-model="enableGlobalTimeline" @change="save()">{{ $t('enableGlobalTimeline') }}</mk-switch>
+ <mk-info>{{ $t('disablingTimelinesInfo') }}</mk-info>
+ </div>
+ </section>
+
+ <section class="_card info">
+ <div class="_title"><fa :icon="faUser"/> {{ $t('registration') }}</div>
+ <div class="_content">
+ <mk-switch v-model="enableRegistration" @change="save()">{{ $t('enableRegistration') }}</mk-switch>
+ <mk-button v-if="!enableRegistration" @click="invite">{{ $t('invite') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div>
+ <div class="_content">
+ <mk-switch v-model="enableRecaptcha">{{ $t('enableRecaptcha') }}</mk-switch>
+ <template v-if="enableRecaptcha">
+ <mk-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSiteKey') }}</mk-input>
+ <mk-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSecretKey') }}</mk-input>
+ </template>
+ </div>
+ <div class="_content" v-if="enableRecaptcha && recaptchaSiteKey">
+ <header>{{ $t('preview') }}</header>
+ <div ref="recaptcha" style="margin: 16px 0 0 0;" :key="recaptchaSiteKey"></div>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
+ <div class="_content">
+ <mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
+ <template v-if="enableServiceWorker">
+ <mk-horizon-group inputs class="fit-bottom">
+ <mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
+ <mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
+ </mk-horizon-group>
+ </template>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
+ <div class="_content">
+ <mk-textarea v-model="pinnedUsers">
+ <template #desc>{{ $t('pinnedUsersDescription') }} <button class="_textButton" @click="addPinUser">{{ $t('addUser') }}</button></template>
+ </mk-textarea>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faCloud"/> {{ $t('files') }}</div>
+ <div class="_content">
+ <mk-switch v-model="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></mk-switch>
+ <mk-switch v-model="proxyRemoteFiles">{{ $t('proxyRemoteFiles') }}<template #desc>{{ $t('proxyRemoteFilesDescription') }}</template></mk-switch>
+ <mk-input v-model="localDriveCapacityMb" type="number">{{ $t('driveCapacityPerLocalAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
+ <mk-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" style="margin-bottom: 0;">{{ $t('driveCapacityPerRemoteAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
+ <div class="_content">
+ <mk-input :value="proxyAccount ? proxyAccount.username : null" style="margin: 0;" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></mk-input>
+ <mk-button primary @click="chooseProxyAccount">{{ $t('chooseProxyAccount') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
+ <div class="_content">
+ <mk-textarea v-model="blockedHosts">
+ <template #desc>{{ $t('blockedInstancesDescription') }}</template>
+ </mk-textarea>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card">
+ <div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
+ <div class="_content">
+ <header><fa :icon="faTwitter"/> Twitter</header>
+ <mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
+ <template v-if="enableTwitterIntegration">
+ <mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
+ <mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
+ <mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
+ </template>
+ </div>
+ <div class="_content">
+ <header><fa :icon="faGithub"/> GitHub</header>
+ <mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
+ <template v-if="enableGithubIntegration">
+ <mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
+ <mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
+ <mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
+ </template>
+ </div>
+ <div class="_content">
+ <header><fa :icon="faDiscord"/> Discord</header>
+ <mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
+ <template v-if="enableDiscordIntegration">
+ <mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
+ <mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
+ <mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
+ </template>
+ </div>
+ <div class="_footer">
+ <mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
+ </div>
+ </section>
+
+ <section class="_card info">
+ <div class="_title"><fa :icon="faInfoCircle"/> {{ $t('instanceInfo') }}</div>
+ <div class="_content table" v-if="stats">
+ <div><b>{{ $t('users') }}</b><span>{{ stats.originalUsersCount | number }}</span></div>
+ <div><b>{{ $t('notes') }}</b><span>{{ stats.originalNotesCount | number }}</span></div>
+ </div>
+ <div class="_content table">
+ <div><b>Misskey</b><span>v{{ version }}</span></div>
+ </div>
+ <div class="_content table" v-if="serverInfo">
+ <div><b>Node.js</b><span>{{ serverInfo.node }}</span></div>
+ <div><b>PostgreSQL</b><span>v{{ serverInfo.psql }}</span></div>
+ <div><b>Redis</b><span>v{{ serverInfo.redis }}</span></div>
+ </div>
+ </section>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import { faPencilAlt, faShareAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faThumbtack, faUser, faShieldAlt, faKey, faBolt } from '@fortawesome/free-solid-svg-icons';
+import { faTrashAlt, faEnvelope } from '@fortawesome/free-regular-svg-icons';
+import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
+import MkButton from '../../components/ui/button.vue';
+import MkInput from '../../components/ui/input.vue';
+import MkTextarea from '../../components/ui/textarea.vue';
+import MkSwitch from '../../components/ui/switch.vue';
+import MkInfo from '../../components/ui/info.vue';
+import MkUserSelect from '../../components/user-select.vue';
+import { version, url } from '../../config';
+import i18n from '../../i18n';
+import getAcct from '../../../misc/acct/render';
+
+export default Vue.extend({
+ i18n,
+
+ metaInfo() {
+ return {
+ title: this.$t('instance') as string
+ };
+ },
+
+ components: {
+ MkButton,
+ MkInput,
+ MkTextarea,
+ MkSwitch,
+ MkInfo,
+ },
+
+ data() {
+ return {
+ version,
+ url,
+ stats: null,
+ serverInfo: null,
+ proxyAccount: null,
+ proxyAccountId: null,
+ cacheRemoteFiles: false,
+ proxyRemoteFiles: false,
+ localDriveCapacityMb: 0,
+ remoteDriveCapacityMb: 0,
+ blockedHosts: '',
+ pinnedUsers: '',
+ maintainerName: null,
+ maintainerEmail: null,
+ name: null,
+ description: null,
+ tosUrl: null,
+ bannerUrl: null,
+ iconUrl: null,
+ maxNoteTextLength: 0,
+ enableRegistration: false,
+ enableLocalTimeline: false,
+ enableGlobalTimeline: false,
+ enableRecaptcha: false,
+ recaptchaSiteKey: null,
+ recaptchaSecretKey: null,
+ enableServiceWorker: false,
+ swPublicKey: null,
+ swPrivateKey: null,
+ enableTwitterIntegration: false,
+ twitterConsumerKey: null,
+ twitterConsumerSecret: null,
+ enableGithubIntegration: false,
+ githubClientId: null,
+ githubClientSecret: null,
+ enableDiscordIntegration: false,
+ discordClientId: null,
+ discordClientSecret: null,
+ faPencilAlt, faTwitter, faDiscord, faGithub, faShareAlt, faTrashAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faEnvelope, faThumbtack, faUser, faShieldAlt, faKey, faBolt
+ }
+ },
+
+ computed: {
+ meta() {
+ return this.$store.state.instance.meta;
+ },
+ },
+
+ created() {
+ this.name = this.meta.name;
+ this.description = this.meta.description;
+ this.tosUrl = this.meta.tosUrl;
+ this.bannerUrl = this.meta.bannerUrl;
+ this.iconUrl = this.meta.iconUrl;
+ this.maintainerName = this.meta.maintainerName;
+ this.maintainerEmail = this.meta.maintainerEmail;
+ this.maxNoteTextLength = this.meta.maxNoteTextLength;
+ this.enableRegistration = !this.meta.disableRegistration;
+ this.enableLocalTimeline = !this.meta.disableLocalTimeline;
+ this.enableGlobalTimeline = !this.meta.disableGlobalTimeline;
+ this.enableRecaptcha = this.meta.enableRecaptcha;
+ this.recaptchaSiteKey = this.meta.recaptchaSiteKey;
+ this.recaptchaSecretKey = this.meta.recaptchaSecretKey;
+ this.proxyAccountId = this.meta.proxyAccountId;
+ this.cacheRemoteFiles = this.meta.cacheRemoteFiles;
+ this.proxyRemoteFiles = this.meta.proxyRemoteFiles;
+ this.localDriveCapacityMb = this.meta.driveCapacityPerLocalUserMb;
+ this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
+ this.blockedHosts = this.meta.blockedHosts.join('\n');
+ this.pinnedUsers = this.meta.pinnedUsers.join('\n');
+ this.enableServiceWorker = this.meta.enableServiceWorker;
+ this.swPublicKey = this.meta.swPublickey;
+ this.swPrivateKey = this.meta.swPrivateKey;
+ this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
+ this.twitterConsumerKey = this.meta.twitterConsumerKey;
+ this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
+ this.enableGithubIntegration = this.meta.enableGithubIntegration;
+ this.githubClientId = this.meta.githubClientId;
+ this.githubClientSecret = this.meta.githubClientSecret;
+ this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
+ this.discordClientId = this.meta.discordClientId;
+ this.discordClientSecret = this.meta.discordClientSecret;
+
+ if (this.proxyAccountId) {
+ this.$root.api('users/show', { userId: this.proxyAccountId }).then(proxyAccount => {
+ this.proxyAccount = proxyAccount;
+ });
+ }
+
+ this.$root.api('admin/server-info').then(res => {
+ this.serverInfo = res;
+ });
+
+ this.$root.api('stats').then(res => {
+ this.stats = res;
+ });
+ },
+
+ mounted() {
+ const renderRecaptchaPreview = () => {
+ if (!(window as any).grecaptcha) return;
+ if (!this.$refs.recaptcha) return;
+ if (!this.recaptchaSiteKey) return;
+ (window as any).grecaptcha.render(this.$refs.recaptcha, {
+ sitekey: this.recaptchaSiteKey
+ });
+ };
+ window.onRecaotchaLoad = () => {
+ renderRecaptchaPreview();
+ };
+ const head = document.getElementsByTagName('head')[0];
+ const script = document.createElement('script');
+ script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?onload=onRecaotchaLoad');
+ head.appendChild(script);
+ this.$watch('enableRecaptcha', () => {
+ renderRecaptchaPreview();
+ });
+ this.$watch('recaptchaSiteKey', () => {
+ renderRecaptchaPreview();
+ });
+ },
+
+ methods: {
+ addPinUser() {
+ this.$root.new(MkUserSelect, {}).$once('selected', user => {
+ this.pinnedUsers = this.pinnedUsers.trim();
+ this.pinnedUsers += '\n@' + getAcct(user);
+ this.pinnedUsers = this.pinnedUsers.trim();
+ });
+ },
+
+ chooseProxyAccount() {
+ this.$root.new(MkUserSelect, {}).$once('selected', user => {
+ this.proxyAccount = user;
+ this.proxyAccountId = user.id;
+ this.save(true);
+ });
+ },
+
+ save(withDialog = false) {
+ this.$root.api('admin/update-meta', {
+ name: this.name,
+ description: this.description,
+ tosUrl: this.tosUrl,
+ bannerUrl: this.bannerUrl,
+ iconUrl: this.iconUrl,
+ maintainerName: this.maintainerName,
+ maintainerEmail: this.maintainerEmail,
+ maxNoteTextLength: this.maxNoteTextLength,
+ disableRegistration: !this.enableRegistration,
+ disableLocalTimeline: !this.enableLocalTimeline,
+ disableGlobalTimeline: !this.enableGlobalTimeline,
+ enableRecaptcha: this.enableRecaptcha,
+ recaptchaSiteKey: this.recaptchaSiteKey,
+ recaptchaSecretKey: this.recaptchaSecretKey,
+ proxyAccountId: this.proxyAccountId,
+ cacheRemoteFiles: this.cacheRemoteFiles,
+ proxyRemoteFiles: this.proxyRemoteFiles,
+ localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
+ remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
+ blockedHosts: this.blockedHosts.split('\n') || [],
+ pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
+ enableServiceWorker: this.enableServiceWorker,
+ swPublicKey: this.swPublicKey,
+ swPrivateKey: this.swPrivateKey,
+ enableTwitterIntegration: this.enableTwitterIntegration,
+ twitterConsumerKey: this.twitterConsumerKey,
+ twitterConsumerSecret: this.twitterConsumerSecret,
+ enableGithubIntegration: this.enableGithubIntegration,
+ githubClientId: this.githubClientId,
+ githubClientSecret: this.githubClientSecret,
+ enableDiscordIntegration: this.enableDiscordIntegration,
+ discordClientId: this.discordClientId,
+ discordClientSecret: this.discordClientSecret,
+ }).then(() => {
+ this.$store.dispatch('instance/fetch');
+ if (withDialog) {
+ this.$root.dialog({
+ type: 'success',
+ iconOnly: true, autoClose: true
+ });
+ }
+ }).catch(e => {
+ this.$root.dialog({
+ type: 'error',
+ text: e
+ });
+ });
+ }
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+.yihovjtf {
+ > .info {
+ > .table {
+ > div {
+ display: flex;
+
+ > * {
+ flex: 1;
+ }
+ }
+ }
+ }
+}
+</style>
diff --git a/src/client/pages/instance/stats.vue b/src/client/pages/instance/stats.vue
deleted file mode 100644
index 4883d8c873..0000000000
--- a/src/client/pages/instance/stats.vue
+++ /dev/null
@@ -1,491 +0,0 @@
-<template>
-<div class="mk-instance-stats">
- <section class="_card">
- <div class="_title"><fa :icon="faChartBar"/> {{ $t('statistics') }}</div>
- <div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
- <div class="selects" style="display: flex;">
- <mk-select v-model="chartSrc" style="margin: 0; flex: 1;">
- <optgroup :label="$t('federation')">
- <option value="federation-instances">{{ $t('_charts.federationInstancesIncDec') }}</option>
- <option value="federation-instances-total">{{ $t('_charts.federationInstancesTotal') }}</option>
- </optgroup>
- <optgroup :label="$t('users')">
- <option value="users">{{ $t('_charts.usersIncDec') }}</option>
- <option value="users-total">{{ $t('_charts.usersTotal') }}</option>
- <option value="active-users">{{ $t('_charts.activeUsers') }}</option>
- </optgroup>
- <optgroup :label="$t('notes')">
- <option value="notes">{{ $t('_charts.notesIncDec') }}</option>
- <option value="local-notes">{{ $t('_charts.localNotesIncDec') }}</option>
- <option value="remote-notes">{{ $t('_charts.remoteNotesIncDec') }}</option>
- <option value="notes-total">{{ $t('_charts.notesTotal') }}</option>
- </optgroup>
- <optgroup :label="$t('drive')">
- <option value="drive-files">{{ $t('_charts.filesIncDec') }}</option>
- <option value="drive-files-total">{{ $t('_charts.filesTotal') }}</option>
- <option value="drive">{{ $t('_charts.storageUsageIncDec') }}</option>
- <option value="drive-total">{{ $t('_charts.storageUsageTotal') }}</option>
- </optgroup>
- </mk-select>
- <mk-select v-model="chartSpan" style="margin: 0;">
- <option value="hour">{{ $t('perHour') }}</option>
- <option value="day">{{ $t('perDay') }}</option>
- </mk-select>
- </div>
- <canvas ref="chart"></canvas>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { faChartBar } from '@fortawesome/free-solid-svg-icons';
-import Chart from 'chart.js';
-import i18n from '../../i18n';
-import MkSelect from '../../components/ui/select.vue';
-
-const chartLimit = 90;
-const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
-const negate = arr => arr.map(x => -x);
-const alpha = (hex, a) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
-
-export default Vue.extend({
- i18n,
-
- metaInfo() {
- return {
- title: `${this.$t('statistics')} | ${this.$t('instance')}`
- };
- },
-
- components: {
- MkSelect
- },
-
- data() {
- return {
- now: null,
- chart: null,
- chartInstance: null,
- chartSrc: 'notes',
- chartSpan: 'hour',
- faChartBar
- }
- },
-
- computed: {
- data(): any {
- if (this.chart == null) return null;
- switch (this.chartSrc) {
- case 'federation-instances': return this.federationInstancesChart(false);
- case 'federation-instances-total': return this.federationInstancesChart(true);
- case 'users': return this.usersChart(false);
- case 'users-total': return this.usersChart(true);
- case 'active-users': return this.activeUsersChart();
- case 'notes': return this.notesChart('combined');
- case 'local-notes': return this.notesChart('local');
- case 'remote-notes': return this.notesChart('remote');
- case 'notes-total': return this.notesTotalChart();
- case 'drive': return this.driveChart();
- case 'drive-total': return this.driveTotalChart();
- case 'drive-files': return this.driveFilesChart();
- case 'drive-files-total': return this.driveFilesTotalChart();
- }
- },
-
- stats(): any[] {
- const stats =
- this.chartSpan == 'day' ? this.chart.perDay :
- this.chartSpan == 'hour' ? this.chart.perHour :
- null;
-
- return stats;
- }
- },
-
- watch: {
- chartSrc() {
- this.renderChart();
- },
-
- chartSpan() {
- this.renderChart();
- }
- },
-
- async created() {
- this.now = new Date();
-
- const [perHour, perDay] = await Promise.all([Promise.all([
- this.$root.api('charts/federation', { limit: chartLimit, span: 'hour' }),
- this.$root.api('charts/users', { limit: chartLimit, span: 'hour' }),
- this.$root.api('charts/active-users', { limit: chartLimit, span: 'hour' }),
- this.$root.api('charts/notes', { limit: chartLimit, span: 'hour' }),
- this.$root.api('charts/drive', { limit: chartLimit, span: 'hour' }),
- ]), Promise.all([
- this.$root.api('charts/federation', { limit: chartLimit, span: 'day' }),
- this.$root.api('charts/users', { limit: chartLimit, span: 'day' }),
- this.$root.api('charts/active-users', { limit: chartLimit, span: 'day' }),
- this.$root.api('charts/notes', { limit: chartLimit, span: 'day' }),
- this.$root.api('charts/drive', { limit: 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.chart = chart;
-
- this.renderChart();
- },
-
- methods: {
- renderChart() {
- if (this.chartInstance) {
- this.chartInstance.destroy();
- }
-
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
- this.chartInstance = new Chart(this.$refs.chart, {
- type: 'line',
- data: {
- labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(),
- datasets: this.data.series.map(x => ({
- label: x.name,
- data: x.data.slice().reverse(),
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: x.color,
- backgroundColor: alpha(x.color, 0.1),
- hidden: !!x.hidden
- }))
- },
- options: {
- aspectRatio: 2.5,
- layout: {
- padding: {
- left: 0,
- right: 0,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- ticks: {
- display: false
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- });
- },
-
- getDate(ago: 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 - ago) : new Date(y, m, d, h - ago);
- },
-
- format(arr) {
- return arr;
- },
-
- federationInstancesChart(total: boolean): any {
- return {
- series: [{
- name: 'Instances',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.federation.instance.total
- : sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec))
- )
- }]
- };
- },
-
- notesChart(type: string): any {
- return {
- series: [{
- name: 'All',
- type: 'line',
- color: '#008FFB',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.inc, negate(this.stats.notes.local.dec), this.stats.notes.remote.inc, negate(this.stats.notes.remote.dec))
- : sum(this.stats.notes[type].inc, negate(this.stats.notes[type].dec))
- )
- }, {
- name: 'Renotes',
- type: 'area',
- color: '#00E396',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.diffs.renote, this.stats.notes.remote.diffs.renote)
- : this.stats.notes[type].diffs.renote
- )
- }, {
- name: 'Replies',
- type: 'area',
- color: '#FEB019',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.diffs.reply, this.stats.notes.remote.diffs.reply)
- : this.stats.notes[type].diffs.reply
- )
- }, {
- name: 'Normal',
- type: 'area',
- color: '#FF4560',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.diffs.normal, this.stats.notes.remote.diffs.normal)
- : this.stats.notes[type].diffs.normal
- )
- }]
- };
- },
-
- notesTotalChart(): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.notes.local.total)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.notes.remote.total)
- }]
- };
- },
-
- usersChart(total: boolean): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(total
- ? sum(this.stats.users.local.total, this.stats.users.remote.total)
- : sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
- )
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(total
- ? this.stats.users.local.total
- : sum(this.stats.users.local.inc, negate(this.stats.users.local.dec))
- )
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(total
- ? this.stats.users.remote.total
- : sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
- )
- }]
- };
- },
-
- activeUsersChart(): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.activeUsers.local.count, this.stats.activeUsers.remote.count))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.activeUsers.local.count)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.activeUsers.remote.count)
- }]
- };
- },
-
- driveChart(): any {
- return {
- bytes: true,
- series: [{
- name: 'All',
- type: 'line',
- color: '#008FFB',
- data: this.format(
- sum(
- this.stats.drive.local.incSize,
- negate(this.stats.drive.local.decSize),
- this.stats.drive.remote.incSize,
- negate(this.stats.drive.remote.decSize)
- )
- )
- }, {
- name: 'Local +',
- type: 'area',
- color: '#008FFB',
- data: this.format(this.stats.drive.local.incSize)
- }, {
- name: 'Local -',
- type: 'area',
- color: '#008FFB',
- data: this.format(negate(this.stats.drive.local.decSize))
- }, {
- name: 'Remote +',
- type: 'area',
- color: '#008FFB',
- data: this.format(this.stats.drive.remote.incSize)
- }, {
- name: 'Remote -',
- type: 'area',
- color: '#008FFB',
- data: this.format(negate(this.stats.drive.remote.decSize))
- }]
- };
- },
-
- driveTotalChart(): any {
- return {
- bytes: true,
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.local.totalSize)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.remote.totalSize)
- }]
- };
- },
-
- driveFilesChart(): any {
- return {
- series: [{
- name: 'All',
- type: 'line',
- color: '#008FFB',
- data: this.format(
- sum(
- this.stats.drive.local.incCount,
- negate(this.stats.drive.local.decCount),
- this.stats.drive.remote.incCount,
- negate(this.stats.drive.remote.decCount)
- )
- )
- }, {
- name: 'Local +',
- type: 'area',
- color: '#008FFB',
- data: this.format(this.stats.drive.local.incCount)
- }, {
- name: 'Local -',
- type: 'area',
- color: '#008FFB',
- data: this.format(negate(this.stats.drive.local.decCount))
- }, {
- name: 'Remote +',
- type: 'area',
- color: '#008FFB',
- data: this.format(this.stats.drive.remote.incCount)
- }, {
- name: 'Remote -',
- type: 'area',
- color: '#008FFB',
- data: this.format(negate(this.stats.drive.remote.decCount))
- }]
- };
- },
-
- driveFilesTotalChart(): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.local.totalCount)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.remote.totalCount)
- }]
- };
- },
- }
-});
-</script>