parent
							
								
									652bb03087
								
							
						
					
					
						commit
						8d42e94e57
					
				|  | @ -1209,6 +1209,7 @@ admin/views/charts.vue: | |||
|     notes-total: "投稿の積算" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの積算" | ||||
|     active-users: "アクティブユーザー数" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の積算" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|  |  | |||
|  | @ -0,0 +1,48 @@ | |||
| import autobind from 'autobind-decorator'; | ||||
| import Chart, { Obj } from '.'; | ||||
| import { IUser, isLocalUser } from '../models/user'; | ||||
| 
 | ||||
| /** | ||||
|  * アクティブユーザーに関するチャート | ||||
|  */ | ||||
| type ActiveUsersLog = { | ||||
| 	local: { | ||||
| 		/** | ||||
| 		 * アクティブユーザー数 | ||||
| 		 */ | ||||
| 		count: number; | ||||
| 	}; | ||||
| 
 | ||||
| 	remote: ActiveUsersLog['local']; | ||||
| }; | ||||
| 
 | ||||
| class ActiveUsersChart extends Chart<ActiveUsersLog> { | ||||
| 	constructor() { | ||||
| 		super('activeUsers'); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	protected async getTemplate(init: boolean, latest?: ActiveUsersLog): Promise<ActiveUsersLog> { | ||||
| 		return { | ||||
| 			local: { | ||||
| 				count: 0 | ||||
| 			}, | ||||
| 			remote: { | ||||
| 				count: 0 | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public async update(user: IUser) { | ||||
| 		const update: Obj = { | ||||
| 			count: 1 | ||||
| 		}; | ||||
| 
 | ||||
| 		await this.incIfUnique({ | ||||
| 			[isLocalUser(user) ? 'local' : 'remote']: update | ||||
| 		}, 'users', user._id.toHexString()); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export default new ActiveUsersChart(); | ||||
|  | @ -10,6 +10,7 @@ | |||
| 			<optgroup :label="$t('users')"> | ||||
| 				<option value="users">{{ $t('charts.users') }}</option> | ||||
| 				<option value="users-total">{{ $t('charts.users-total') }}</option> | ||||
| 				<option value="active-users">{{ $t('charts.active-users') }}</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup :label="$t('notes')"> | ||||
| 				<option value="notes">{{ $t('charts.notes') }}</option> | ||||
|  | @ -67,6 +68,7 @@ export default Vue.extend({ | |||
| 				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'); | ||||
|  | @ -107,12 +109,14 @@ export default Vue.extend({ | |||
| 		const [perHour, perDay] = await Promise.all([Promise.all([ | ||||
| 			this.$root.api('charts/federation', { limit: limit, span: 'hour' }), | ||||
| 			this.$root.api('charts/users', { limit: limit, span: 'hour' }), | ||||
| 			this.$root.api('charts/active-users', { limit: limit, span: 'hour' }), | ||||
| 			this.$root.api('charts/notes', { limit: limit, span: 'hour' }), | ||||
| 			this.$root.api('charts/drive', { limit: limit, span: 'hour' }), | ||||
| 			this.$root.api('charts/network', { limit: limit, span: 'hour' }) | ||||
| 		]), Promise.all([ | ||||
| 			this.$root.api('charts/federation', { limit: limit, span: 'day' }), | ||||
| 			this.$root.api('charts/users', { limit: limit, span: 'day' }), | ||||
| 			this.$root.api('charts/active-users', { limit: limit, span: 'day' }), | ||||
| 			this.$root.api('charts/notes', { limit: limit, span: 'day' }), | ||||
| 			this.$root.api('charts/drive', { limit: limit, span: 'day' }), | ||||
| 			this.$root.api('charts/network', { limit: limit, span: 'day' }) | ||||
|  | @ -122,16 +126,18 @@ export default Vue.extend({ | |||
| 			perHour: { | ||||
| 				federation: perHour[0], | ||||
| 				users: perHour[1], | ||||
| 				notes: perHour[2], | ||||
| 				drive: perHour[3], | ||||
| 				network: perHour[4] | ||||
| 				activeUsers: perHour[2], | ||||
| 				notes: perHour[3], | ||||
| 				drive: perHour[4], | ||||
| 				network: perHour[5] | ||||
| 			}, | ||||
| 			perDay: { | ||||
| 				federation: perDay[0], | ||||
| 				users: perDay[1], | ||||
| 				notes: perDay[2], | ||||
| 				drive: perDay[3], | ||||
| 				network: perDay[4] | ||||
| 				activeUsers: perDay[2], | ||||
| 				notes: perDay[3], | ||||
| 				drive: perDay[4], | ||||
| 				network: perDay[5] | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
|  | @ -321,6 +327,24 @@ export default Vue.extend({ | |||
| 			}; | ||||
| 		}, | ||||
| 
 | ||||
| 		activeUsersChart(): any { | ||||
| 			return { | ||||
| 				series: [{ | ||||
| 					name: 'Combined', | ||||
| 					type: 'line', | ||||
| 					data: this.format(sum(this.stats.activeUsers.local.count, this.stats.activeUsers.remote.count)) | ||||
| 				}, { | ||||
| 					name: 'Local', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.activeUsers.local.count) | ||||
| 				}, { | ||||
| 					name: 'Remote', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.activeUsers.remote.count) | ||||
| 				}] | ||||
| 			}; | ||||
| 		}, | ||||
| 
 | ||||
| 		driveChart(): any { | ||||
| 			return { | ||||
| 				bytes: true, | ||||
|  |  | |||
|  | @ -0,0 +1,34 @@ | |||
| import $ from 'cafy'; | ||||
| import define from '../../define'; | ||||
| import activeUsersChart from '../../../../chart/active-users'; | ||||
| 
 | ||||
| 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時間分のデータが返ります。' | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, (ps) => new Promise(async (res, rej) => { | ||||
| 	const stats = await activeUsersChart.getChart(ps.span as any, ps.limit); | ||||
| 
 | ||||
| 	res(stats); | ||||
| })); | ||||
|  | @ -6,6 +6,7 @@ import { packMany } from '../../../../models/note'; | |||
| import define from '../../define'; | ||||
| import { countIf } from '../../../../prelude/array'; | ||||
| import fetchMeta from '../../../../misc/fetch-meta'; | ||||
| import activeUsersChart from '../../../../chart/active-users'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
|  | @ -272,4 +273,6 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => { | |||
| 		}); | ||||
| 
 | ||||
| 	res(await packMany(timeline, user)); | ||||
| 
 | ||||
| 	activeUsersChart.update(user); | ||||
| })); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { packMany } from '../../../../models/note'; | |||
| import define from '../../define'; | ||||
| import { countIf } from '../../../../prelude/array'; | ||||
| import fetchMeta from '../../../../misc/fetch-meta'; | ||||
| import activeUsersChart from '../../../../chart/active-users'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
|  | @ -161,4 +162,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => { | |||
| 		}); | ||||
| 
 | ||||
| 	res(await packMany(timeline, user)); | ||||
| 
 | ||||
| 	if (user) { | ||||
| 		activeUsersChart.update(user); | ||||
| 	} | ||||
| })); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { getFriends } from '../../common/get-friends'; | |||
| import { packMany } from '../../../../models/note'; | ||||
| import define from '../../define'; | ||||
| import { countIf } from '../../../../prelude/array'; | ||||
| import activeUsersChart from '../../../../chart/active-users'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
|  | @ -266,4 +267,6 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => { | |||
| 
 | ||||
| 	// Serialize
 | ||||
| 	res(await packMany(timeline, user)); | ||||
| 
 | ||||
| 	activeUsersChart.update(user); | ||||
| })); | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import registerHashtag from '../register-hashtag'; | |||
| import isQuote from '../../misc/is-quote'; | ||||
| import notesChart from '../../chart/notes'; | ||||
| import perUserNotesChart from '../../chart/per-user-notes'; | ||||
| import activeUsersChart from '../../chart/active-users'; | ||||
| 
 | ||||
| import { erase } from '../../prelude/array'; | ||||
| import insertNoteUnread from './unread'; | ||||
|  | @ -196,6 +197,8 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< | |||
| 	// 統計を更新
 | ||||
| 	notesChart.update(note, true); | ||||
| 	perUserNotesChart.update(user, note, true); | ||||
| 	// ローカルユーザーのチャートはタイムライン取得時に更新しているのでリモートユーザーの場合だけでよい
 | ||||
| 	if (isRemoteUser(user)) activeUsersChart.update(user); | ||||
| 
 | ||||
| 	// Register host
 | ||||
| 	if (isRemoteUser(user)) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue