1+ import path from 'path' ;
12import { Context , Schema } from 'cordis' ;
3+ import fs from 'fs-extra' ;
24import { BadRequestError , Handler } from '@hydrooj/framework' ;
5+ import { Logger } from '../utils' ;
36import { AuthHandler } from './misc' ;
47
8+ const logger = new Logger ( 'monitor' ) ;
9+ const actions = fs . createWriteStream ( path . join ( process . cwd ( ) , 'data/actions.log' ) , { flags : 'a' } ) ;
10+
511class MonitorAdminHandler extends AuthHandler {
612 async get ( params ) {
713 const { nogroup } = params ;
@@ -90,14 +96,23 @@ class MonitorAdminHandler extends AuthHandler {
9096 }
9197}
9298
93- async function saveMonitorInfo ( ctx : Context , monitor : any ) {
99+ const escape = ( str : string ) => str . trim ( ) . replace ( / " / g, '\\"' ) . replace ( / \r / g, '' ) . replace ( / \n / g, '\\n' ) ;
100+
101+ async function saveMonitorInfo ( ctx : Context , monitor : any , config ) {
94102 const {
95103 mac, version, uptime, seats, ip,
96104 os, kernel, cpu, cpuused, mem, memused, load,
105+ window_cmdline, window_exe, window_name,
97106 } = monitor ;
107+ logger . debug ( 'save monitor info %o' , monitor ) ;
108+ actions . write ( `${ Date . now ( ) } ,${ seats } ,"${ escape ( window_cmdline ) } ","${ escape ( window_exe ) } ","${ escape ( window_name ) } "\n` ) ;
98109 const monitors = await ctx . db . monitor . find ( { mac } ) ;
99110 const warn = monitors . length > 1 || ( monitors . length && monitors [ 0 ] . ip !== ip ) ;
100111 if ( warn ) ctx . logger ( 'monitor' ) . warn ( `Duplicate monitor ${ mac } from (${ ip } , ${ monitors . length ? monitors [ 0 ] . ip : 'null' } )` ) ;
112+ const autoGroupPayload = ( config . autoGroup && / ^ [ A - Z ] [ 0 - 9 ] + $ / . test ( seats ) ) ? {
113+ group : seats [ 0 ] ,
114+ name : seats ,
115+ } : { } ;
101116 await ctx . db . monitor . updateOne ( { mac } , {
102117 $set : {
103118 mac,
@@ -114,13 +129,15 @@ async function saveMonitorInfo(ctx: Context, monitor: any) {
114129 ...mem && { mem } ,
115130 ...mem && { memUsed : memused } ,
116131 ...load && { load } ,
132+ ...autoGroupPayload ,
117133 } ,
118134 } , { upsert : true } ) ;
119135}
120136
121137export const Config = Schema . object ( {
122138 timeSync : Schema . boolean ( ) . default ( false ) ,
123- } ) ;
139+ autoGroup : Schema . boolean ( ) . default ( false ) ,
140+ } ) . default ( { timeSync : false , autoGroup : false } ) ;
124141
125142export async function apply ( ctx : Context , config : ReturnType < typeof Config > ) {
126143 class MonitorReportHandler extends Handler {
@@ -132,8 +149,49 @@ export async function apply(ctx: Context, config: ReturnType<typeof Config>) {
132149 if ( ! params . mac ) throw new BadRequestError ( ) ;
133150 params . ip = this . request . ip . replace ( '::ffff:' , '' ) ;
134151 if ( params . mac === '00:00:00:00:00:00' ) throw new BadRequestError ( 'Invalid MAC address' ) ;
135- await saveMonitorInfo ( this . ctx , params ) ;
136- this . response . body = `#!/bin/bash\n${ config . timeSync ? `date "${ new Date ( ) . toISOString ( ) } "` : 'echo Success' } ` ;
152+ await saveMonitorInfo ( this . ctx , params , config ) ;
153+ if ( this . request . files ?. file ) {
154+ const resultContent = fs . readFileSync ( this . request . files . file . filepath , 'utf-8' ) ;
155+ const commandResults : Map < string , string > = new Map ( ) ;
156+ let currentCommandId : string | null = null ;
157+ let currentOutput : string [ ] = [ ] ;
158+ const lines = resultContent . split ( '\n' ) ;
159+ for ( const line of lines ) {
160+ const startMatch = line . match ( / ^ - - - C O M M A N D _ S T A R T : ( .+ ?) - - - $ / ) ;
161+ const endMatch = line . match ( / ^ - - - C O M M A N D _ E N D : ( .+ ?) - - - $ / ) ;
162+ if ( startMatch ) {
163+ currentCommandId = startMatch [ 1 ] ;
164+ currentOutput = [ ] ;
165+ } else if ( endMatch && currentCommandId === endMatch [ 1 ] ) {
166+ commandResults . set ( currentCommandId , currentOutput . join ( '\n' ) || '(No output)' ) ;
167+ currentCommandId = null ;
168+ currentOutput = [ ] ;
169+ } else if ( currentCommandId ) {
170+ currentOutput . push ( line ) ;
171+ }
172+ }
173+ if ( currentCommandId ) commandResults . set ( currentCommandId , currentOutput . join ( '\n' ) || '(No output)' ) ;
174+ await Promise . all ( Array . from ( commandResults . entries ( ) ) . map ( async ( [ commandId , output ] ) => {
175+ const cmd = await ctx . db . command . findOne ( { _id : commandId } ) ;
176+ if ( cmd ) {
177+ const executionResult = cmd . executionResult || { } ;
178+ executionResult [ params . mac ] = output ;
179+ const newPending = cmd . pending . filter ( ( t : string ) => t !== params . mac ) ;
180+ await ctx . db . command . updateOne ( { _id : commandId } , { $set : { executionResult, pending : newPending } } ) ;
181+ }
182+ } ) ) ;
183+ }
184+ const scriptParts : string [ ] = [
185+ '#!/bin/bash' ,
186+ config . timeSync ? `date --set="${ new Date ( ) . toISOString ( ) } "` : 'echo Time sync disabled' ,
187+ ] ;
188+ for ( const cmd of await ctx . db . command . find ( { pending : params . mac } ) ) {
189+ scriptParts . push ( `echo ---COMMAND_START:${ cmd . _id } ---` ) ;
190+ scriptParts . push ( cmd . command ) ;
191+ scriptParts . push ( `echo ---COMMAND_END:${ cmd . _id } ---` ) ;
192+ }
193+ this . response . body = `${ scriptParts . join ( '\n' ) } \n` ;
194+ this . response . type = 'text/x-shellscript' ;
137195 }
138196 }
139197 ctx . Route ( 'monitor_report' , '/report' , MonitorReportHandler ) ;
0 commit comments