Add access log widget
This commit is contained in:
		
							parent
							
								
									bc9a8283c6
								
							
						
					
					
						commit
						0a994e5b98
					
				
					 12 changed files with 181 additions and 0 deletions
				
			
		|  | @ -390,6 +390,9 @@ desktop: | ||||||
|       post: "Post" |       post: "Post" | ||||||
|       placeholder: "What's happening?" |       placeholder: "What's happening?" | ||||||
| 
 | 
 | ||||||
|  |     mk-access-log-home-widget: | ||||||
|  |       title: "Access log" | ||||||
|  | 
 | ||||||
|     mk-repost-form: |     mk-repost-form: | ||||||
|       quote: "Quote..." |       quote: "Quote..." | ||||||
|       cancel: "Cancel" |       cancel: "Cancel" | ||||||
|  |  | ||||||
|  | @ -390,6 +390,9 @@ desktop: | ||||||
|       post: "投稿" |       post: "投稿" | ||||||
|       placeholder: "いまどうしてる?" |       placeholder: "いまどうしてる?" | ||||||
| 
 | 
 | ||||||
|  |     mk-access-log-home-widget: | ||||||
|  |       title: "アクセスログ" | ||||||
|  | 
 | ||||||
|     mk-repost-form: |     mk-repost-form: | ||||||
|       quote: "引用する..." |       quote: "引用する..." | ||||||
|       cancel: "キャンセル" |       cancel: "キャンセル" | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								src/api/stream/requests.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/api/stream/requests.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | import * as websocket from 'websocket'; | ||||||
|  | import Xev from 'xev'; | ||||||
|  | 
 | ||||||
|  | const ev = new Xev(); | ||||||
|  | 
 | ||||||
|  | export default function homeStream(request: websocket.request, connection: websocket.connection): void { | ||||||
|  | 	const onRequest = request => { | ||||||
|  | 		connection.send(JSON.stringify({ | ||||||
|  | 			type: 'request', | ||||||
|  | 			body: request | ||||||
|  | 		})); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	ev.addListener('request', onRequest); | ||||||
|  | 
 | ||||||
|  | 	connection.on('close', () => { | ||||||
|  | 		ev.removeListener('request', onRequest); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | @ -9,6 +9,7 @@ import isNativeToken from './common/is-native-token'; | ||||||
| import homeStream from './stream/home'; | import homeStream from './stream/home'; | ||||||
| import messagingStream from './stream/messaging'; | import messagingStream from './stream/messaging'; | ||||||
| import serverStream from './stream/server'; | import serverStream from './stream/server'; | ||||||
|  | import requestsStream from './stream/requests'; | ||||||
| import channelStream from './stream/channel'; | import channelStream from './stream/channel'; | ||||||
| 
 | 
 | ||||||
| module.exports = (server: http.Server) => { | module.exports = (server: http.Server) => { | ||||||
|  | @ -27,6 +28,11 @@ module.exports = (server: http.Server) => { | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if (request.resourceURL.pathname === '/requests') { | ||||||
|  | 			requestsStream(request, connection); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		// Connect to Redis
 | 		// Connect to Redis
 | ||||||
| 		const subscriber = redis.createClient( | 		const subscriber = redis.createClient( | ||||||
| 			config.redis.port, config.redis.host); | 			config.redis.port, config.redis.host); | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								src/log-request.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/log-request.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | import * as crypto from 'crypto'; | ||||||
|  | import * as express from 'express'; | ||||||
|  | import * as proxyAddr from 'proxy-addr'; | ||||||
|  | import Xev from 'xev'; | ||||||
|  | 
 | ||||||
|  | const ev = new Xev(); | ||||||
|  | 
 | ||||||
|  | export default function(req: express.Request) { | ||||||
|  | 	const ip = proxyAddr(req, () => true); | ||||||
|  | 
 | ||||||
|  | 	const md5 = crypto.createHash('md5'); | ||||||
|  | 	md5.update(ip); | ||||||
|  | 	const hashedIp = md5.digest('hex').substr(0, 3); | ||||||
|  | 
 | ||||||
|  | 	ev.emit('request', { | ||||||
|  | 		ip: hashedIp, | ||||||
|  | 		method: req.method, | ||||||
|  | 		hostname: req.hostname, | ||||||
|  | 		path: req.originalUrl | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | @ -11,6 +11,7 @@ import * as morgan from 'morgan'; | ||||||
| import Accesses from 'accesses'; | import Accesses from 'accesses'; | ||||||
| import vhost = require('vhost'); | import vhost = require('vhost'); | ||||||
| 
 | 
 | ||||||
|  | import log from './log-request'; | ||||||
| import config from './conf'; | import config from './conf'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -35,6 +36,11 @@ app.use(morgan(process.env.NODE_ENV == 'production' ? 'combined' : 'dev', { | ||||||
| 	stream: config.accesslog ? fs.createWriteStream(config.accesslog) : null | 	stream: config.accesslog ? fs.createWriteStream(config.accesslog) : null | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
|  | app.use((req, res, next) => { | ||||||
|  | 	log(req); | ||||||
|  | 	next(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| // Drop request when without 'Host' header
 | // Drop request when without 'Host' header
 | ||||||
| app.use((req, res, next) => { | app.use((req, res, next) => { | ||||||
| 	if (!req.headers['host']) { | 	if (!req.headers['host']) { | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import * as riot from 'riot'; | ||||||
| import activateMe from './i'; | import activateMe from './i'; | ||||||
| import activateApi from './api'; | import activateApi from './api'; | ||||||
| import ServerStreamManager from '../scripts/server-stream-manager'; | import ServerStreamManager from '../scripts/server-stream-manager'; | ||||||
|  | import RequestsStreamManager from '../scripts/requests-stream-manager'; | ||||||
| 
 | 
 | ||||||
| export default (me, stream) => { | export default (me, stream) => { | ||||||
| 	activateMe(me); | 	activateMe(me); | ||||||
|  | @ -11,4 +12,5 @@ export default (me, stream) => { | ||||||
| 	(riot as any).mixin('stream', { stream }); | 	(riot as any).mixin('stream', { stream }); | ||||||
| 
 | 
 | ||||||
| 	(riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); | 	(riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); | ||||||
|  | 	(riot as any).mixin('requests-stream', { requestsStream: new RequestsStreamManager() }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								src/web/app/common/scripts/requests-stream-manager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/web/app/common/scripts/requests-stream-manager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import StreamManager from './stream-manager'; | ||||||
|  | import Connection from './requests-stream'; | ||||||
|  | 
 | ||||||
|  | export default class RequestsStreamManager extends StreamManager<Connection> { | ||||||
|  | 	public getConnection() { | ||||||
|  | 		if (this.connection == null) { | ||||||
|  | 			this.connection = new Connection(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return this.connection; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/web/app/common/scripts/requests-stream.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/web/app/common/scripts/requests-stream.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | import Stream from './stream'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Requests stream connection | ||||||
|  |  */ | ||||||
|  | class Connection extends Stream { | ||||||
|  | 	constructor() { | ||||||
|  | 		super('requests'); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Connection; | ||||||
							
								
								
									
										93
									
								
								src/web/app/desktop/tags/home-widgets/access-log.tag
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/web/app/desktop/tags/home-widgets/access-log.tag
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,93 @@ | ||||||
|  | <mk-access-log-home-widget> | ||||||
|  | 	<virtual if={ data.design == 0 }> | ||||||
|  | 		<p class="title"><i class="fa fa-server"></i>%i18n:desktop.tags.mk-access-log-home-widget.title%</p> | ||||||
|  | 	</virtual> | ||||||
|  | 	<div ref="log"> | ||||||
|  | 		<p each={ requests }> | ||||||
|  | 			<span class="ip" style="color:{ fg }; background:{ bg }">{ ip }</span> | ||||||
|  | 			<span>{ method }</span> | ||||||
|  | 			<span>{ path }</span> | ||||||
|  | 		</p> | ||||||
|  | 	</div> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 			overflow hidden | ||||||
|  | 			background #fff | ||||||
|  | 
 | ||||||
|  | 			> .title | ||||||
|  | 				z-index 1 | ||||||
|  | 				margin 0 | ||||||
|  | 				padding 0 16px | ||||||
|  | 				line-height 42px | ||||||
|  | 				font-size 0.9em | ||||||
|  | 				font-weight bold | ||||||
|  | 				color #888 | ||||||
|  | 				box-shadow 0 1px rgba(0, 0, 0, 0.07) | ||||||
|  | 
 | ||||||
|  | 				> i | ||||||
|  | 					margin-right 4px | ||||||
|  | 
 | ||||||
|  | 			> div | ||||||
|  | 				max-height 250px | ||||||
|  | 				overflow auto | ||||||
|  | 
 | ||||||
|  | 				> p | ||||||
|  | 					margin 0 | ||||||
|  | 					padding 8px | ||||||
|  | 					font-size 0.8em | ||||||
|  | 					color #555 | ||||||
|  | 
 | ||||||
|  | 					&:nth-child(odd) | ||||||
|  | 						background rgba(0, 0, 0, 0.025) | ||||||
|  | 
 | ||||||
|  | 					> .ip | ||||||
|  | 						margin-right 4px | ||||||
|  | 
 | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		import seedrandom from 'seedrandom'; | ||||||
|  | 
 | ||||||
|  | 		this.data = { | ||||||
|  | 			design: 0 | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		this.mixin('widget'); | ||||||
|  | 
 | ||||||
|  | 		this.mixin('requests-stream'); | ||||||
|  | 		this.connection = this.requestsStream.getConnection(); | ||||||
|  | 		this.connectionId = this.requestsStream.use(); | ||||||
|  | 
 | ||||||
|  | 		this.requests = []; | ||||||
|  | 
 | ||||||
|  | 		this.on('mount', () => { | ||||||
|  | 			this.connection.on('request', this.onRequest); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		this.on('unmount', () => { | ||||||
|  | 			this.connection.off('request', this.onRequest); | ||||||
|  | 			this.requestsStream.dispose(this.connectionId); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		this.onRequest = request => { | ||||||
|  | 			const random = seedrandom(request.ip); | ||||||
|  | 			const r = Math.floor(random() * 255); | ||||||
|  | 			const g = Math.floor(random() * 255); | ||||||
|  | 			const b = Math.floor(random() * 255); | ||||||
|  | 			const luma = (0.2126 * r) + (0.7152 * g) + (0.0722 * b); // SMPTE C, Rec. 709 weightings | ||||||
|  | 			request.bg = `rgb(${r}, ${g}, ${b})`; | ||||||
|  | 			request.fg = luma >= 165 ? '#000' : '#fff'; | ||||||
|  | 
 | ||||||
|  | 			this.requests.push(request); | ||||||
|  | 			if (this.requests.length > 30) this.requests.shift(); | ||||||
|  | 			this.update(); | ||||||
|  | 
 | ||||||
|  | 			this.refs.log.scrollTop = this.refs.log.scrollHeight; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		this.func = () => { | ||||||
|  | 			if (++this.data.design == 2) this.data.design = 0; | ||||||
|  | 			this.save(); | ||||||
|  | 		}; | ||||||
|  | 	</script> | ||||||
|  | </mk-access-log-home-widget> | ||||||
|  | @ -20,6 +20,7 @@ | ||||||
| 					<option value="recommended-polls">投票</option> | 					<option value="recommended-polls">投票</option> | ||||||
| 					<option value="post-form">投稿フォーム</option> | 					<option value="post-form">投稿フォーム</option> | ||||||
| 					<option value="channel">チャンネル</option> | 					<option value="channel">チャンネル</option> | ||||||
|  | 					<option value="access-log">アクセスログ</option> | ||||||
| 					<option value="server">サーバー情報</option> | 					<option value="server">サーバー情報</option> | ||||||
| 					<option value="donation">寄付のお願い</option> | 					<option value="donation">寄付のお願い</option> | ||||||
| 					<option value="nav">ナビゲーション</option> | 					<option value="nav">ナビゲーション</option> | ||||||
|  |  | ||||||
|  | @ -43,6 +43,7 @@ require('./home-widgets/slideshow.tag'); | ||||||
| require('./home-widgets/channel.tag'); | require('./home-widgets/channel.tag'); | ||||||
| require('./home-widgets/timemachine.tag'); | require('./home-widgets/timemachine.tag'); | ||||||
| require('./home-widgets/post-form.tag'); | require('./home-widgets/post-form.tag'); | ||||||
|  | require('./home-widgets/access-log.tag'); | ||||||
| require('./timeline.tag'); | require('./timeline.tag'); | ||||||
| require('./messaging/window.tag'); | require('./messaging/window.tag'); | ||||||
| require('./messaging/room-window.tag'); | require('./messaging/room-window.tag'); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue