2018-12-17 16:37:19 +01:00
'use strict' ;
exports = module . exports = {
NotificationsError : NotificationsError ,
add : add ,
2019-02-06 15:47:31 +01:00
upsert : upsert ,
2018-12-17 16:37:19 +01:00
get : get ,
ack : ack ,
2019-01-04 17:13:52 +01:00
getAllPaged : getAllPaged ,
2018-12-17 17:35:19 +01:00
// specialized notifications
2019-01-07 12:57:57 +01:00
userAdded : userAdded ,
2019-01-10 12:00:04 +01:00
userRemoved : userRemoved ,
adminChanged : adminChanged ,
2019-01-07 13:01:27 +01:00
oomEvent : oomEvent ,
2019-01-10 13:57:47 +01:00
appDied : appDied ,
2019-01-19 13:31:31 +01:00
processCrash : processCrash ,
2019-02-06 15:47:31 +01:00
apptaskCrash : apptaskCrash ,
backupConfigWarning : backupConfigWarning ,
diskSpaceWarning : diskSpaceWarning
2018-12-17 16:37:19 +01:00
} ;
var assert = require ( 'assert' ) ,
2018-12-17 17:35:19 +01:00
async = require ( 'async' ) ,
2019-01-10 12:00:04 +01:00
config = require ( './config.js' ) ,
2018-12-17 16:37:19 +01:00
DatabaseError = require ( './databaseerror.js' ) ,
debug = require ( 'debug' ) ( 'box:notifications' ) ,
2018-12-17 17:35:19 +01:00
mailer = require ( './mailer.js' ) ,
2018-12-17 16:37:19 +01:00
notificationdb = require ( './notificationdb.js' ) ,
2019-01-19 13:22:29 +01:00
safe = require ( 'safetydance' ) ,
2018-12-17 17:35:19 +01:00
users = require ( './users.js' ) ,
2018-12-17 16:37:19 +01:00
util = require ( 'util' ) ;
function NotificationsError ( reason , errorOrMessage ) {
assert . strictEqual ( typeof reason , 'string' ) ;
assert ( errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined' ) ;
Error . call ( this ) ;
Error . captureStackTrace ( this , this . constructor ) ;
this . name = this . constructor . name ;
this . reason = reason ;
if ( typeof errorOrMessage === 'undefined' ) {
this . message = reason ;
} else if ( typeof errorOrMessage === 'string' ) {
this . message = errorOrMessage ;
} else {
this . message = 'Internal error' ;
this . nestedError = errorOrMessage ;
}
}
util . inherits ( NotificationsError , Error ) ;
NotificationsError . INTERNAL _ERROR = 'Internal Error' ;
NotificationsError . NOT _FOUND = 'Not Found' ;
2019-01-19 13:22:29 +01:00
function add ( userId , eventId , title , message , action , callback ) {
2018-12-17 16:37:19 +01:00
assert . strictEqual ( typeof userId , 'string' ) ;
2019-02-06 16:18:12 +01:00
assert ( typeof eventId === 'string' || eventId === null ) ;
2018-12-17 16:37:19 +01:00
assert . strictEqual ( typeof title , 'string' ) ;
assert . strictEqual ( typeof message , 'string' ) ;
assert . strictEqual ( typeof action , 'string' ) ;
2018-12-17 17:40:53 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-17 16:37:19 +01:00
2018-12-17 17:35:19 +01:00
debug ( 'add: ' , userId , title , action ) ;
2018-12-17 16:37:19 +01:00
notificationdb . add ( {
userId : userId ,
2019-01-19 13:22:29 +01:00
eventId : eventId ,
2018-12-17 16:37:19 +01:00
title : title ,
message : message ,
action : action
} , function ( error , result ) {
2019-01-21 08:51:04 +01:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND , error . message ) ) ;
2018-12-17 16:37:19 +01:00
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null , { id : result } ) ;
} ) ;
}
2019-02-06 15:47:31 +01:00
function upsert ( userId , eventId , title , message , action , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
2019-02-06 16:18:12 +01:00
assert ( typeof eventId === 'string' || eventId === null ) ;
2019-02-06 15:47:31 +01:00
assert . strictEqual ( typeof title , 'string' ) ;
assert . strictEqual ( typeof message , 'string' ) ;
assert . strictEqual ( typeof action , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'upsert: ' , userId , title , action ) ;
notificationdb . upsert ( {
userId : userId ,
eventId : eventId ,
title : title ,
message : message ,
action : action
} , function ( error , result ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND , error . message ) ) ;
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null , { id : result } ) ;
} ) ;
}
2018-12-17 16:37:19 +01:00
function get ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
notificationdb . get ( id , function ( error , result ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND ) ) ;
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null , result ) ;
} ) ;
}
function ack ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
notificationdb . update ( id , { acknowledged : true } , function ( error ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new NotificationsError ( NotificationsError . NOT _FOUND ) ) ;
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
// if acknowledged === null we return all, otherwise yes or no based on acknowledged as a boolean
2019-01-04 17:13:52 +01:00
function getAllPaged ( userId , acknowledged , page , perPage , callback ) {
2018-12-17 16:37:19 +01:00
assert . strictEqual ( typeof userId , 'string' ) ;
assert ( acknowledged === null || typeof acknowledged === 'boolean' ) ;
assert . strictEqual ( typeof page , 'number' ) ;
assert . strictEqual ( typeof perPage , 'number' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
notificationdb . listByUserIdPaged ( userId , page , perPage , function ( error , result ) {
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
if ( acknowledged === null ) return callback ( null , result ) ;
callback ( null , result . filter ( function ( r ) { return r . acknowledged === acknowledged ; } ) ) ;
} ) ;
}
2018-12-17 17:35:19 +01:00
2019-01-07 14:56:43 +01:00
// Calls iterator with (admin, callback)
2019-01-17 13:36:54 +01:00
function actionForAllAdmins ( skippingUserIds , iterator , callback ) {
assert ( Array . isArray ( skippingUserIds ) ) ;
2019-01-07 14:56:43 +01:00
assert . strictEqual ( typeof iterator , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
users . getAllAdmins ( function ( error , result ) {
if ( error ) return callback ( new NotificationsError ( NotificationsError . INTERNAL _ERROR , error ) ) ;
2019-01-17 13:36:54 +01:00
// filter out users we want to skip (like the user who did the action or the user the action was performed on)
result = result . filter ( function ( r ) { return skippingUserIds . indexOf ( r . id ) === - 1 ; } ) ;
2019-01-07 14:56:43 +01:00
async . each ( result , iterator , callback ) ;
} ) ;
}
2019-01-19 13:22:29 +01:00
function userAdded ( performedBy , eventId , user ) {
2019-01-17 13:36:54 +01:00
assert . strictEqual ( typeof performedBy , 'string' ) ;
2019-01-19 13:22:29 +01:00
assert . strictEqual ( typeof eventId , 'string' ) ;
2018-12-17 17:35:19 +01:00
assert . strictEqual ( typeof user , 'object' ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ performedBy , user . id ] , function ( admin , callback ) {
2019-01-07 14:56:43 +01:00
mailer . userAdded ( admin . email , user ) ;
2019-01-19 13:22:29 +01:00
add ( admin . id , eventId , 'User added' , ` User ${ user . fallbackEmail } was added ` , '/#/users' , callback ) ;
2019-01-17 13:12:26 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2018-12-17 17:35:19 +01:00
}
2019-01-07 12:57:57 +01:00
2019-01-19 13:22:29 +01:00
function userRemoved ( performedBy , eventId , user ) {
2019-01-17 13:36:54 +01:00
assert . strictEqual ( typeof performedBy , 'string' ) ;
2019-01-19 13:22:29 +01:00
assert . strictEqual ( typeof eventId , 'string' ) ;
2019-01-10 12:00:04 +01:00
assert . strictEqual ( typeof user , 'object' ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ performedBy , user . id ] , function ( admin , callback ) {
2019-01-10 12:00:04 +01:00
mailer . userRemoved ( admin . email , user ) ;
2019-01-19 13:27:45 +01:00
add ( admin . id , eventId , 'User removed' , ` User ${ user . username || user . email || user . fallbackEmail } was removed ` , '/#/users' , callback ) ;
2019-01-17 13:12:26 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-10 12:00:04 +01:00
}
2019-01-19 13:22:29 +01:00
function adminChanged ( performedBy , eventId , user ) {
2019-01-17 13:36:54 +01:00
assert . strictEqual ( typeof performedBy , 'string' ) ;
2019-01-10 12:00:04 +01:00
assert . strictEqual ( typeof user , 'object' ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ performedBy , user . id ] , function ( admin , callback ) {
2019-01-17 13:12:26 +01:00
mailer . adminChanged ( admin . email , user , user . admin ) ;
2019-01-19 13:27:45 +01:00
add ( admin . id , eventId , 'Admin status change' , ` User ${ user . username || user . email || user . fallbackEmail } ${ user . admin ? 'is now an admin' : 'is no more an admin' } ` , '/#/users' , callback ) ;
2019-01-17 13:12:26 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-10 12:00:04 +01:00
}
2019-01-19 13:22:29 +01:00
function oomEvent ( eventId , program , context ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
2019-01-07 12:57:57 +01:00
assert . strictEqual ( typeof program , 'string' ) ;
2019-01-08 14:20:08 +01:00
assert . strictEqual ( typeof context , 'object' ) ;
2019-01-07 12:57:57 +01:00
2019-01-10 12:00:04 +01:00
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . oomEvent ( 'support@cloudron.io' , program , JSON . stringify ( context , null , 4 ) ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-01-10 12:00:04 +01:00
mailer . oomEvent ( admin . email , program , JSON . stringify ( context , null , 4 ) ) ;
2019-01-08 14:20:08 +01:00
var message ;
if ( context . app ) message = ` The application ${ context . app . manifest . title } with id ${ context . app . id } ran out of memory. ` ;
else message = ` The container with id ${ context . details . id } ran out of memory ` ;
2019-01-19 13:27:45 +01:00
add ( admin . id , eventId , 'Process died out-of-memory' , message , context . app ? '/#/apps' : '' , callback ) ;
2019-01-17 15:31:34 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-07 12:57:57 +01:00
}
2019-01-07 13:01:27 +01:00
2019-01-19 13:22:29 +01:00
function appDied ( eventId , app ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
2019-01-07 13:01:27 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-01-10 12:00:04 +01:00
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . appDied ( 'support@cloudron.io' , app ) ;
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-01-10 12:00:04 +01:00
mailer . appDied ( admin . email , app ) ;
2019-01-19 13:27:45 +01:00
add ( admin . id , eventId , ` App ${ app . fqdn } is down ` , ` The application ${ app . manifest . title } installed at ${ app . fqdn } is not responding. ` , '/#/apps' , callback ) ;
2019-01-17 15:49:04 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-07 13:01:27 +01:00
}
2019-01-10 13:57:47 +01:00
2019-01-19 13:31:31 +01:00
function processCrash ( eventId , processName , crashLogFile ) {
2019-01-19 13:22:29 +01:00
assert . strictEqual ( typeof eventId , 'string' ) ;
assert . strictEqual ( typeof processName , 'string' ) ;
assert . strictEqual ( typeof crashLogFile , 'string' ) ;
2019-01-10 13:57:47 +01:00
2019-01-19 13:22:29 +01:00
var subject = ` ${ processName } exited unexpectedly ` ;
2019-01-19 15:23:54 +01:00
var crashLogs = safe . fs . readFileSync ( crashLogFile , 'utf8' ) || ` No logs found at ${ crashLogFile } ` ;
2019-01-10 13:57:47 +01:00
// also send us a notification mail
2019-01-19 13:22:29 +01:00
if ( config . provider ( ) === 'caas' ) mailer . unexpectedExit ( 'support@cloudron.io' , subject , crashLogs ) ;
2019-01-10 13:57:47 +01:00
2019-01-17 13:36:54 +01:00
actionForAllAdmins ( [ ] , function ( admin , callback ) {
2019-01-19 13:22:29 +01:00
mailer . unexpectedExit ( admin . email , subject , crashLogs ) ;
2019-01-19 13:27:45 +01:00
add ( admin . id , eventId , subject , 'Detailed logs have been sent to your email address.' , '/#/system' , callback ) ;
2019-01-19 13:22:29 +01:00
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
2019-01-10 13:57:47 +01:00
}
2019-01-19 13:30:24 +01:00
function apptaskCrash ( eventId , appId , crashLogFile ) {
assert . strictEqual ( typeof eventId , 'string' ) ;
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof crashLogFile , 'string' ) ;
var subject = ` Apptask for ${ appId } crashed ` ;
2019-01-19 15:23:54 +01:00
var crashLogs = safe . fs . readFileSync ( crashLogFile , 'utf8' ) || ` No logs found at ${ crashLogFile } ` ;
2019-01-19 13:30:24 +01:00
// also send us a notification mail
if ( config . provider ( ) === 'caas' ) mailer . unexpectedExit ( 'support@cloudron.io' , subject , crashLogs ) ;
actionForAllAdmins ( [ ] , function ( admin , callback ) {
mailer . unexpectedExit ( admin . email , subject , crashLogs ) ;
add ( admin . id , eventId , subject , 'Detailed logs have been sent to your email address.' , '/#/system' , callback ) ;
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
}
2019-02-06 15:47:31 +01:00
function backupConfigWarning ( message ) {
assert . strictEqual ( typeof message , 'string' ) ;
actionForAllAdmins ( [ ] , function ( admin , callback ) {
upsert ( admin . id , null , 'Backup Configuration' , message , '/#/backups' , callback ) ;
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
}
function diskSpaceWarning ( message ) {
assert . strictEqual ( typeof message , 'string' ) ;
actionForAllAdmins ( [ ] , function ( admin , callback ) {
mailer . outOfDiskSpace ( admin . email , message ) ;
upsert ( admin . id , null , 'Out of Disk Space' , message , '/#/graphs' , callback ) ;
} , function ( error ) {
if ( error ) console . error ( error ) ;
} ) ;
}