From aadd5b95b811dbd9d0fce9e622613a148e4ad7da Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Sat, 3 Nov 2018 11:38:00 +0900 Subject: [PATCH] Improve admin dashboard --- locales/ja-JP.yml | 2 + src/client/app/admin/views/ap-log.vue | 113 ++++++++++++++++++ src/client/app/admin/views/dashboard.vue | 23 ++-- src/client/app/common/views/filters/bytes.ts | 1 + src/client/app/common/views/filters/number.ts | 1 + src/queue/processors/http/process-inbox.ts | 10 ++ src/remote/activitypub/request.ts | 10 ++ src/server/api/stream/channels/ap-log.ts | 24 ++++ src/server/api/stream/channels/index.ts | 2 + src/stream.ts | 5 + 10 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 src/client/app/admin/views/ap-log.vue create mode 100644 src/server/api/stream/channels/ap-log.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c1555132d..11866c0fa 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1082,6 +1082,8 @@ admin/views/dashboard.vue: dashboard: "ダッシュボード" accounts: "アカウント" notes: "投稿" + drive: "ドライブ" + instances: "インスタンス" this-instance: "このインスタンス" federated: "連合" invite: "招待" diff --git a/src/client/app/admin/views/ap-log.vue b/src/client/app/admin/views/ap-log.vue new file mode 100644 index 000000000..a26627a90 --- /dev/null +++ b/src/client/app/admin/views/ap-log.vue @@ -0,0 +1,113 @@ +<template> +<div class="hyhctythnmwihguaaapnbrbszsjqxpio"> + <table> + <thead> + <tr> + <th>%fa:exchange-alt% In/Out</th> + <th>%fa:server% Host</th> + <th>%fa:bolt% Activity</th> + <th>%fa:user% Actor</th> + </tr> + </thead> + <tbody> + <tr v-for="log in logs" :key="log.id"> + <td :class="log.direction">{{ log.direction == 'in' ? '<' : '>' }} {{ log.direction }}</td> + <td>{{ log.host }}</td> + <td>{{ log.activity }}</td> + <td>@{{ log.actor }}</td> + </tr> + </tbody> + </table> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + data() { + return { + logs: [], + connection: null + }; + }, + + mounted() { + this.connection = (this as any).os.stream.useSharedConnection('apLog'); + this.connection.on('stats', this.onLog); + this.connection.on('statsLog', this.onLogs); + this.connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 50 + }); + + setInterval(() => { + this.onLog({ + direction: ['in', 'out'][Math.floor(Math.random() * 2)], + activity: 'Create', + host: 'misskey.ai', + actor: 'foobar' + }); + }, 1000); + }, + + beforeDestroy() { + this.connection.dispose(); + }, + + methods: { + onLog(log) { + log.id = Math.random(); + this.logs.unshift(log); + if (this.logs.length > 50) this.logs.pop(); + }, + + onLogs(logs) { + logs.reverse().forEach(log => this.onLog(log)); + } + } +}); +</script> + +<style lang="stylus" scoped> +.hyhctythnmwihguaaapnbrbszsjqxpio + display block + padding 16px + height 250px + overflow auto + box-shadow 0 2px 4px rgba(0, 0, 0, 0.1) + background var(--face) + border-radius 8px + + > table + width 100% + max-width 100% + overflow auto + border-spacing 0 + border-collapse collapse + color #555 + + thead + font-weight bold + border-bottom solid 2px #eee + + tr + th + text-align left + + tbody + tr + &:nth-child(odd) + background #fbfbfb + + th, td + padding 8px 16px + min-width 128px + + td.in + color #d26755 + + td.out + color #55bb83 + +</style> diff --git a/src/client/app/admin/views/dashboard.vue b/src/client/app/admin/views/dashboard.vue index 95000d4ba..04a4de874 100644 --- a/src/client/app/admin/views/dashboard.vue +++ b/src/client/app/admin/views/dashboard.vue @@ -7,6 +7,7 @@ <p><b>Node</b><span>{{ meta.node }}</span></p> <p>藍ちゃかわいい</p> </header> + <div v-if="stats" class="stats"> <div> <div> @@ -34,22 +35,22 @@ </div> <div> <div> - <div>%fa:user%</div> + <div>%fa:database%</div> <div> - <span>%i18n:@accounts%</span> - <b>{{ stats.usersCount | number }}</b> + <span>%i18n:@drive%</span> + <b>{{ stats.driveUsageLocal | bytes }}</b> </div> </div> <div> - <span>%fa:globe% %i18n:@federated%</span> + <span>%fa:home% %i18n:@this-instance%</span> </div> </div> <div> <div> - <div>%fa:pencil-alt%</div> + <div>%fa:hdd R%</div> <div> - <span>%i18n:@notes%</span> - <b>{{ stats.notesCount | number }}</b> + <span>%i18n:@instances%</span> + <b>{{ stats.instances | number }}</b> </div> </div> <div> @@ -65,6 +66,10 @@ <div class="cpu-memory"> <x-cpu-memory :connection="connection"/> </div> + + <div class="ap"> + <x-ap-log/> + </div> </div> </template> @@ -72,11 +77,13 @@ import Vue from "vue"; import XCpuMemory from "./cpu-memory.vue"; import XCharts from "./charts.vue"; +import XApLog from "./ap-log.vue"; export default Vue.extend({ components: { XCpuMemory, - XCharts + XCharts, + XApLog }, data() { return { diff --git a/src/client/app/common/views/filters/bytes.ts b/src/client/app/common/views/filters/bytes.ts index f7a1b2690..5b5d966cf 100644 --- a/src/client/app/common/views/filters/bytes.ts +++ b/src/client/app/common/views/filters/bytes.ts @@ -1,6 +1,7 @@ import Vue from 'vue'; Vue.filter('bytes', (v, digits = 0) => { + if (v == null) return '?'; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; if (v == 0) return '0'; const isMinus = v < 0; diff --git a/src/client/app/common/views/filters/number.ts b/src/client/app/common/views/filters/number.ts index d9f48229d..08f9fea80 100644 --- a/src/client/app/common/views/filters/number.ts +++ b/src/client/app/common/views/filters/number.ts @@ -1,5 +1,6 @@ import Vue from 'vue'; Vue.filter('number', (n) => { + if (n == null) return 'N/A'; return n.toLocaleString(); }); diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts index 8e6b3769d..87f0fbfb8 100644 --- a/src/queue/processors/http/process-inbox.ts +++ b/src/queue/processors/http/process-inbox.ts @@ -8,6 +8,7 @@ import perform from '../../../remote/activitypub/perform'; import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person'; import { toUnicode } from 'punycode'; import { URL } from 'url'; +import { publishApLogStream } from '../../../stream'; const log = debug('misskey:queue:inbox'); @@ -61,6 +62,15 @@ export default async (job: bq.Job, done: any): Promise<void> => { }) as IRemoteUser; } + //#region Log + publishApLogStream({ + direction: 'in', + activity: activity.type, + host: user.host, + actor: user.username + }); + //#endregion + // Update activityの場合は、ここで署名検証/更新処理まで実施して終了 if (activity.type === 'Update') { if (activity.object && activity.object.type === 'Person') { diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index 177b6f458..68c53e0c6 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -6,6 +6,7 @@ const crypto = require('crypto'); import config from '../../config'; import { ILocalUser } from '../../models/user'; +import { publishApLogStream } from '../../stream'; const log = debug('misskey:activitypub:deliver'); @@ -64,4 +65,13 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso }); req.end(data); + + //#region Log + publishApLogStream({ + direction: 'out', + activity: object.type, + host: null, + actor: user.username + }); + //#endregion }); diff --git a/src/server/api/stream/channels/ap-log.ts b/src/server/api/stream/channels/ap-log.ts new file mode 100644 index 000000000..dfa1cc702 --- /dev/null +++ b/src/server/api/stream/channels/ap-log.ts @@ -0,0 +1,24 @@ +import autobind from 'autobind-decorator'; +import Channel from '../channel'; + +export default class extends Channel { + public readonly chName = 'apLog'; + public static shouldShare = true; + + @autobind + public async init(params: any) { + // Subscribe events + this.subscriber.on('apLog', this.onLog); + } + + @autobind + private async onLog(log: any) { + this.send('log', log); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('apLog', this.onLog); + } +} diff --git a/src/server/api/stream/channels/index.ts b/src/server/api/stream/channels/index.ts index 7e71590d0..7248579ab 100644 --- a/src/server/api/stream/channels/index.ts +++ b/src/server/api/stream/channels/index.ts @@ -10,6 +10,7 @@ import messaging from './messaging'; import messagingIndex from './messaging-index'; import drive from './drive'; import hashtag from './hashtag'; +import apLog from './ap-log'; import gamesReversi from './games/reversi'; import gamesReversiGame from './games/reversi-game'; @@ -26,6 +27,7 @@ export default { messagingIndex, drive, hashtag, + apLog, gamesReversi, gamesReversiGame }; diff --git a/src/stream.ts b/src/stream.ts index b222a45ca..543421726 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -100,6 +100,10 @@ class Publisher { public publishHashtagStream = (note: any): void => { this.publish('hashtag', null, note); } + + public publishApLogStream = (log: any): void => { + this.publish('apLog', null, log); + } } const publisher = new Publisher(); @@ -119,3 +123,4 @@ export const publishLocalTimelineStream = publisher.publishLocalTimelineStream; export const publishHybridTimelineStream = publisher.publishHybridTimelineStream; export const publishGlobalTimelineStream = publisher.publishGlobalTimelineStream; export const publishHashtagStream = publisher.publishHashtagStream; +export const publishApLogStream = publisher.publishApLogStream;