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
This commit is contained in:
parent
f35688bab8
commit
56275bcfcb
17 changed files with 746 additions and 13 deletions
|
@ -1399,11 +1399,31 @@ admin/views/federation.vue:
|
||||||
followingDesc: "フォローが多い順"
|
followingDesc: "フォローが多い順"
|
||||||
followersAsc: "フォロワーが少ない順"
|
followersAsc: "フォロワーが少ない順"
|
||||||
followersDesc: "フォロワーが多い順"
|
followersDesc: "フォロワーが多い順"
|
||||||
|
driveUsageAsc: "ドライブ使用量が少ない順"
|
||||||
|
driveUsageDesc: "ドライブ使用量が多い順"
|
||||||
|
driveFilesAsc: "ドライブのファイル数が少ない順"
|
||||||
|
driveFilesDesc: "ドライブのファイル数が多い順"
|
||||||
state: "状態"
|
state: "状態"
|
||||||
states:
|
states:
|
||||||
all: "すべて"
|
all: "すべて"
|
||||||
blocked: "ブロック"
|
blocked: "ブロック"
|
||||||
result-is-truncated: "上位{n}件を表示しています。"
|
result-is-truncated: "上位{n}件を表示しています。"
|
||||||
|
charts: "チャート"
|
||||||
|
chart-srcs:
|
||||||
|
requests: "リクエスト"
|
||||||
|
users: "ユーザーの増減"
|
||||||
|
users-total: "ユーザーの積算"
|
||||||
|
notes: "投稿の増減"
|
||||||
|
notes-total: "投稿の積算"
|
||||||
|
ff: "フォロー/フォロワーの増減"
|
||||||
|
ff-total: "フォロー/フォロワーの積算"
|
||||||
|
drive-usage: "ドライブ使用量の増減"
|
||||||
|
drive-usage-total: "ドライブ使用量の増減"
|
||||||
|
drive-files: "ドライブファイル数の増減"
|
||||||
|
drive-files-total: "ドライブファイル数の増減"
|
||||||
|
chart-spans:
|
||||||
|
hour: "1時間ごと"
|
||||||
|
day: "1日ごと"
|
||||||
|
|
||||||
desktop/views/pages/welcome.vue:
|
desktop/views/pages/welcome.vue:
|
||||||
about: "詳しく..."
|
about: "詳しく..."
|
||||||
|
|
|
@ -40,6 +40,29 @@
|
||||||
<span>{{ $t('latest-request-received-at') }}</span>
|
<span>{{ $t('latest-request-received-at') }}</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
|
<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>
|
<details>
|
||||||
<summary>{{ $t('remove-all-following') }}</summary>
|
<summary>{{ $t('remove-all-following') }}</summary>
|
||||||
<ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
|
<ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
|
||||||
|
@ -50,7 +73,7 @@
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<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">
|
<section class="fit-top">
|
||||||
<ui-horizon-group inputs>
|
<ui-horizon-group inputs>
|
||||||
<ui-select v-model="sort">
|
<ui-select v-model="sort">
|
||||||
|
@ -65,6 +88,10 @@
|
||||||
<option value="+following">{{ $t('sorts.followingDesc') }}</option>
|
<option value="+following">{{ $t('sorts.followingDesc') }}</option>
|
||||||
<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
|
<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
|
||||||
<option value="+followers">{{ $t('sorts.followersDesc') }}</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>
|
||||||
<ui-select v-model="state">
|
<ui-select v-model="state">
|
||||||
<span slot="label">{{ $t('state') }}</span>
|
<span slot="label">{{ $t('state') }}</span>
|
||||||
|
@ -101,7 +128,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../i18n';
|
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({
|
export default Vue.extend({
|
||||||
i18n: i18n('admin/views/federation.vue'),
|
i18n: i18n('admin/views/federation.vue'),
|
||||||
|
@ -114,10 +147,42 @@ export default Vue.extend({
|
||||||
state: 'all',
|
state: 'all',
|
||||||
limit: 50,
|
limit: 50,
|
||||||
instances: [],
|
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: {
|
watch: {
|
||||||
sort() {
|
sort() {
|
||||||
this.fetchInstances();
|
this.fetchInstances();
|
||||||
|
@ -126,12 +191,42 @@ export default Vue.extend({
|
||||||
state() {
|
state() {
|
||||||
this.fetchInstances();
|
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() {
|
mounted() {
|
||||||
this.fetchInstances();
|
this.fetchInstances();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.chartInstance.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
showInstance() {
|
showInstance() {
|
||||||
this.$root.api('federation/show-instance', {
|
this.$root.api('federation/show-instance', {
|
||||||
|
@ -177,6 +272,180 @@ export default Vue.extend({
|
||||||
isBlocked: this.instance.isBlocked,
|
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>
|
</script>
|
||||||
|
|
|
@ -11,6 +11,7 @@ DriveFile.createIndex('md5');
|
||||||
DriveFile.createIndex('metadata.uri');
|
DriveFile.createIndex('metadata.uri');
|
||||||
DriveFile.createIndex('metadata.userId');
|
DriveFile.createIndex('metadata.userId');
|
||||||
DriveFile.createIndex('metadata.folderId');
|
DriveFile.createIndex('metadata.folderId');
|
||||||
|
DriveFile.createIndex('metadata._user.host');
|
||||||
export default DriveFile;
|
export default DriveFile;
|
||||||
|
|
||||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
||||||
|
|
|
@ -43,6 +43,16 @@ export interface IInstance {
|
||||||
*/
|
*/
|
||||||
followersCount: number;
|
followersCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ドライブ使用量
|
||||||
|
*/
|
||||||
|
driveUsage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ドライブのファイル数
|
||||||
|
*/
|
||||||
|
driveFiles: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 直近のリクエスト送信日時
|
* 直近のリクエスト送信日時
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,6 +17,7 @@ const User = db.get<IUser>('users');
|
||||||
|
|
||||||
User.createIndex('username');
|
User.createIndex('username');
|
||||||
User.createIndex('usernameLower');
|
User.createIndex('usernameLower');
|
||||||
|
User.createIndex('host');
|
||||||
User.createIndex(['username', 'host'], { unique: true });
|
User.createIndex(['username', 'host'], { unique: true });
|
||||||
User.createIndex(['usernameLower', 'host'], { unique: true });
|
User.createIndex(['usernameLower', 'host'], { unique: true });
|
||||||
User.createIndex('token', { sparse: true, unique: true });
|
User.createIndex('token', { sparse: true, unique: true });
|
||||||
|
|
|
@ -4,6 +4,7 @@ import request from '../../../remote/activitypub/request';
|
||||||
import { queueLogger } from '../../logger';
|
import { queueLogger } from '../../logger';
|
||||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||||
import Instance from '../../../models/instance';
|
import Instance from '../../../models/instance';
|
||||||
|
import instanceChart from '../../../services/chart/instance';
|
||||||
|
|
||||||
export default async (job: bq.Job, done: any): Promise<void> => {
|
export default async (job: bq.Job, done: any): Promise<void> => {
|
||||||
const { host } = new URL(job.data.to);
|
const { host } = new URL(job.data.to);
|
||||||
|
@ -19,6 +20,8 @@ export default async (job: bq.Job, done: any): Promise<void> => {
|
||||||
latestStatus: 200
|
latestStatus: 200
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
instanceChart.requestSent(i.host, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -31,6 +34,8 @@ export default async (job: bq.Job, done: any): Promise<void> => {
|
||||||
latestStatus: res != null && res.hasOwnProperty('statusCode') ? res.statusCode : null
|
latestStatus: res != null && res.hasOwnProperty('statusCode') ? res.statusCode : null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
instanceChart.requestSent(i.host, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res != null && res.hasOwnProperty('statusCode')) {
|
if (res != null && res.hasOwnProperty('statusCode')) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { publishApLogStream } from '../../../services/stream';
|
||||||
import Logger from '../../../misc/logger';
|
import Logger from '../../../misc/logger';
|
||||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||||
import Instance from '../../../models/instance';
|
import Instance from '../../../models/instance';
|
||||||
|
import instanceChart from '../../../services/chart/instance';
|
||||||
|
|
||||||
const logger = new Logger('inbox');
|
const logger = new Logger('inbox');
|
||||||
|
|
||||||
|
@ -128,6 +129,8 @@ export default async (job: bq.Job, done: any): Promise<void> => {
|
||||||
latestRequestReceivedAt: new Date()
|
latestRequestReceivedAt: new Date()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
instanceChart.requestReceived(i.host);
|
||||||
});
|
});
|
||||||
|
|
||||||
// アクティビティを処理
|
// アクティビティを処理
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { IDriveFile } from '../../../models/drive-file';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import { fromHtml } from '../../../mfm/fromHtml';
|
import { fromHtml } from '../../../mfm/fromHtml';
|
||||||
import usersChart from '../../../services/chart/users';
|
import usersChart from '../../../services/chart/users';
|
||||||
|
import instanceChart from '../../../services/chart/instance';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { resolveNote, extractEmojis } from './note';
|
import { resolveNote, extractEmojis } from './note';
|
||||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||||
|
@ -195,8 +196,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
instanceChart.newUser(i.host);
|
||||||
//perInstanceChart.newUser();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region Increment users count
|
//#region Increment users count
|
||||||
|
|
42
src/server/api/endpoints/charts/instance.ts
Normal file
42
src/server/api/endpoints/charts/instance.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import $ from 'cafy';
|
||||||
|
import define from '../../define';
|
||||||
|
import instanceChart from '../../../../services/chart/instance';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
stability: 'stable',
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'インスタンスごとのチャートを取得します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
span: {
|
||||||
|
validator: $.str.or(['day', 'hour']),
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '集計のスパン (day または hour)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
validator: $.num.optional.range(1, 500),
|
||||||
|
default: 30,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
host: {
|
||||||
|
validator: $.str,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '対象のインスタンスのホスト',
|
||||||
|
'en-US': 'Target instance host'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default define(meta, (ps) => new Promise(async (res, rej) => {
|
||||||
|
const stats = await instanceChart.getChart(ps.span as any, ps.limit, ps.host);
|
||||||
|
|
||||||
|
res(stats);
|
||||||
|
}));
|
|
@ -70,6 +70,22 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||||
sort = {
|
sort = {
|
||||||
caughtAt: 1
|
caughtAt: 1
|
||||||
};
|
};
|
||||||
|
} else if (ps.sort == '+driveUsage') {
|
||||||
|
sort = {
|
||||||
|
driveUsage: -1
|
||||||
|
};
|
||||||
|
} else if (ps.sort == '-driveUsage') {
|
||||||
|
sort = {
|
||||||
|
driveUsage: 1
|
||||||
|
};
|
||||||
|
} else if (ps.sort == '+driveFiles') {
|
||||||
|
sort = {
|
||||||
|
driveFiles: -1
|
||||||
|
};
|
||||||
|
} else if (ps.sort == '-driveFiles') {
|
||||||
|
sort = {
|
||||||
|
driveFiles: 1
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sort = {
|
sort = {
|
||||||
|
|
302
src/services/chart/instance.ts
Normal file
302
src/services/chart/instance.ts
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from '.';
|
||||||
|
import User from '../../models/user';
|
||||||
|
import Note from '../../models/note';
|
||||||
|
import Following from '../../models/following';
|
||||||
|
import DriveFile, { IDriveFile } from '../../models/drive-file';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* インスタンスごとのチャート
|
||||||
|
*/
|
||||||
|
type InstanceLog = {
|
||||||
|
requests: {
|
||||||
|
/**
|
||||||
|
* 失敗したリクエスト数
|
||||||
|
*/
|
||||||
|
failed: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功したリクエスト数
|
||||||
|
*/
|
||||||
|
succeeded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 受信したリクエスト数
|
||||||
|
*/
|
||||||
|
received: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
notes: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全投稿数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加した投稿数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少した投稿数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
users: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ユーザー数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したユーザー数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したユーザー数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
following: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全フォロー数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したフォロー数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したフォロー数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
followers: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全フォロワー数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したフォロワー数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したフォロワー数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
drive: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ドライブファイル数
|
||||||
|
*/
|
||||||
|
totalFiles: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ドライブファイルの合計サイズ
|
||||||
|
*/
|
||||||
|
totalUsage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したドライブファイル数
|
||||||
|
*/
|
||||||
|
incFiles: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したドライブ使用量
|
||||||
|
*/
|
||||||
|
incUsage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したドライブファイル数
|
||||||
|
*/
|
||||||
|
decFiles: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したドライブ使用量
|
||||||
|
*/
|
||||||
|
decUsage: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class InstanceChart extends Chart<InstanceLog> {
|
||||||
|
constructor() {
|
||||||
|
super('instance', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: InstanceLog, group?: any): Promise<InstanceLog> {
|
||||||
|
const calcUsage = () => DriveFile
|
||||||
|
.aggregate([{
|
||||||
|
$match: {
|
||||||
|
'metadata._user.host': group,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
$project: {
|
||||||
|
length: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
$group: {
|
||||||
|
_id: null,
|
||||||
|
usage: { $sum: '$length' }
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
.then(res => res.length > 0 ? res[0].usage : 0);
|
||||||
|
|
||||||
|
const [
|
||||||
|
notesCount,
|
||||||
|
usersCount,
|
||||||
|
followingCount,
|
||||||
|
followersCount,
|
||||||
|
driveFiles,
|
||||||
|
driveUsage,
|
||||||
|
] = init ? await Promise.all([
|
||||||
|
Note.count({ '_user.host': group }),
|
||||||
|
User.count({ host: group }),
|
||||||
|
Following.count({ '_follower.host': group }),
|
||||||
|
Following.count({ '_followee.host': group }),
|
||||||
|
DriveFile.count({ 'metadata._user.host': group }),
|
||||||
|
calcUsage(),
|
||||||
|
]) : [
|
||||||
|
latest ? latest.notes.total : 0,
|
||||||
|
latest ? latest.users.total : 0,
|
||||||
|
latest ? latest.following.total : 0,
|
||||||
|
latest ? latest.followers.total : 0,
|
||||||
|
latest ? latest.drive.totalFiles : 0,
|
||||||
|
latest ? latest.drive.totalUsage : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
requests: {
|
||||||
|
failed: 0,
|
||||||
|
succeeded: 0,
|
||||||
|
received: 0
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
total: notesCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
total: usersCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
following: {
|
||||||
|
total: followingCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
followers: {
|
||||||
|
total: followersCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
drive: {
|
||||||
|
totalFiles: driveFiles,
|
||||||
|
totalUsage: driveUsage,
|
||||||
|
incFiles: 0,
|
||||||
|
incUsage: 0,
|
||||||
|
decFiles: 0,
|
||||||
|
decUsage: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async requestReceived(host: string) {
|
||||||
|
await this.inc({
|
||||||
|
requests: {
|
||||||
|
received: 1
|
||||||
|
}
|
||||||
|
}, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async requestSent(host: string, isSucceeded: boolean) {
|
||||||
|
const update: Obj = {};
|
||||||
|
|
||||||
|
if (isSucceeded) {
|
||||||
|
update.succeeded = 1;
|
||||||
|
} else {
|
||||||
|
update.failed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.inc({
|
||||||
|
requests: update
|
||||||
|
}, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async newUser(host: string) {
|
||||||
|
await this.inc({
|
||||||
|
users: {
|
||||||
|
total: 1,
|
||||||
|
inc: 1
|
||||||
|
}
|
||||||
|
}, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async updateNote(host: string, isAdditional: boolean) {
|
||||||
|
await this.inc({
|
||||||
|
notes: {
|
||||||
|
total: isAdditional ? 1 : -1,
|
||||||
|
inc: isAdditional ? 1 : 0,
|
||||||
|
dec: isAdditional ? 0 : 1,
|
||||||
|
}
|
||||||
|
}, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async updateFollowing(host: string, isAdditional: boolean) {
|
||||||
|
await this.inc({
|
||||||
|
following: {
|
||||||
|
total: isAdditional ? 1 : -1,
|
||||||
|
inc: isAdditional ? 1 : 0,
|
||||||
|
dec: isAdditional ? 0 : 1,
|
||||||
|
}
|
||||||
|
}, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async updateFollowers(host: string, isAdditional: boolean) {
|
||||||
|
await this.inc({
|
||||||
|
followers: {
|
||||||
|
total: isAdditional ? 1 : -1,
|
||||||
|
inc: isAdditional ? 1 : 0,
|
||||||
|
dec: isAdditional ? 0 : 1,
|
||||||
|
}
|
||||||
|
}, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async updateDrive(file: IDriveFile, isAdditional: boolean) {
|
||||||
|
const update: Obj = {};
|
||||||
|
|
||||||
|
update.totalFiles = isAdditional ? 1 : -1;
|
||||||
|
update.totalUsage = isAdditional ? file.length : -file.length;
|
||||||
|
if (isAdditional) {
|
||||||
|
update.incFiles = 1;
|
||||||
|
update.incUsage = file.length;
|
||||||
|
} else {
|
||||||
|
update.decFiles = 1;
|
||||||
|
update.decUsage = file.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.inc({
|
||||||
|
drive: update
|
||||||
|
}, file.metadata._user.host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new InstanceChart();
|
|
@ -13,17 +13,19 @@ import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../mode
|
||||||
import DriveFolder from '../../models/drive-folder';
|
import DriveFolder from '../../models/drive-folder';
|
||||||
import { pack } from '../../models/drive-file';
|
import { pack } from '../../models/drive-file';
|
||||||
import { publishMainStream, publishDriveStream } from '../stream';
|
import { publishMainStream, publishDriveStream } from '../stream';
|
||||||
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
import { isLocalUser, IUser, IRemoteUser, isRemoteUser } from '../../models/user';
|
||||||
import delFile from './delete-file';
|
import delFile from './delete-file';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
|
import { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
|
||||||
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
||||||
import driveChart from '../../services/chart/drive';
|
import driveChart from '../../services/chart/drive';
|
||||||
import perUserDriveChart from '../../services/chart/per-user-drive';
|
import perUserDriveChart from '../../services/chart/per-user-drive';
|
||||||
|
import instanceChart from '../../services/chart/instance';
|
||||||
import fetchMeta from '../../misc/fetch-meta';
|
import fetchMeta from '../../misc/fetch-meta';
|
||||||
import { GenerateVideoThumbnail } from './generate-video-thumbnail';
|
import { GenerateVideoThumbnail } from './generate-video-thumbnail';
|
||||||
import { driveLogger } from './logger';
|
import { driveLogger } from './logger';
|
||||||
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
||||||
|
import Instance from '../../models/instance';
|
||||||
|
|
||||||
const logger = driveLogger.createSubLogger('register', 'yellow');
|
const logger = driveLogger.createSubLogger('register', 'yellow');
|
||||||
|
|
||||||
|
@ -523,6 +525,15 @@ export default async function(
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
driveChart.update(driveFile, true);
|
driveChart.update(driveFile, true);
|
||||||
perUserDriveChart.update(driveFile, true);
|
perUserDriveChart.update(driveFile, true);
|
||||||
|
if (isRemoteUser(driveFile.metadata._user)) {
|
||||||
|
instanceChart.updateDrive(driveFile, true);
|
||||||
|
Instance.update({ host: driveFile.metadata._user.host }, {
|
||||||
|
$inc: {
|
||||||
|
driveUsage: driveFile.length,
|
||||||
|
driveFiles: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return driveFile;
|
return driveFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@ import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import driveChart from '../../services/chart/drive';
|
import driveChart from '../../services/chart/drive';
|
||||||
import perUserDriveChart from '../../services/chart/per-user-drive';
|
import perUserDriveChart from '../../services/chart/per-user-drive';
|
||||||
|
import instanceChart from '../../services/chart/instance';
|
||||||
import DriveFileWebpublic, { DriveFileWebpublicChunk } from '../../models/drive-file-webpublic';
|
import DriveFileWebpublic, { DriveFileWebpublicChunk } from '../../models/drive-file-webpublic';
|
||||||
|
import Instance from '../../models/instance';
|
||||||
|
import { isRemoteUser } from '../../models/user';
|
||||||
|
|
||||||
export default async function(file: IDriveFile, isExpired = false) {
|
export default async function(file: IDriveFile, isExpired = false) {
|
||||||
if (file.metadata.storage == 'minio') {
|
if (file.metadata.storage == 'minio') {
|
||||||
|
@ -84,4 +87,13 @@ export default async function(file: IDriveFile, isExpired = false) {
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
driveChart.update(file, false);
|
driveChart.update(file, false);
|
||||||
perUserDriveChart.update(file, false);
|
perUserDriveChart.update(file, false);
|
||||||
|
if (isRemoteUser(file.metadata._user)) {
|
||||||
|
instanceChart.updateDrive(file, false);
|
||||||
|
Instance.update({ host: file.metadata._user.host }, {
|
||||||
|
$inc: {
|
||||||
|
driveUsage: -file.length,
|
||||||
|
driveFiles: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import createFollowRequest from './requests/create';
|
||||||
import perUserFollowingChart from '../../services/chart/per-user-following';
|
import perUserFollowingChart from '../../services/chart/per-user-following';
|
||||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||||
import Instance from '../../models/instance';
|
import Instance from '../../models/instance';
|
||||||
|
import instanceChart from '../../services/chart/instance';
|
||||||
|
|
||||||
export default async function(follower: IUser, followee: IUser, requestId?: string) {
|
export default async function(follower: IUser, followee: IUser, requestId?: string) {
|
||||||
// check blocking
|
// check blocking
|
||||||
|
@ -108,8 +109,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
instanceChart.updateFollowing(i.host, true);
|
||||||
//perInstanceChart.newFollowing();
|
|
||||||
});
|
});
|
||||||
} else if (isLocalUser(follower) && isRemoteUser(followee)) {
|
} else if (isLocalUser(follower) && isRemoteUser(followee)) {
|
||||||
registerOrFetchInstanceDoc(followee.host).then(i => {
|
registerOrFetchInstanceDoc(followee.host).then(i => {
|
||||||
|
@ -119,8 +119,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
instanceChart.updateFollowers(i.host, true);
|
||||||
//perInstanceChart.newFollower();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
|
@ -7,6 +7,9 @@ import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import perUserFollowingChart from '../../services/chart/per-user-following';
|
import perUserFollowingChart from '../../services/chart/per-user-following';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../../misc/logger';
|
||||||
|
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||||
|
import Instance from '../../models/instance';
|
||||||
|
import instanceChart from '../../services/chart/instance';
|
||||||
|
|
||||||
const logger = new Logger('following/delete');
|
const logger = new Logger('following/delete');
|
||||||
|
|
||||||
|
@ -41,6 +44,30 @@ export default async function(follower: IUser, followee: IUser) {
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region Update instance stats
|
||||||
|
if (isRemoteUser(follower) && isLocalUser(followee)) {
|
||||||
|
registerOrFetchInstanceDoc(follower.host).then(i => {
|
||||||
|
Instance.update({ _id: i._id }, {
|
||||||
|
$inc: {
|
||||||
|
followingCount: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
instanceChart.updateFollowing(i.host, false);
|
||||||
|
});
|
||||||
|
} else if (isLocalUser(follower) && isRemoteUser(followee)) {
|
||||||
|
registerOrFetchInstanceDoc(followee.host).then(i => {
|
||||||
|
Instance.update({ _id: i._id }, {
|
||||||
|
$inc: {
|
||||||
|
followersCount: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
instanceChart.updateFollowers(i.host, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
perUserFollowingChart.update(follower, followee, false);
|
perUserFollowingChart.update(follower, followee, false);
|
||||||
|
|
||||||
// Publish unfollow event
|
// Publish unfollow event
|
||||||
|
|
|
@ -24,6 +24,7 @@ import isQuote from '../../misc/is-quote';
|
||||||
import notesChart from '../../services/chart/notes';
|
import notesChart from '../../services/chart/notes';
|
||||||
import perUserNotesChart from '../../services/chart/per-user-notes';
|
import perUserNotesChart from '../../services/chart/per-user-notes';
|
||||||
import activeUsersChart from '../../services/chart/active-users';
|
import activeUsersChart from '../../services/chart/active-users';
|
||||||
|
import instanceChart from '../../services/chart/instance';
|
||||||
|
|
||||||
import { erase, concat } from '../../prelude/array';
|
import { erase, concat } from '../../prelude/array';
|
||||||
import insertNoteUnread from './unread';
|
import insertNoteUnread from './unread';
|
||||||
|
@ -229,8 +230,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
instanceChart.updateNote(i.host, true);
|
||||||
//perInstanceChart.newNote();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Note, { INote } from '../../models/note';
|
import Note, { INote } from '../../models/note';
|
||||||
import { IUser, isLocalUser } from '../../models/user';
|
import { IUser, isLocalUser, isRemoteUser } from '../../models/user';
|
||||||
import { publishNoteStream } from '../stream';
|
import { publishNoteStream } from '../stream';
|
||||||
import renderDelete from '../../remote/activitypub/renderer/delete';
|
import renderDelete from '../../remote/activitypub/renderer/delete';
|
||||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||||
|
@ -12,6 +12,9 @@ import config from '../../config';
|
||||||
import NoteUnread from '../../models/note-unread';
|
import NoteUnread from '../../models/note-unread';
|
||||||
import read from './read';
|
import read from './read';
|
||||||
import DriveFile from '../../models/drive-file';
|
import DriveFile from '../../models/drive-file';
|
||||||
|
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||||
|
import Instance from '../../models/instance';
|
||||||
|
import instanceChart from '../../services/chart/instance';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 投稿を削除します。
|
* 投稿を削除します。
|
||||||
|
@ -91,4 +94,16 @@ export default async function(user: IUser, note: INote) {
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
notesChart.update(note, false);
|
notesChart.update(note, false);
|
||||||
perUserNotesChart.update(user, note, false);
|
perUserNotesChart.update(user, note, false);
|
||||||
|
|
||||||
|
if (isRemoteUser(user)) {
|
||||||
|
registerOrFetchInstanceDoc(user.host).then(i => {
|
||||||
|
Instance.update({ _id: i._id }, {
|
||||||
|
$inc: {
|
||||||
|
notesCount: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
instanceChart.updateNote(i.host, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue