Improve admin dashboard
This commit is contained in:
parent
f9f2ca51ac
commit
aadd5b95b8
10 changed files with 183 additions and 8 deletions
|
@ -1082,6 +1082,8 @@ admin/views/dashboard.vue:
|
||||||
dashboard: "ダッシュボード"
|
dashboard: "ダッシュボード"
|
||||||
accounts: "アカウント"
|
accounts: "アカウント"
|
||||||
notes: "投稿"
|
notes: "投稿"
|
||||||
|
drive: "ドライブ"
|
||||||
|
instances: "インスタンス"
|
||||||
this-instance: "このインスタンス"
|
this-instance: "このインスタンス"
|
||||||
federated: "連合"
|
federated: "連合"
|
||||||
invite: "招待"
|
invite: "招待"
|
||||||
|
|
113
src/client/app/admin/views/ap-log.vue
Normal file
113
src/client/app/admin/views/ap-log.vue
Normal file
|
@ -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>
|
|
@ -7,6 +7,7 @@
|
||||||
<p><b>Node</b><span>{{ meta.node }}</span></p>
|
<p><b>Node</b><span>{{ meta.node }}</span></p>
|
||||||
<p>藍ちゃかわいい</p>
|
<p>藍ちゃかわいい</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div v-if="stats" class="stats">
|
<div v-if="stats" class="stats">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -34,22 +35,22 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div>%fa:user%</div>
|
<div>%fa:database%</div>
|
||||||
<div>
|
<div>
|
||||||
<span>%i18n:@accounts%</span>
|
<span>%i18n:@drive%</span>
|
||||||
<b>{{ stats.usersCount | number }}</b>
|
<b>{{ stats.driveUsageLocal | bytes }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>%fa:globe% %i18n:@federated%</span>
|
<span>%fa:home% %i18n:@this-instance%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div>%fa:pencil-alt%</div>
|
<div>%fa:hdd R%</div>
|
||||||
<div>
|
<div>
|
||||||
<span>%i18n:@notes%</span>
|
<span>%i18n:@instances%</span>
|
||||||
<b>{{ stats.notesCount | number }}</b>
|
<b>{{ stats.instances | number }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -65,6 +66,10 @@
|
||||||
<div class="cpu-memory">
|
<div class="cpu-memory">
|
||||||
<x-cpu-memory :connection="connection"/>
|
<x-cpu-memory :connection="connection"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ap">
|
||||||
|
<x-ap-log/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -72,11 +77,13 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import XCpuMemory from "./cpu-memory.vue";
|
import XCpuMemory from "./cpu-memory.vue";
|
||||||
import XCharts from "./charts.vue";
|
import XCharts from "./charts.vue";
|
||||||
|
import XApLog from "./ap-log.vue";
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
XCpuMemory,
|
XCpuMemory,
|
||||||
XCharts
|
XCharts,
|
||||||
|
XApLog
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
Vue.filter('bytes', (v, digits = 0) => {
|
Vue.filter('bytes', (v, digits = 0) => {
|
||||||
|
if (v == null) return '?';
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
if (v == 0) return '0';
|
if (v == 0) return '0';
|
||||||
const isMinus = v < 0;
|
const isMinus = v < 0;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
Vue.filter('number', (n) => {
|
Vue.filter('number', (n) => {
|
||||||
|
if (n == null) return 'N/A';
|
||||||
return n.toLocaleString();
|
return n.toLocaleString();
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import perform from '../../../remote/activitypub/perform';
|
||||||
import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person';
|
import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person';
|
||||||
import { toUnicode } from 'punycode';
|
import { toUnicode } from 'punycode';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
|
import { publishApLogStream } from '../../../stream';
|
||||||
|
|
||||||
const log = debug('misskey:queue:inbox');
|
const log = debug('misskey:queue:inbox');
|
||||||
|
|
||||||
|
@ -61,6 +62,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
|
||||||
}) as IRemoteUser;
|
}) as IRemoteUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#region Log
|
||||||
|
publishApLogStream({
|
||||||
|
direction: 'in',
|
||||||
|
activity: activity.type,
|
||||||
|
host: user.host,
|
||||||
|
actor: user.username
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// Update activityの場合は、ここで署名検証/更新処理まで実施して終了
|
// Update activityの場合は、ここで署名検証/更新処理まで実施して終了
|
||||||
if (activity.type === 'Update') {
|
if (activity.type === 'Update') {
|
||||||
if (activity.object && activity.object.type === 'Person') {
|
if (activity.object && activity.object.type === 'Person') {
|
||||||
|
|
|
@ -6,6 +6,7 @@ const crypto = require('crypto');
|
||||||
|
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { ILocalUser } from '../../models/user';
|
import { ILocalUser } from '../../models/user';
|
||||||
|
import { publishApLogStream } from '../../stream';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub:deliver');
|
const log = debug('misskey:activitypub:deliver');
|
||||||
|
|
||||||
|
@ -64,4 +65,13 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso
|
||||||
});
|
});
|
||||||
|
|
||||||
req.end(data);
|
req.end(data);
|
||||||
|
|
||||||
|
//#region Log
|
||||||
|
publishApLogStream({
|
||||||
|
direction: 'out',
|
||||||
|
activity: object.type,
|
||||||
|
host: null,
|
||||||
|
actor: user.username
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
});
|
});
|
||||||
|
|
24
src/server/api/stream/channels/ap-log.ts
Normal file
24
src/server/api/stream/channels/ap-log.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import messaging from './messaging';
|
||||||
import messagingIndex from './messaging-index';
|
import messagingIndex from './messaging-index';
|
||||||
import drive from './drive';
|
import drive from './drive';
|
||||||
import hashtag from './hashtag';
|
import hashtag from './hashtag';
|
||||||
|
import apLog from './ap-log';
|
||||||
import gamesReversi from './games/reversi';
|
import gamesReversi from './games/reversi';
|
||||||
import gamesReversiGame from './games/reversi-game';
|
import gamesReversiGame from './games/reversi-game';
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ export default {
|
||||||
messagingIndex,
|
messagingIndex,
|
||||||
drive,
|
drive,
|
||||||
hashtag,
|
hashtag,
|
||||||
|
apLog,
|
||||||
gamesReversi,
|
gamesReversi,
|
||||||
gamesReversiGame
|
gamesReversiGame
|
||||||
};
|
};
|
||||||
|
|
|
@ -100,6 +100,10 @@ class Publisher {
|
||||||
public publishHashtagStream = (note: any): void => {
|
public publishHashtagStream = (note: any): void => {
|
||||||
this.publish('hashtag', null, note);
|
this.publish('hashtag', null, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public publishApLogStream = (log: any): void => {
|
||||||
|
this.publish('apLog', null, log);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const publisher = new Publisher();
|
const publisher = new Publisher();
|
||||||
|
@ -119,3 +123,4 @@ export const publishLocalTimelineStream = publisher.publishLocalTimelineStream;
|
||||||
export const publishHybridTimelineStream = publisher.publishHybridTimelineStream;
|
export const publishHybridTimelineStream = publisher.publishHybridTimelineStream;
|
||||||
export const publishGlobalTimelineStream = publisher.publishGlobalTimelineStream;
|
export const publishGlobalTimelineStream = publisher.publishGlobalTimelineStream;
|
||||||
export const publishHashtagStream = publisher.publishHashtagStream;
|
export const publishHashtagStream = publisher.publishHashtagStream;
|
||||||
|
export const publishApLogStream = publisher.publishApLogStream;
|
||||||
|
|
Loading…
Reference in a new issue