stats
This commit is contained in:
		
							parent
							
								
									2465b4aa7b
								
							
						
					
					
						commit
						dd1aa8c7d6
					
				
					 17 changed files with 462 additions and 2 deletions
				
			
		|  | @ -25,6 +25,7 @@ Note that Misskey uses following subdomains: | ||||||
| * **api**.*{primary domain}* | * **api**.*{primary domain}* | ||||||
| * **auth**.*{primary domain}* | * **auth**.*{primary domain}* | ||||||
| * **about**.*{primary domain}* | * **about**.*{primary domain}* | ||||||
|  | * **stats**.*{primary domain}* | ||||||
| * **status**.*{primary domain}* | * **status**.*{primary domain}* | ||||||
| * **dev**.*{primary domain}* | * **dev**.*{primary domain}* | ||||||
| * **file**.*{secondary domain}* | * **file**.*{secondary domain}* | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ Misskeyは以下のサブドメインを使います: | ||||||
| * **api**.*{primary domain}* | * **api**.*{primary domain}* | ||||||
| * **auth**.*{primary domain}* | * **auth**.*{primary domain}* | ||||||
| * **about**.*{primary domain}* | * **about**.*{primary domain}* | ||||||
|  | * **stats**.*{primary domain}* | ||||||
| * **status**.*{primary domain}* | * **status**.*{primary domain}* | ||||||
| * **dev**.*{primary domain}* | * **dev**.*{primary domain}* | ||||||
| * **file**.*{secondary domain}* | * **file**.*{secondary domain}* | ||||||
|  |  | ||||||
|  | @ -427,6 +427,10 @@ mobile: | ||||||
|       known: "You know" |       known: "You know" | ||||||
|       load-more: "More" |       load-more: "More" | ||||||
| 
 | 
 | ||||||
|  | stats: | ||||||
|  |   posts-count: "Number of all posts" | ||||||
|  |   users-count: "Number of all users" | ||||||
|  | 
 | ||||||
| status: | status: | ||||||
|   all-systems-maybe-operational: "All systems maybe operational" |   all-systems-maybe-operational: "All systems maybe operational" | ||||||
|   what-is-this-site: "" |   what-is-this-site: "" | ||||||
|  |  | ||||||
|  | @ -428,6 +428,10 @@ mobile: | ||||||
|       known: "知り合い" |       known: "知り合い" | ||||||
|       load-more: "もっと" |       load-more: "もっと" | ||||||
| 
 | 
 | ||||||
|  | stats: | ||||||
|  |   posts-count: "投稿の数" | ||||||
|  |   users-count: "アカウントの数" | ||||||
|  | 
 | ||||||
| status: | status: | ||||||
|   all-systems-maybe-operational: "すべてのシステムがたぶん正常に作動しています" |   all-systems-maybe-operational: "すべてのシステムがたぶん正常に作動しています" | ||||||
|   what-is-this-site: "" |   what-is-this-site: "" | ||||||
|  |  | ||||||
|  | @ -69,6 +69,9 @@ const endpoints: Endpoint[] = [ | ||||||
| 	{ | 	{ | ||||||
| 		name: 'meta' | 		name: 'meta' | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		name: 'stats' | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		name: 'username/available' | 		name: 'username/available' | ||||||
| 	}, | 	}, | ||||||
|  | @ -109,6 +112,12 @@ const endpoints: Endpoint[] = [ | ||||||
| 		withCredential: true, | 		withCredential: true, | ||||||
| 		secure: true | 		secure: true | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		name: 'aggregation/posts', | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		name: 'aggregation/users', | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		name: 'aggregation/users/activity', | 		name: 'aggregation/users/activity', | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
							
								
								
									
										95
									
								
								src/api/endpoints/aggregation/posts.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/api/endpoints/aggregation/posts.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | /** | ||||||
|  |  * Module dependencies | ||||||
|  |  */ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import Post from '../../models/post'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Aggregate posts | ||||||
|  |  * | ||||||
|  |  * @param {any} params | ||||||
|  |  * @return {Promise<any>} | ||||||
|  |  */ | ||||||
|  | module.exports = params => new Promise(async (res, rej) => { | ||||||
|  | 	// Get 'limit' parameter
 | ||||||
|  | 	const [limit = 365, limitErr] = $(params.limit).optional.number().range(1, 365).$; | ||||||
|  | 	if (limitErr) return rej('invalid limit param'); | ||||||
|  | 
 | ||||||
|  | 	const datas = await Post | ||||||
|  | 		.aggregate([ | ||||||
|  | 			{ $project: { | ||||||
|  | 				repost_id: '$repost_id', | ||||||
|  | 				reply_to_id: '$reply_to_id', | ||||||
|  | 				created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST
 | ||||||
|  | 			}}, | ||||||
|  | 			{ $project: { | ||||||
|  | 				date: { | ||||||
|  | 					year: { $year: '$created_at' }, | ||||||
|  | 					month: { $month: '$created_at' }, | ||||||
|  | 					day: { $dayOfMonth: '$created_at' } | ||||||
|  | 				}, | ||||||
|  | 				type: { | ||||||
|  | 					$cond: { | ||||||
|  | 						if: { $ne: ['$repost_id', null] }, | ||||||
|  | 						then: 'repost', | ||||||
|  | 						else: { | ||||||
|  | 							$cond: { | ||||||
|  | 								if: { $ne: ['$reply_to_id', null] }, | ||||||
|  | 								then: 'reply', | ||||||
|  | 								else: 'post' | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				}} | ||||||
|  | 			}, | ||||||
|  | 			{ $group: { _id: { | ||||||
|  | 				date: '$date', | ||||||
|  | 				type: '$type' | ||||||
|  | 			}, count: { $sum: 1 } } }, | ||||||
|  | 			{ $group: { | ||||||
|  | 				_id: '$_id.date', | ||||||
|  | 				data: { $addToSet: { | ||||||
|  | 					type: '$_id.type', | ||||||
|  | 					count: '$count' | ||||||
|  | 				}} | ||||||
|  | 			} } | ||||||
|  | 		]); | ||||||
|  | 
 | ||||||
|  | 	datas.forEach(data => { | ||||||
|  | 		data.date = data._id; | ||||||
|  | 		delete data._id; | ||||||
|  | 
 | ||||||
|  | 		data.posts = (data.data.filter(x => x.type == 'post')[0] || { count: 0 }).count; | ||||||
|  | 		data.reposts = (data.data.filter(x => x.type == 'repost')[0] || { count: 0 }).count; | ||||||
|  | 		data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; | ||||||
|  | 
 | ||||||
|  | 		delete data.data; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	const graph = []; | ||||||
|  | 
 | ||||||
|  | 	for (let i = 0; i < limit; i++) { | ||||||
|  | 		const day = new Date(new Date().setDate(new Date().getDate() - i)); | ||||||
|  | 
 | ||||||
|  | 		const data = datas.filter(d => | ||||||
|  | 			d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() | ||||||
|  | 		)[0]; | ||||||
|  | 
 | ||||||
|  | 		if (data) { | ||||||
|  | 			graph.push(data); | ||||||
|  | 		} else { | ||||||
|  | 			graph.push({ | ||||||
|  | 				date: { | ||||||
|  | 					year: day.getFullYear(), | ||||||
|  | 					month: day.getMonth() + 1, // In JavaScript, month is zero-based.
 | ||||||
|  | 					day: day.getDate() | ||||||
|  | 				}, | ||||||
|  | 				posts: 0, | ||||||
|  | 				reposts: 0, | ||||||
|  | 				replies: 0 | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	res(graph); | ||||||
|  | }); | ||||||
							
								
								
									
										59
									
								
								src/api/endpoints/aggregation/users.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/api/endpoints/aggregation/users.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | ||||||
|  | /** | ||||||
|  |  * Module dependencies | ||||||
|  |  */ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import User from '../../models/user'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Aggregate users | ||||||
|  |  * | ||||||
|  |  * @param {any} params | ||||||
|  |  * @return {Promise<any>} | ||||||
|  |  */ | ||||||
|  | module.exports = params => new Promise(async (res, rej) => { | ||||||
|  | 	// Get 'limit' parameter
 | ||||||
|  | 	const [limit = 365, limitErr] = $(params.limit).optional.number().range(1, 365).$; | ||||||
|  | 	if (limitErr) return rej('invalid limit param'); | ||||||
|  | 
 | ||||||
|  | 	const startTime = new Date(new Date().setMonth(new Date().getMonth() - 1)); | ||||||
|  | 
 | ||||||
|  | 	const users = await User | ||||||
|  | 		.find({ | ||||||
|  | 			$or: [ | ||||||
|  | 				{ deleted_at: { $exists: false } }, | ||||||
|  | 				{ deleted_at: { $gt: startTime } } | ||||||
|  | 			] | ||||||
|  | 		}, { | ||||||
|  | 			_id: false, | ||||||
|  | 			created_at: true, | ||||||
|  | 			deleted_at: true | ||||||
|  | 		}, { | ||||||
|  | 			sort: { created_at: -1 } | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 	const graph = []; | ||||||
|  | 
 | ||||||
|  | 	for (let i = 0; i < limit; i++) { | ||||||
|  | 		let day = new Date(new Date().setDate(new Date().getDate() - i)); | ||||||
|  | 		day = new Date(day.setMilliseconds(999)); | ||||||
|  | 		day = new Date(day.setSeconds(59)); | ||||||
|  | 		day = new Date(day.setMinutes(59)); | ||||||
|  | 		day = new Date(day.setHours(23)); | ||||||
|  | 		// day = day.getTime();
 | ||||||
|  | 
 | ||||||
|  | 		const count = users.filter(f => | ||||||
|  | 			f.created_at < day && (f.deleted_at == null || f.deleted_at > day) | ||||||
|  | 		).length; | ||||||
|  | 
 | ||||||
|  | 		graph.push({ | ||||||
|  | 			date: { | ||||||
|  | 				year: day.getFullYear(), | ||||||
|  | 				month: day.getMonth() + 1, // In JavaScript, month is zero-based.
 | ||||||
|  | 				day: day.getDate() | ||||||
|  | 			}, | ||||||
|  | 			count: count | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	res(graph); | ||||||
|  | }); | ||||||
							
								
								
									
										48
									
								
								src/api/endpoints/stats.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/api/endpoints/stats.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | /** | ||||||
|  |  * Module dependencies | ||||||
|  |  */ | ||||||
|  | import Post from '../models/post'; | ||||||
|  | import User from '../models/user'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /stats: | ||||||
|  |  *   post: | ||||||
|  |  *     summary: Show the misskey's statistics | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: Success | ||||||
|  |  *         schema: | ||||||
|  |  *           type: object | ||||||
|  |  *           properties: | ||||||
|  |  *             posts_count: | ||||||
|  |  *               description: count of all posts of misskey | ||||||
|  |  *               type: number | ||||||
|  |  *             users_count: | ||||||
|  |  *               description: count of all users of misskey | ||||||
|  |  *               type: number | ||||||
|  |  * | ||||||
|  |  *       default: | ||||||
|  |  *         description: Failed | ||||||
|  |  *         schema: | ||||||
|  |  *           $ref: "#/definitions/Error" | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Show the misskey's statistics | ||||||
|  |  * | ||||||
|  |  * @param {any} params | ||||||
|  |  * @return {Promise<any>} | ||||||
|  |  */ | ||||||
|  | module.exports = params => new Promise(async (res, rej) => { | ||||||
|  | 	const postsCount = await Post | ||||||
|  | 		.count(); | ||||||
|  | 
 | ||||||
|  | 	const usersCount = await User | ||||||
|  | 		.count(); | ||||||
|  | 
 | ||||||
|  | 	res({ | ||||||
|  | 		posts_count: postsCount, | ||||||
|  | 		users_count: usersCount | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | @ -81,6 +81,7 @@ type Mixin = { | ||||||
| 	api_url: string; | 	api_url: string; | ||||||
| 	auth_url: string; | 	auth_url: string; | ||||||
| 	about_url: string; | 	about_url: string; | ||||||
|  | 	stats_url: string; | ||||||
| 	status_url: string; | 	status_url: string; | ||||||
| 	dev_url: string; | 	dev_url: string; | ||||||
| 	drive_url: string; | 	drive_url: string; | ||||||
|  | @ -116,6 +117,7 @@ export default function load() { | ||||||
| 	mixin.auth_url = `${mixin.scheme}://auth.${mixin.host}`; | 	mixin.auth_url = `${mixin.scheme}://auth.${mixin.host}`; | ||||||
| 	mixin.dev_url = `${mixin.scheme}://dev.${mixin.host}`; | 	mixin.dev_url = `${mixin.scheme}://dev.${mixin.host}`; | ||||||
| 	mixin.about_url = `${mixin.scheme}://about.${mixin.host}`; | 	mixin.about_url = `${mixin.scheme}://about.${mixin.host}`; | ||||||
|  | 	mixin.stats_url = `${mixin.scheme}://stats.${mixin.host}`; | ||||||
| 	mixin.status_url = `${mixin.scheme}://status.${mixin.host}`; | 	mixin.status_url = `${mixin.scheme}://status.${mixin.host}`; | ||||||
| 	mixin.drive_url = `${mixin.secondary_scheme}://file.${mixin.secondary_host}`; | 	mixin.drive_url = `${mixin.secondary_scheme}://file.${mixin.secondary_host}`; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ const url = `${scheme}//${host}`; | ||||||
| const apiUrl = `${scheme}//api.${host}`; | const apiUrl = `${scheme}//api.${host}`; | ||||||
| const devUrl = `${scheme}//dev.${host}`; | const devUrl = `${scheme}//dev.${host}`; | ||||||
| const aboutUrl = `${scheme}//about.${host}`; | const aboutUrl = `${scheme}//about.${host}`; | ||||||
|  | const statsUrl = `${scheme}//stats.${host}`; | ||||||
| const statusUrl = `${scheme}//status.${host}`; | const statusUrl = `${scheme}//status.${host}`; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  | @ -17,5 +18,6 @@ export default { | ||||||
| 	apiUrl, | 	apiUrl, | ||||||
| 	devUrl, | 	devUrl, | ||||||
| 	aboutUrl, | 	aboutUrl, | ||||||
|  | 	statsUrl, | ||||||
| 	statusUrl | 	statusUrl | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| <mk-nav-home-widget><a href={ CONFIG.aboutUrl }>Misskeyについて</a><i>・</i><a href={ CONFIG.statusUrl }>ステータス</a><i>・</i><a href="http://zawazawa.jp/misskey/">Wiki</a><i>・</i><a href="https://github.com/syuilo/misskey">リポジトリ</a><i>・</i><a href={ CONFIG.devUrl }>開発者</a><i>・</i><a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on <i class="fa fa-twitter"></i></a> | <mk-nav-home-widget><a href={ CONFIG.aboutUrl }>Misskeyについて</a><i>・</i><a href={ CONFIG.statsUrl }>統計</a><i>・</i><a href={ CONFIG.statusUrl }>ステータス</a><i>・</i><a href="http://zawazawa.jp/misskey/">Wiki</a><i>・</i><a href="https://github.com/syuilo/misskey">リポジトリ</a><i>・</i><a href={ CONFIG.devUrl }>開発者</a><i>・</i><a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on <i class="fa fa-twitter"></i></a> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								src/web/app/stats/script.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/web/app/stats/script.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | /** | ||||||
|  |  * Stats | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | // Style
 | ||||||
|  | import './style.styl'; | ||||||
|  | 
 | ||||||
|  | import * as riot from 'riot'; | ||||||
|  | require('./tags'); | ||||||
|  | import init from '../init'; | ||||||
|  | 
 | ||||||
|  | document.title = 'Misskey Statistics'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * init | ||||||
|  |  */ | ||||||
|  | init(me => { | ||||||
|  | 	mount(document.createElement('mk-index')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function mount(content) { | ||||||
|  | 	riot.mount(document.getElementById('app').appendChild(content)); | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/web/app/stats/style.styl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/web/app/stats/style.styl
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | @import "../base" | ||||||
|  | 
 | ||||||
|  | html | ||||||
|  | 	color #456267 | ||||||
|  | 	background #fff | ||||||
|  | 
 | ||||||
|  | body | ||||||
|  | 	margin 0 | ||||||
|  | 	padding 0 | ||||||
							
								
								
									
										1
									
								
								src/web/app/stats/tags/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/web/app/stats/tags/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | require('./index.tag'); | ||||||
							
								
								
									
										202
									
								
								src/web/app/stats/tags/index.tag
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/web/app/stats/tags/index.tag
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,202 @@ | ||||||
|  | <mk-index> | ||||||
|  | 	<h1>Misskey<i>Statistics</i></h1> | ||||||
|  | 	<main if={ !initializing }> | ||||||
|  | 		<mk-users stats={ stats }/> | ||||||
|  | 		<mk-posts stats={ stats }/> | ||||||
|  | 	</main> | ||||||
|  | 	<footer><a href={ CONFIG.url }>{ CONFIG.host }</a></footer> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 			margin 0 auto | ||||||
|  | 			padding 0 16px | ||||||
|  | 			max-width 700px | ||||||
|  | 
 | ||||||
|  | 			> h1 | ||||||
|  | 				margin 0 | ||||||
|  | 				padding 24px 0 0 0 | ||||||
|  | 				font-size 24px | ||||||
|  | 				font-weight normal | ||||||
|  | 
 | ||||||
|  | 				> i | ||||||
|  | 					font-style normal | ||||||
|  | 					color #f43b16 | ||||||
|  | 
 | ||||||
|  | 			> main | ||||||
|  | 				> * | ||||||
|  | 					margin 24px 0 | ||||||
|  | 					padding-top 24px | ||||||
|  | 					border-top solid 1px #eee | ||||||
|  | 
 | ||||||
|  | 					> h2 | ||||||
|  | 						margin 0 0 12px 0 | ||||||
|  | 						font-size 18px | ||||||
|  | 						font-weight normal | ||||||
|  | 
 | ||||||
|  | 			> footer | ||||||
|  | 				margin 24px 0 | ||||||
|  | 				text-align center | ||||||
|  | 
 | ||||||
|  | 				> a | ||||||
|  | 					color #546567 | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.mixin('api'); | ||||||
|  | 
 | ||||||
|  | 		this.initializing = true; | ||||||
|  | 
 | ||||||
|  | 		this.on('mount', () => { | ||||||
|  | 			this.api('stats').then(stats => { | ||||||
|  | 				this.update({ | ||||||
|  | 					initializing: false, | ||||||
|  | 					stats | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	</script> | ||||||
|  | </mk-index> | ||||||
|  | 
 | ||||||
|  | <mk-posts> | ||||||
|  | 	<h2>%i18n:stats.posts-count% <b>{ stats.posts_count }</b></h2> | ||||||
|  | 	<mk-posts-chart if={ !initializing } data={ data }/> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.mixin('api'); | ||||||
|  | 
 | ||||||
|  | 		this.initializing = true; | ||||||
|  | 		this.stats = this.opts.stats; | ||||||
|  | 
 | ||||||
|  | 		this.on('mount', () => { | ||||||
|  | 			this.api('aggregation/posts', { | ||||||
|  | 				limit: 365 | ||||||
|  | 			}).then(data => { | ||||||
|  | 				this.update({ | ||||||
|  | 					initializing: false, | ||||||
|  | 					data | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	</script> | ||||||
|  | </mk-posts> | ||||||
|  | 
 | ||||||
|  | <mk-users> | ||||||
|  | 	<h2>%i18n:stats.users-count% <b>{ stats.users_count }</b></h2> | ||||||
|  | 	<mk-users-chart if={ !initializing } data={ data }/> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.mixin('api'); | ||||||
|  | 
 | ||||||
|  | 		this.initializing = true; | ||||||
|  | 		this.stats = this.opts.stats; | ||||||
|  | 
 | ||||||
|  | 		this.on('mount', () => { | ||||||
|  | 			this.api('aggregation/users', { | ||||||
|  | 				limit: 365 | ||||||
|  | 			}).then(data => { | ||||||
|  | 				this.update({ | ||||||
|  | 					initializing: false, | ||||||
|  | 					data | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	</script> | ||||||
|  | </mk-users> | ||||||
|  | 
 | ||||||
|  | <mk-posts-chart> | ||||||
|  | 	<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> | ||||||
|  | 		<title>Black ... Total<br/>Blue ... Posts<br/>Red ... Replies<br/>Green ... Reposts</title> | ||||||
|  | 		<polyline | ||||||
|  | 			riot-points={ pointsPost } | ||||||
|  | 			fill="none" | ||||||
|  | 			stroke-width="1" | ||||||
|  | 			stroke="#41ddde"/> | ||||||
|  | 		<polyline | ||||||
|  | 			riot-points={ pointsReply } | ||||||
|  | 			fill="none" | ||||||
|  | 			stroke-width="1" | ||||||
|  | 			stroke="#f7796c"/> | ||||||
|  | 		<polyline | ||||||
|  | 			riot-points={ pointsRepost } | ||||||
|  | 			fill="none" | ||||||
|  | 			stroke-width="1" | ||||||
|  | 			stroke="#a1de41"/> | ||||||
|  | 		<polyline | ||||||
|  | 			riot-points={ pointsTotal } | ||||||
|  | 			fill="none" | ||||||
|  | 			stroke-width="1" | ||||||
|  | 			stroke="#555" | ||||||
|  | 			stroke-dasharray="2 2"/> | ||||||
|  | 	</svg> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 
 | ||||||
|  | 			> svg | ||||||
|  | 				display block | ||||||
|  | 				padding 1px | ||||||
|  | 				width 100% | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.viewBoxX = 365; | ||||||
|  | 		this.viewBoxY = 60; | ||||||
|  | 
 | ||||||
|  | 		this.data = this.opts.data.reverse(); | ||||||
|  | 		this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); | ||||||
|  | 		const peak = Math.max.apply(null, this.data.map(d => d.total)); | ||||||
|  | 
 | ||||||
|  | 		this.on('mount', () => { | ||||||
|  | 			this.render(); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		this.render = () => { | ||||||
|  | 			this.update({ | ||||||
|  | 				pointsPost: this.data.map((d, i) => `${i},${(1 - (d.posts / peak)) * this.viewBoxY}`).join(' '), | ||||||
|  | 				pointsReply: this.data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '), | ||||||
|  | 				pointsRepost: this.data.map((d, i) => `${i},${(1 - (d.reposts / peak)) * this.viewBoxY}`).join(' '), | ||||||
|  | 				pointsTotal: this.data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ') | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  | 	</script> | ||||||
|  | </mk-posts-chart> | ||||||
|  | 
 | ||||||
|  | <mk-users-chart> | ||||||
|  | 	<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> | ||||||
|  | 		<polyline | ||||||
|  | 			riot-points={ points } | ||||||
|  | 			fill="none" | ||||||
|  | 			stroke-width="1" | ||||||
|  | 			stroke="#555"/> | ||||||
|  | 	</svg> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 
 | ||||||
|  | 			> svg | ||||||
|  | 				display block | ||||||
|  | 				padding 1px | ||||||
|  | 				width 100% | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.viewBoxX = 365; | ||||||
|  | 		this.viewBoxY = 60; | ||||||
|  | 
 | ||||||
|  | 		this.data = this.opts.data.reverse(); | ||||||
|  | 		const peak = Math.max.apply(null, this.data.map(d => d.count)); | ||||||
|  | 
 | ||||||
|  | 		this.on('mount', () => { | ||||||
|  | 			this.render(); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		this.render = () => { | ||||||
|  | 			this.update({ | ||||||
|  | 				points: this.data.map((d, i) => `${i},${(1 - (d.count / peak)) * this.viewBoxY}`).join(' ') | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  | 	</script> | ||||||
|  | </mk-users-chart> | ||||||
|  | @ -56,7 +56,6 @@ | ||||||
| 		this.mixin('api'); | 		this.mixin('api'); | ||||||
| 
 | 
 | ||||||
| 		this.initializing = true; | 		this.initializing = true; | ||||||
| 		this.view = 0; |  | ||||||
| 		this.connection = new Connection(); | 		this.connection = new Connection(); | ||||||
| 
 | 
 | ||||||
| 		this.on('mount', () => { | 		this.on('mount', () => { | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ module.exports = langs.map(([lang, locale]) => { | ||||||
| 	const entry = { | 	const entry = { | ||||||
| 		desktop: './src/web/app/desktop/script.js', | 		desktop: './src/web/app/desktop/script.js', | ||||||
| 		mobile: './src/web/app/mobile/script.js', | 		mobile: './src/web/app/mobile/script.js', | ||||||
|  | 		stats: './src/web/app/stats/script.js', | ||||||
| 		status: './src/web/app/status/script.js', | 		status: './src/web/app/status/script.js', | ||||||
| 		dev: './src/web/app/dev/script.js', | 		dev: './src/web/app/dev/script.js', | ||||||
| 		auth: './src/web/app/auth/script.js' | 		auth: './src/web/app/auth/script.js' | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue