1
0
Fork 0
mirror of https://gitlab.com/Shinobi-Systems/ShinobiCE.git synced 2025-03-09 15:40:15 +00:00

Blue Turtle - The Refactoring

This commit is contained in:
Moe 2018-10-24 21:42:47 -07:00
parent 04011678fb
commit b7d08eb500
67 changed files with 11651 additions and 8452 deletions

7936
camera.js

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
{
"port": 8080,
"passwordType": "sha256",
"addStorage": [
{"name":"second","path":"__DIR__/videos2"}
],

181
cron.js
View file

@ -57,59 +57,70 @@ s.sqlDate = function(value){
}else{
value = value.toUpperCase()
if (value.slice(-1) === 'S') {
value = value.slice(0, -1);
value = value.slice(0, -1);
}
dateQueryFunction = "DATE_SUB(NOW(), INTERVAL "+value+")"
}
return dateQueryFunction
}
s.sqlQuery = function(query,values,onMoveOn,hideLog){
s.debugLog(query,values)
s.mergeQueryValues = function(query,values){
if(!values){values=[]}
var valuesNotFunction = true;
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
valuesNotFunction = false;
}
if(!onMoveOn){onMoveOn=function(){}}
if(values&&valuesNotFunction){
var splitQuery = query.split('?')
var newQuery = ''
splitQuery.forEach(function(v,n){
newQuery += v
if(values[n]){
if(isNaN(values[n])){
newQuery += "'"+values[n]+"'"
var value = values[n]
if(value){
if(isNaN(value) || value instanceof Date){
newQuery += "'"+value+"'"
}else{
newQuery += values[n]
newQuery += value
}
}
})
}else{
newQuery = query
}
return s.databaseEngine.raw(newQuery)
.asCallback(function(err,r){
if(err&&config.databaseLogs){
s.systemLog('s.sqlQuery QUERY',query)
s.systemLog('s.sqlQuery ERROR',err)
return newQuery
}
s.stringToSqlTime = function(value){
newValue = new Date(value.replace('T',' '))
return newValue
}
s.sqlQuery = function(query,values,onMoveOn){
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(!onMoveOn){onMoveOn=function(){}}
var mergedQuery = s.mergeQueryValues(query,values)
s.debugLog('s.sqlQuery QUERY',mergedQuery)
return s.databaseEngine
.raw(query,values)
.asCallback(function(err,r){
if(err){
console.log('s.sqlQuery QUERY ERRORED',query)
console.log('s.sqlQuery ERROR',err)
}
if(onMoveOn && typeof onMoveOn === 'function'){
switch(databaseOptions.client){
case'sqlite3':
if(!r)r=[]
break;
default:
if(r)r=r[0]
break;
}
if(onMoveOn)
if(typeof onMoveOn === 'function'){
switch(databaseOptions.client){
case'sqlite3':
if(!r)r=[]
break;
default:
if(r)r=r[0]
break;
}
onMoveOn(err,r)
}else{
s.debugLog('onMoveOn',onMoveOn)
}
})
onMoveOn(err,r)
}
})
}
s.debugLog = function(arg1,arg2){
@ -145,15 +156,15 @@ s.utcToLocal = function(time){
s.localToUtc = function(time){
return moment(time).utc()
}
s.nameToTime=function(x){x=x.replace('.webm','').replace('.mp4','').split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
s.nameToTime = function(x){x=x.replace('.webm','').replace('.mp4','').split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
io = require('socket.io-client')('ws://'+config.ip+':'+config.port);//connect to master
s.cx=function(x){x.cronKey=config.cron.key;return io.emit('cron',x)}
s.cx = function(x){x.cronKey=config.cron.key;return io.emit('cron',x)}
//emulate master socket emitter
s.tx=function(x,y){s.cx({f:'s.tx',data:x,to:y})}
s.video=function(x,y){s.cx({f:'s.video',data:x,file:y})}
s.tx = function(x,y){s.cx({f:'s.tx',data:x,to:y})}
s.deleteVideo = function(x){s.cx({f:'s.deleteVideo',file:x})}
//Cron Job
s.cx({f:'init',time:moment()})
s.getVideoDirectory=function(e){
s.getVideoDirectory = function(e){
if(e.mid&&!e.id){e.id=e.mid};
if(e.details&&(e.details instanceof Object)===false){
try{e.details=JSON.parse(e.details)}catch(err){}
@ -164,19 +175,33 @@ s.getVideoDirectory=function(e){
return s.dir.videos+e.ke+'/'+e.id+'/';
}
}
s.getFileBinDirectory=function(e){
s.getFileBinDirectory = function(e){
if(e.mid&&!e.id){e.id=e.mid};
return s.dir.fileBin+e.ke+'/'+e.id+'/';
}
//filters set by the user in their dashboard
//deleting old videos is part of the filter - config.cron.deleteOld
s.checkFilterRules=function(v,callback){
s.checkFilterRules = function(v,callback){
//filters
if(!v.d.filters||v.d.filters==''){
v.d.filters={};
}
//delete old videos with filter
if(config.cron.deleteOld===true){
var where = [{
"p1":"end",
"p2":"<",
"p3":s.sqlDate(v.d.days+" DAYS"),
"p3_type":"function",
}]
//exclude monitors with their own max days
v.monitorsWithMaxKeepDays.forEach(function(mid){
where.push({
"p1":"mid",
"p2":"!=",
"p3":mid,
})
})
v.d.filters.deleteOldVideosByCron={
"id":"deleteOldVideosByCron",
"name":"deleteOldVideosByCron",
@ -188,12 +213,7 @@ s.checkFilterRules=function(v,callback){
"email":"0",
"delete":"1",
"execute":"",
"where":[{
"p1":"end",
"p2":"<",
"p3":s.sqlDate(v.d.days+" DAYS"),
"p3_type":"function",
}]
"where":where
};
}
s.debugLog('Filters')
@ -269,7 +289,7 @@ s.checkFilterRules=function(v,callback){
}
}
//database rows with no videos in the filesystem
s.deleteRowsWithNoVideo=function(v,callback){
s.deleteRowsWithNoVideo = function(v,callback){
if(
config.cron.deleteNoVideo===true&&(
config.cron.deleteNoVideoRecursion===true||
@ -301,7 +321,7 @@ s.deleteRowsWithNoVideo=function(v,callback){
}
fileExists = fs.existsSync(dir+filename)
if(fileExists !== true){
s.video('delete',ev)
s.deleteVideo(ev)
s.tx({f:'video_delete',filename:filename+'.'+ev.ext,mid:ev.mid,ke:ev.ke,time:ev.time,end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+ev.ke);
}
});
@ -318,7 +338,7 @@ s.deleteRowsWithNoVideo=function(v,callback){
}
}
//info about what the application is doing
s.deleteOldLogs=function(v,callback){
s.deleteOldLogs = function(v,callback){
if(!v.d.log_days||v.d.log_days==''){v.d.log_days=10}else{v.d.log_days=parseFloat(v.d.log_days)};
if(config.cron.deleteLogs===true&&v.d.log_days!==0){
s.sqlQuery("DELETE FROM Logs WHERE ke=? AND `time` < "+s.sqlDate('? DAYS'),[v.ke,v.d.log_days],function(err,rrr){
@ -333,7 +353,7 @@ s.deleteOldLogs=function(v,callback){
}
}
//events - motion, object, etc. detections
s.deleteOldEvents=function(v,callback){
s.deleteOldEvents = function(v,callback){
if(!v.d.event_days||v.d.event_days==''){v.d.event_days=10}else{v.d.event_days=parseFloat(v.d.event_days)};
if(config.cron.deleteEvents===true&&v.d.event_days!==0){
s.sqlQuery("DELETE FROM Events WHERE ke=? AND `time` < "+s.sqlDate('? DAYS'),[v.ke,v.d.event_days],function(err,rrr){
@ -348,10 +368,10 @@ s.deleteOldEvents=function(v,callback){
}
}
//check for temporary files (special archive)
s.deleteOldFileBins=function(v,callback){
s.deleteOldFileBins = function(v,callback){
if(!v.d.fileBin_days||v.d.fileBin_days==''){v.d.fileBin_days=10}else{v.d.fileBin_days=parseFloat(v.d.fileBin_days)};
if(config.cron.deleteFileBins===true&&v.d.fileBin_days!==0){
var fileBinQuery = " FROM Files WHERE ke=? AND `date` < "+s.sqlDate('? DAYS');
var fileBinQuery = " FROM Files WHERE ke=? AND `time` < "+s.sqlDate('? DAYS');
s.sqlQuery("SELECT *"+fileBinQuery,[v.ke,v.d.fileBin_days],function(err,files){
if(files&&files[0]){
//delete the files
@ -377,60 +397,12 @@ s.deleteOldFileBins=function(v,callback){
}
}
//check for files with no database row
s.checkForOrphanedFiles=function(v,callback){
if(config.cron.deleteOrphans===true){
var finish=function(count){
if(count>0 || config.debugLog === true){
s.cx({f:'deleteOrphanedFiles',msg:count+' SQL rows with no database row deleted',ke:v.ke,time:moment()})
}
callback()
}
e={};
var numberOfItems = 0;
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[v.ke],function(arr,b) {
if(b&&b[0]){
b.forEach(function(mon,m){
fs.readdir(s.getVideoDirectory(mon), function(err, items) {
e.query=[];
e.filesFound=[mon.ke,mon.mid];
numberOfItems+=items.length;
if(items&&items.length>0){
items.forEach(function(v,n){
e.query.push('time=?')
e.filesFound.push(s.nameToTime(v))
})
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND ('+e.query.join(' OR ')+')',e.filesFound,function(arr,r) {
if(!r){r=[]};
e.foundSQLrows=[];
r.forEach(function(v,n){
v.index=e.filesFound.indexOf(s.moment(v.time,'YYYY-MM-DD HH:mm:ss'));
if(v.index>-1){
delete(items[v.index-2]);
}
});
items.forEach(function(v,n){
if(v&&v!==null){
exec('rm '+s.getVideoDirectory(mon)+v);
}
if(m===b.length-1&&n===items.length-1){
finish(numberOfItems)
}
})
})
}else{
if(m===b.length-1){
finish(numberOfItems)
}
}
})
});
}else{
finish(numberOfItems)
}
});
}else{
callback()
s.checkForOrphanedFiles = function(v,callback){
if(config.cron.deleteOrphans === true){
console.log('"config.cron.deleteOrphans" has been removed. It has been replace by a one-time-run at startup with "config.insertOrphans". As the variable name suggests, instead of deleting, it will insert videos found without a database row.')
console.log('By default "config.orphanedVideoCheckMax" will only check up to 20 video. You can raise this value to any number you choose but be careful as it will check that number of videos on every start.')
}
callback()
}
//user processing function
s.processUser = function(number,rows){
@ -456,9 +428,11 @@ s.processUser = function(number,rows){
if(!v.d.filters||v.d.filters==''){
v.d.filters={};
}
v.monitorsWithMaxKeepDays = []
rr.forEach(function(b,m){
b.details=JSON.parse(b.details);
if(b.details.max_keep_days&&b.details.max_keep_days!==''){
v.monitorsWithMaxKeepDays.push(b.mid)
v.d.filters['deleteOldVideosByCron'+b.mid]={
"id":'deleteOldVideosByCron'+b.mid,
"name":'deleteOldVideosByCron'+b.mid,
@ -471,7 +445,7 @@ s.processUser = function(number,rows){
"delete":"1",
"execute":"",
"where":[{
"p1":"ke",
"p1":"mid",
"p2":"=",
"p3":b.mid
},{
@ -494,7 +468,6 @@ s.processUser = function(number,rows){
s.deleteRowsWithNoVideo(v,function(){
s.debugLog('--- deleteRowsWithNoVideo Complete')
s.checkForOrphanedFiles(v,function(){
s.debugLog('--- checkForOrphanedFiles Complete')
//done user, unlock current, and do next
s.overlapLock[v.ke]=false;
s.processUser(number+1,rows)
@ -538,4 +511,4 @@ io.on('f',function(d){
break;
}
})
console.log('Shinobi : cron.js started')
console.log('Shinobi : cron.js started')

View file

@ -289,8 +289,8 @@
},
{
"name": "detail=stream_flv_type",
"field": "FLV Stream Type",
"description": "The method you will view your FLV stream. Both methods are always active for each stream regardless of your viewing choice for the Shinobi Dashboard.",
"field": "Connection Type",
"description": "The method you will view your stream. Both methods are always active regardless of your viewing choice for the Dashboard.",
"default": "10",
"example": "",
"possible": ""
@ -737,7 +737,7 @@
{
"name": "detail=detector",
"field": "Enabled",
"description": "This will add another output in the FFMPEG command for the motion detector. A detector plugin must be connected for this to have any effect.",
"description": "This will add another output in the FFMPEG command for the motion detector.",
"default": "No",
"example": "",
"possible": ""
@ -876,11 +876,35 @@
{
"name": "detail=detector_sensitivity",
"field": "Indifference",
"description": "How much Shinobi doesn't care about motion before doing something. The opposite of sensitivity; a lower number means it will trigger sooner. The value ranges up to 15(+) decimal places. 10 is default, 0.005 is pretty sensitive to motion changes. Note: If using Region Editor, leave this blank, and set indifference in the Region Editor (above).",
"default": "10",
"description": "This can mean multiple things depending on the detector used. Built-In Motion Detection defines this as \"Percentage Changed in View or Region\"",
"default": "0.005",
"example": "10",
"possible": ""
},
{
"name": "detail=detector_max_sensitivity",
"field": "Max Indifference",
"description": "An upperbound to indifference. Any value over this amount will be ignored.",
"default": "",
"example": "75",
"possible": "Any number."
},
{
"name": "detail=detector_threshold",
"field": "Trigger Threshold",
"description": "Minimum number of detections to fire a motion event. Detections must be within the detector the threshold divided by detector fps seconds. For example, if detector fps is 2 and trigger threshold is 3, then three detections must occur within 1.5 seconds to trigger a motion event. This threshold is per detection region.",
"default": "1",
"example": "3",
"possible": "Any non-negative integer."
},
{
"name": "detail=detector_color_threshold",
"field": "Color Threshold",
"description": "The amount of difference allowed in a pixel before it is considered motion.",
"default": "9",
"example": "9",
"possible": "Any non-negative integer."
},
{
"name": "detail=detector_webhook",
"field": "Webhook",

View file

@ -1,6 +1,9 @@
{
"Shinobi": "Shinobi",
"superAdminTitle": "Shinobi : Super Admin",
"failedLoginText1": "You have failed to login too many times. You must wait 15 minutes before trying again.",
"failedLoginText2": "Please check your login credentials.",
"Time Left": "Time Left",
"Login": "Login",
"Authenticate": "Authenticate",
"Dashboard": "Dashboard",
@ -27,6 +30,7 @@
"Input Selector": "Input Selector",
"Input Settings": "Input Settings",
"Connection": "Connection",
"Video Set": "Video Set",
"API": "API",
"ONVIF": "ONVIF",
"FFprobe": "Probe",
@ -121,6 +125,7 @@
"Sort By": "Sort By",
"Start Time": "Start Time",
"End Time": "End Time",
"Time": "Time",
"Monitor ID": "Monitor ID",
"File Type": "File Type",
"Filesize": "Filesize",
@ -179,26 +184,36 @@
"RegionNote": "Points are only saved when you press <b>Save</b> on the <b>Monitor Settings</b> window.",
"Points": "Points <small>When adding points click on the edge of the polygon.</small>",
"Indifference": "Indifference",
"Max Indifference": "Max Indifference",
"Trigger Threshold": "Trigger Threshold",
"Color Threshold": "Color Threshold",
"Region Name": "Region Name",
"Regions": "Regions",
"Again": "Again",
"Account Info": "Account Info",
"blankPassword": "Leave blank to keep same password",
"2-Factor Authentication": "2-Factor Authentication",
"Use Max Storage Amount": "Use Max Storage Amount",
"Max Storage Amount": "Max Storage Amount <small>in Megabytes</small>",
"Number of Days to keep": "Number of Days to keep",
"Monitor Groups": "Monitor Groups",
"Group Name": "Group Name",
"WebDAV": "WebDAV",
"Backblaze B2": "Backblaze B2",
"Backblaze Error": "Backblaze Error",
"Could not create Bucket.": "Could not create Bucket.",
"Amazon S3": "Amazon S3",
"Save Links to Database": "Save Links to Database",
"Bucket": "Bucket",
"Region": "Region",
"Amazon S3 Upload Error": "Amazon S3 Upload Error",
"accountId": "Account ID",
"applicationKey": "Application Key",
"aws_accessKeyId": "Access Key Id",
"aws_secretAccessKey": "Secret Access Key",
"Discord Bot": "Discord Bot",
"URL": "URL",
"Operating Hours": "Operating Hours",
"Autosave": "Autosave",
"Save Directory": "Save Directory",
"CSS": "CSS <small>Style your dashboard.</small>",
@ -267,6 +282,7 @@
"Invalid JSON": "Invalid JSON",
"InvalidJSONText": "Please ensure this is a valid JSON string for Shinobi monitor configuration.",
"Passwords don't match": "Passwords don't match",
"Email address is in use.": "Email address is in use.",
"No Events found for this video": "No Events found for this video",
"Video and Time Span (Minutes)": "Video and Time Span (Minutes)",
"Video Length (minutes) and Motion Count per video": "Video Length (minutes) and Motion Count per video",
@ -444,6 +460,7 @@
"Log Level": "Log Level",
"Save Log in SQL": "Save Log in SQL <small>This can fill up quickly.</small>",
"JPEG": "JPEG",
"Web Page": "Web Page",
"MJPEG": "MJPEG",
"H.264 / H.265 / H.265+": "H.264 / H.265 / H.265+",
"HLS (.m3u8)": "HLS (.m3u8)",
@ -526,7 +543,8 @@
"Flush PM2 Logs": "Flush PM2 Logs",
"Filter ID": "Filter ID",
"Webdav Error": "Webdav Error",
"WebdavErrorText": "Cannot save. Did you make the camera folders inside your chosen save directory?",
"WebdavErrorTextTryCreatingDir": "Cannot save. Trying to create directory.",
"WebdavErrorTextCreatingDir": "Cannot create directory.",
"File Not Exist": "File Not Exist",
"No Videos Found": "No Videos Found",
"FileNotExistText": "Cannot save non existant file. Something went wrong.",
@ -578,6 +596,7 @@
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
"No Monitor Found, Ignoring Request": "No Monitor Found, Ignoring Request",
"Event": "Event",
"CPU used by this stream": "CPU used by this stream",
"Detector Buffer": "Detector Buffer",
"EventText1": "Triggered a motion event at",
"EventText2": "Could not email image, file was not accessible",
@ -585,10 +604,14 @@
"updateKeyText1": "\"updateKey\" is missing from \"conf.json\", cannot do updates this way until you add it.",
"updateKeyText2": "\"updateKey\" is incorrect.",
"Control Error": "Control Error",
"Database row does not exist": "Database row does not exist",
"File Delete Error": "File Delete Error",
"postDataBroken": "Check the format of the JSON. Ensure it is stringified and defined under 'data'",
"ControlErrorText1": "Control is not enabled",
"ControlErrorText2": "Check your connection details. You may need to point the Base URL at port 8000 or 80. Check your authentication info.",
"NotAuthorizedText1": "Not Authorized, Submit init command with \"auth\",\"ke\", and \"uid\"",
"Fields cannot be empty": "Fields cannot be empty",
"Email and Password fields cannot be empty": "Email and Password fields cannot be empty",
"AccountEditText1": "Could not edit. Refresh page if problem continues.",
"Not an Administrator Account": "Not an Administrator Account",
"superAdminText": "\"super.json\" does not exist. Please rename \"super.sample.json\" to \"super.json\".",
@ -606,15 +629,18 @@
"No Group with this key exists": "No Group with this key exists",
"Trigger Successful": "Trigger Successful",
"No such file": "No such file",
"h265BrowserText1": "If you are trying to play an H.265 file, you may need to download it and open it in another application like VLC.",
"modifyVideoText1": "Method doesn't exist. Check to make sure that the last value of the URL is not blank.",
"CPU indicator will not work. Continuing...": "CPU indicator will not work. Continuing...",
"startUpText0": "size check for videos",
"startUpText1": "end of size check for videos",
"startUpText0": "Checking Disk Used..",
"startUpText1": "Completed Checking Disk Used.",
"startUpText2": "all users checked, wait to close open files and remove files over user limit",
"startUpText3": "waiting to give unfinished video check some time. 3 seconds.",
"startUpText4": "starting all monitors set to watch and record",
"startUpText4": "Starting Monitors... Please Wait...",
"startUpText5": "Shinobi is ready.",
"startUpText6": "Orphaned Videos Found and Inserted",
"Migrator": "Migrator",
"Thumbnail": "Thumbnail",
"Host Type": "Host Type",
"Edit": "Edit",
"Dashboard Language": "Dashboard Language",
@ -692,6 +718,7 @@
"Get Logs to Client": "Get Logs to Client",
"Hardware Accelerated": "Hardware Accelerated",
"Accelerator": "Accelerator",
"drm": "DRM object sharing",
"qsv": "qsv",
"dxva2": "dxva2 (DirectX Video, Windows)",
"vdpau": "vdpau",

191
libs/auth.js Normal file
View file

@ -0,0 +1,191 @@
var fs = require('fs');
module.exports = function(s,config,lang){
//Authenticator functions
s.api = {}
s.superUsersApi = {}
s.factorAuth = {}
s.failedLoginAttempts = {}
//auth handler
//params = parameters
//cb = callback
//res = response, only needed for express (http server)
//request = request, only needed for express (http server)
s.auth = function(params,cb,res,req){
if(req){
//express (http server) use of auth function
params.ip=req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var failed=function(){
if(!req.ret){req.ret={ok:false}}
req.ret.msg=lang['Not Authorized'];
res.end(s.s(req.ret));
}
}else{
//socket.io use of auth function
var failed = function(){
//maybe log
}
}
var clearAfterTime=function(){
//remove temp key from memory
clearTimeout(s.api[params.auth].timeout)
s.api[params.auth].timeout=setTimeout(function(){
delete(s.api[params.auth])
},1000*60*5)
}
//check IP address of connecting user
var finish=function(user){
if(s.api[params.auth].ip.indexOf('0.0.0.0')>-1||s.api[params.auth].ip.indexOf(params.ip)>-1){
cb(user);
}else{
failed();
}
}
//check if auth key is user's temporary session key
if(s.group[params.ke]&&s.group[params.ke].users&&s.group[params.ke].users[params.auth]){
s.group[params.ke].users[params.auth].permissions={};
cb(s.group[params.ke].users[params.auth]);
}else{
//check if key is already in memory to save query time
if(s.api[params.auth]&&s.api[params.auth].details){
finish(s.api[params.auth]);
if(s.api[params.auth].timeout){
clearAfterTime()
}
}else{
//no key in memory, query db to see if key exists
//check if using username and password in plain text or md5
if(params.username&&params.username!==''&&params.password&&params.password!==''){
s.sqlQuery('SELECT * FROM Users WHERE mail=? AND (pass=? OR pass=?)',[params.username,params.password,s.createHash(params.password)],function(err,r){
if(r&&r[0]){
r=r[0];
r.ip='0.0.0.0';
r.auth = s.gid(20);
params.auth = r.auth;
r.details=JSON.parse(r.details);
r.permissions = {};
s.api[r.auth]=r;
clearAfterTime();
finish(r);
}else{
failed();
}
})
}else{
//not using plain login
s.sqlQuery('SELECT * FROM API WHERE code=? AND ke=?',[params.auth,params.ke],function(err,r){
if(r&&r[0]){
r=r[0];
s.api[params.auth]={ip:r.ip,uid:r.uid,ke:r.ke,permissions:JSON.parse(r.details),details:{}};
s.sqlQuery('SELECT mail,details FROM Users WHERE uid=? AND ke=?',[r.uid,r.ke],function(err,rr){
if(rr&&rr[0]){
rr=rr[0];
try{
s.api[params.auth].mail=rr.mail
s.api[params.auth].details=JSON.parse(rr.details)
s.api[params.auth].lang=s.getLanguageFile(s.api[params.auth].details.lang)
}catch(er){}
}
finish(s.api[params.auth]);
})
}else{
s.sqlQuery('SELECT * FROM Users WHERE auth=? AND ke=?',[params.auth,params.ke],function(err,r){
if(r&&r[0]){
r=r[0];
r.ip='0.0.0.0'
s.api[params.auth]=r
s.api[params.auth].details=JSON.parse(r.details)
s.api[params.auth].permissions={}
clearAfterTime()
finish(r)
}else{
failed();
}
})
}
})
}
}
}
}
//super user authentication handler
s.superAuth = function(params,callback,res,req){
var userFound = false
var userSelected = false
var adminUsersSelected = null
try{
var success = function(){
if(req && res){
res.setHeader('Content-Type', 'application/json');
var ip = req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress;
var resp = {
ok: userFound,
ip: ip
}
if(userFound === false){
resp.msg = lang['Not Authorized']
res.end(s.prettyPrint(resp))
}
if(userSelected){
resp.$user = userSelected
}
if(adminUsersSelected){
resp.users = adminUsersSelected
}
}
callback({
ip : ip,
$user:userSelected,
users:adminUsersSelected,
config:config,
lang:lang
})
}
var foundUser = function(){
if(params.users === true){
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r) {
adminUsersSelected = r
success()
})
}else{
success()
}
}
if(params.auth && s.superUsersApi[params.auth]){
userFound = true
userSelected = s.superUsersApi[params.auth].$user
foundUser()
}else{
var superUserList = JSON.parse(fs.readFileSync(s.location.super))
superUserList.forEach(function(superUser,n){
if(
userFound === false &&
(
params.auth && superUser.tokens && superUser.tokens[params.auth] || //using API key (object)
params.auth && superUser.tokens && superUser.tokens.indexOf && superUser.tokens.indexOf(params.auth) > -1 || //using API key (array)
(
params.mail && params.mail.toLowerCase() === superUser.mail.toLowerCase() && //email matches
(
params.pass === superUser.pass || //user give it already hashed
superUser.pass === s.createHash(params.pass) || //hash and check it
superUser.pass.toLowerCase() === s.md5(params.pass).toLowerCase() //check if still using md5
)
)
)
){
userFound = true
userSelected = superUser
foundUser()
}
})
}
}catch(err){
console.log('The following error may mean your super.json is not formatted correctly.')
console.log(err)
}
if(userFound === true){
return true
}else{
return false
}
}
}

227
libs/basic.js Normal file
View file

@ -0,0 +1,227 @@
var moment = require('moment');
var crypto = require('crypto');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var events = require('events');
var http = require('http');
var https = require('https');
module.exports = function(s,config){
//kill any ffmpeg running
s.ffmpegKill=function(){
var cmd=''
if(s.isWin===true){
cmd = "Taskkill /IM ffmpeg.exe /F"
}else{
cmd = "ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9"
}
exec(cmd,{detached: true})
};
process.on('exit',s.ffmpegKill.bind(null,{cleanup:true}));
process.on('SIGINT',s.ffmpegKill.bind(null, {exit:true}));
s.checkRelativePath = function(x){
if(x.charAt(0)!=='/'){
x=s.mainDirectory+'/'+x
}
return x
}
s.checkDetails = function(e){
if(!e.id && e.mid){e.id = e.mid}
if(e.details&&(e.details instanceof Object)===false){
try{e.details=JSON.parse(e.details)}catch(err){}
}
}
s.parseJSON = function(string){
try{
string = JSON.parse(string)
}catch(err){
}
return string
}
s.stringJSON = function(json){
try{
if(json instanceof Object){
json = JSON.stringify(json)
}
}catch(err){
}
return json
}
s.addUserPassToUrl = function(url,user,pass){
var splitted = url.split('://')
splitted[1] = user + ':' + pass + '@' + splitted[1]
return splitted.join('://')
}
s.checkCorrectPathEnding = function(x){
var length=x.length
if(x.charAt(length-1)!=='/'){
x=x+'/'
}
return x.replace('__DIR__',s.mainDirectory)
}
s.md5 = function(x){return crypto.createHash('md5').update(x).digest("hex")}
s.createHash = s.md5
switch(config.passwordType){
case'sha512':
if(config.passwordSalt){
s.createHash = function(x){return crypto.pbkdf2Sync(x, config.passwordSalt, 100000, 64, 'sha512').toString('hex')}
}
break;
case'sha256':
s.createHash = function(x){return crypto.createHash('sha256').update(x).digest("hex")}
break;
}
//load camera controller vars
s.nameToTime=function(x){x=x.split('.')[0].split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
s.ratio=function(width,height,ratio){ratio = width / height;return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';}
s.randomNumber=function(x){
if(!x){x=10};
return Math.floor((Math.random() * x) + 1);
};
s.gid=function(x){
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.nid=function(x){
if(!x){x=6};var t = "";var p = "0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.formattedTime_withOffset=function(e,x){
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
e=s.timeObject(e);if(config.utcOffset){e=e.utcOffset(config.utcOffset)}
return e.format(x);
}
s.formattedTime=function(e,x){
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
return s.timeObject(e).format(x);
}
s.utcToLocal = function(time){
return moment.utc(time).utcOffset(s.utcOffset).format()
}
s.localTimeObject = function(e,x){
return moment(e)
}
if(config.useUTC === true){
s.timeObject = function(time){
return moment(time).utc()
}
}else{
s.timeObject = moment
}
s.ipRange=function(start_ip, end_ip) {
var start_long = s.toLong(start_ip);
var end_long = s.toLong(end_ip);
if (start_long > end_long) {
var tmp=start_long;
start_long=end_long
end_long=tmp;
}
var range_array = [];
var i;
for (i=start_long; i<=end_long;i++) {
range_array.push(s.fromLong(i));
}
return range_array;
}
s.portRange=function(lowEnd,highEnd){
var list = [];
for (var i = lowEnd; i <= highEnd; i++) {
list.push(i);
}
return list;
}
//toLong taken from NPM package 'ip'
s.toLong=function(ip) {
var ipl = 0;
ip.split('.').forEach(function(octet) {
ipl <<= 8;
ipl += parseInt(octet);
});
return(ipl >>> 0);
}
//fromLong taken from NPM package 'ip'
s.fromLong=function(ipl) {
return ((ipl >>> 24) + '.' +
(ipl >> 16 & 255) + '.' +
(ipl >> 8 & 255) + '.' +
(ipl & 255) );
}
s.getFunctionParamNames = function(func) {
var fnStr = func.toString().replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g);
if(result === null)
result = [];
return result;
}
s.getRequest = function(url,callback){
return http.get(url, function(res){
var body = '';
res.on('data', function(chunk){
body += chunk;
});
res.on('end',function(){
try{body = JSON.parse(body)}catch(err){}
callback(body)
});
}).on('error', function(e){
// s.systemLog("Get Snapshot Error", e);
});
}
//system log
s.systemLog = function(q,w,e){
if(!w){w=''}
if(!e){e=''}
if(config.systemLog===true){
if(typeof q==='string'&&s.databaseEngine){
s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',['$','$SYSTEM',s.s({type:q,msg:w})]);
s.tx({f:'log',log:{time:s.timeObject(),ke:'$',mid:'$SYSTEM',time:s.timeObject(),info:s.s({type:q,msg:w})}},'$');
}
return console.log(s.timeObject().format(),q,w,e)
}
}
//system log
s.debugLog = function(q,w,e){
if(config.debugLog === true){
if(!w){w = ''}
if(!e){e = ''}
console.log(s.timeObject().format(),q,w,e)
if(config.debugLogVerbose === true){
console.log(new Error())
}
}
}
s.getOriginalUrl = function(req){
var url
if(config.baseURL || config.baseURL === ''){
url = config.baseURL
}else{
url = req.protocol + '://' + req.get('host') + '/'
}
return url
}
s.file=function(x,e){
if(!e){e={}};
switch(x){
case'size':
return fs.statSync(e.filename)["size"];
break;
case'delete':
if(!e){return false;}
return exec('rm -f '+e,{detached: true});
break;
case'deleteFolder':
if(!e){return false;}
return exec('rm -rf '+e,{detached: true});
break;
case'deleteFiles':
if(!e.age_type){e.age_type='min'};if(!e.age){e.age='1'};
exec('find '+e.path+' -type f -c'+e.age_type+' +'+e.age+' -exec rm -f {} +',{detached: true});
break;
}
}
}

217
libs/childNode.js Normal file
View file

@ -0,0 +1,217 @@
var fs = require('fs');
var http = require('http');
var https = require('https');
var express = require('express');
module.exports = function(s,config,lang,app,io){
//setup Master for childNodes
if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
s.childNodes = {};
var childNodeHTTP = express();
var childNodeServer = http.createServer(app);
var childNodeWebsocket = new (require('socket.io'))()
childNodeServer.listen(config.childNodes.port,config.bindip,function(){
console.log(lang.Shinobi+' - CHILD NODE PORT : '+config.childNodes.port);
});
s.debugLog('childNodeWebsocket.attach(childNodeServer)')
childNodeWebsocket.attach(childNodeServer);
//send data to child node function (experimental)
s.cx = function(z,y,x){
if(!z.mid && !z.d){
console.error('Missing ID')
}else if(x){
x.broadcast.to(y).emit('c',z)
}else{
childNodeWebsocket.to(y).emit('c',z)
}
}
//child Node Websocket
childNodeWebsocket.on('connection', function (cn) {
//functions for dispersing work to child servers;
cn.on('c',function(d){
if(config.childNodes.key.indexOf(d.socketKey) > -1){
if(!cn.shinobi_child&&d.f=='init'){
cn.ip = cn.request.connection.remoteAddress.replace('::ffff:','')+':'+d.port
cn.shinobi_child = 1
tx = function(z){
cn.emit('c',z)
}
if(!s.childNodes[cn.ip]){
s.childNodes[cn.ip] = {}
};
s.childNodes[cn.ip].cnid = cn.id
s.childNodes[cn.ip].cpu = 0
s.childNodes[cn.ip].activeCameras = {}
d.availableHWAccels.forEach(function(accel){
if(config.availableHWAccels.indexOf(accel) === -1)config.availableHWAccels.push(accel)
})
tx({
f : 'init_success',
childNodes : s.childNodes
})
s.childNodes[cn.ip].coreCount = d.coreCount
}else{
switch(d.f){
case'cpu':
s.childNodes[cn.ip].cpu = d.cpu;
break;
case'sql':
s.sqlQuery(d.query,d.values,function(err,rows){
cn.emit('c',{f:'sqlCallback',rows:rows,err:err,callbackId:d.callbackId});
});
break;
case'camera':
s.camera(d.mode,d.data)
break;
case's.tx':
s.tx(d.data,d.to)
break;
case's.userLog':
if(!d.mon || !d.data)return console.log('LOG DROPPED',d.mon,d.data);
s.userLog(d.mon,d.data)
break;
case'created_file_chunk':
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
d.dir = s.getVideoDirectory(s.group[d.ke].mon_conf[d.mid])
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename] = fs.createWriteStream(d.dir+d.filename)
}
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename].write(d.chunk)
break;
case'created_file':
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
return console.log('FILE NOT EXIST')
}
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename].end();
tx({
f:'delete',
file:d.filename,
ke:d.ke,
mid:d.mid
});
s.txWithSubPermissions({
f:'video_build_success',
hrefNoAuth:'/videos/'+d.ke+'/'+d.mid+'/'+d.filename,
filename:d.filename,
mid:d.mid,
ke:d.ke,
time:d.time,
size:d.filesize,
end:d.end
},'GRP_'+d.ke,'video_view')
//save database row
var insert = {
startTime : d.time,
filesize : d.filesize,
endTime : d.end,
dir : s.getVideoDirectory(d.d),
file : d.filename,
filename : d.filename,
filesizeMB : parseFloat((d.filesize/1000000).toFixed(2))
}
s.insertDatabaseRow(d.d,insert)
s.insertCompletedVideoExtensions.forEach(function(extender){
extender(d.d,insert)
})
//purge over max
s.purgeDiskForGroup(d)
//send new diskUsage values
s.setDiskUsedForGroup(d,insert.filesizeMB)
clearTimeout(s.group[d.ke].mon[d.mid].recordingChecker)
clearTimeout(s.group[d.ke].mon[d.mid].streamChecker)
break;
}
}
}
})
cn.on('disconnect',function(){
console.log('childNodeWebsocket.disconnect')
if(s.childNodes[cn.ip]){
var activeCameraKeys = Object.keys(s.childNodes[cn.ip].activeCameras)
activeCameraKeys.forEach(function(key){
var monitor = s.childNodes[cn.ip].activeCameras[key]
s.camera('stop',s.cleanMonitorObject(monitor))
delete(s.group[monitor.ke].mon[monitor.mid].childNode)
delete(s.group[monitor.ke].mon[monitor.mid].childNodeId)
setTimeout(function(){
s.camera(monitor.mode,s.cleanMonitorObject(monitor))
},1300)
})
delete(s.childNodes[cn.ip]);
}
})
})
}else
//setup Child for childNodes
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
s.connected = false;
childIO = require('socket.io-client')('ws://'+config.childNodes.host);
s.cx = function(x){x.socketKey = config.childNodes.key;childIO.emit('c',x)}
s.tx = function(x,y){s.cx({f:'s.tx',data:x,to:y})}
s.userLog = function(x,y){s.cx({f:'s.userLog',mon:x,data:y})}
s.queuedSqlCallbacks = {}
s.sqlQuery = function(query,values,onMoveOn){
var callbackId = s.gid()
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(typeof onMoveOn !== 'function'){onMoveOn=function(){}}
s.queuedSqlCallbacks[callbackId] = onMoveOn
s.cx({f:'sql',query:query,values:values,callbackId:callbackId});
}
setInterval(function(){
s.cpuUsage(function(cpu){
io.emit('c',{f:'cpu',cpu:parseFloat(cpu)});
})
},2000);
childIO.on('connect', function(d){
console.log('CHILD CONNECTION SUCCESS')
s.cx({
f : 'init',
port : config.port,
coreCount : s.coreCount,
availableHWAccels : config.availableHWAccels
})
})
childIO.on('c', function (d) {
switch(d.f){
case'sqlCallback':
if(s.queuedSqlCallbacks[d.callbackId]){
s.queuedSqlCallbacks[d.callbackId](d.err,d.rows)
delete(s.queuedSqlCallbacks[d.callbackId])
}
break;
case'init_success':
s.connected=true;
s.other_helpers=d.child_helpers;
break;
case'kill':
s.initiateMonitorObject(d.d);
s.cameraDestroy(s.group[d.d.ke].mon[d.d.id].spawn,d.d)
break;
case'sync':
s.initiateMonitorObject(d.sync);
Object.keys(d.sync).forEach(function(v){
s.group[d.sync.ke].mon[d.sync.mid][v]=d.sync[v];
});
break;
case'delete'://delete video
s.file('delete',s.dir.videos+d.ke+'/'+d.mid+'/'+d.file)
break;
case'insertCompleted'://close video
s.insertCompletedVideo(d.d,d.k)
break;
case'cameraStop'://start camera
s.camera('stop',d.d)
break;
case'cameraStart'://start or record camera
s.camera(d.mode,d.d)
break;
}
})
childIO.on('disconnect',function(d){
s.connected = false;
})
}
}

555
libs/cloudUploaders.js Normal file
View file

@ -0,0 +1,555 @@
var fs = require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var webdav = require("webdav-fs");
module.exports = function(s,config,lang){
// WebDAV
var beforeAccountSaveForWebDav = function(d){
//d = save event
d.form.details.use_webdav=d.d.use_webdav
}
var cloudDiskUseStartupForWebDav = function(group,userDetails){
group.cloudDiskUse['webdav'].name = 'WebDAV'
group.cloudDiskUse['webdav'].sizeLimitCheck = (userDetails.use_webdav_size_limit === '1')
if(!userDetails.webdav_size_limit || userDetails.webdav_size_limit === ''){
group.cloudDiskUse['webdav'].sizeLimit = 10000
}else{
group.cloudDiskUse['webdav'].sizeLimit = parseFloat(userDetails.webdav_size_limit)
}
}
var loadWebDavForUser = function(e){
// e = user
var ar = JSON.parse(e.details);
//owncloud/webdav
if(!s.group[e.ke].webdav &&
ar.webdav_user&&
ar.webdav_user!==''&&
ar.webdav_pass&&
ar.webdav_pass!==''&&
ar.webdav_url&&
ar.webdav_url!==''
){
if(!ar.webdav_dir||ar.webdav_dir===''){
ar.webdav_dir='/'
}
ar.webdav_dir = s.checkCorrectPathEnding(ar.webdav_dir)
s.group[e.ke].webdav = webdav(
ar.webdav_url,
ar.webdav_user,
ar.webdav_pass
)
}
}
var unloadWebDavForUser = function(user){
s.group[user.ke].webdav = null
}
var deleteVideoFromWebDav = function(e,video,callback){
// e = user
try{
var videoDetails = JSON.parse(video.details)
}catch(err){
var videoDetails = video.details
}
if(!videoDetails.location){
var prefix = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass)
videoDetails.location = video.href.replace(prefix,'')
}
s.group[e.ke].webdav.unlink(videoDetails.location, function(err) {
if (err) console.log(videoDetails.location,err)
callback()
})
}
var uploadVideoToWebDav = function(e,k){
//e = video object
//k = temporary values
if(!k)k={};
//cloud saver - webdav
var wfs = s.group[e.ke].webdav
if(wfs && s.group[e.ke].init.use_webdav !== '0' && s.group[e.ke].init.webdav_save === "1"){
var webdavUploadDir = s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'
var startWebDavUpload = function(){
s.group[e.ke].mon[e.id].webdavDirExist = true
var wfsWriteStream =
fs.createReadStream(k.dir + k.filename).pipe(wfs.createWriteStream(webdavUploadDir + k.filename))
if(s.group[e.ke].init.webdav_log === '1'){
var webdavRemoteUrl = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass) + s.group[e.ke].init.webdav_dir + e.ke + '/'+e.mid+'/'+k.filename
var save = [
e.mid,
e.ke,
k.startTime,
1,
s.s({
type : 'webdav',
location : webdavUploadDir + k.filename
}),
k.filesize,
k.endTime,
webdavRemoteUrl
]
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
s.setCloudDiskUsedForGroup(e,{
amount : k.filesizeMB,
storageType : 'webdav'
})
s.purgeCloudDiskForGroup(e,'webdav')
}
}
if(s.group[e.ke].mon[e.id].webdavDirExist !== true){
//check if webdav dir exist
var parentPoint = 0
var webDavParentz = webdavUploadDir.split('/')
var webDavParents = []
webDavParentz.forEach(function(v){
if(v && v !== '')webDavParents.push(v)
})
var stitchPieces = './'
var lastParentCheck = function(){
++parentPoint
if(parentPoint === webDavParents.length){
startWebDavUpload()
}
checkPathPiece(webDavParents[parentPoint])
}
var checkPathPiece = function(pathPiece){
if(pathPiece && pathPiece !== ''){
stitchPieces += pathPiece + '/'
wfs.stat(stitchPieces, function(error, stats) {
if(error){
reply = {
status : error.status,
msg : lang.WebdavErrorTextTryCreatingDir,
dir : stitchPieces,
}
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
wfs.mkdir(stitchPieces, function(error) {
if(error){
reply = {
status : error.status,
msg : lang.WebdavErrorTextCreatingDir,
dir : stitchPieces,
}
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
}else{
lastParentCheck()
}
})
}else{
lastParentCheck()
}
})
}else{
++parentPoint
}
}
checkPathPiece(webDavParents[0])
}else{
startWebDavUpload()
}
}
}
//Amazon S3
var beforeAccountSaveForAmazonS3 = function(d){
//d = save event
d.form.details.use_aws_s3=d.d.use_aws_s3
}
var cloudDiskUseStartupForAmazonS3 = function(group,userDetails){
group.cloudDiskUse['s3'].name = 'Amazon S3'
group.cloudDiskUse['s3'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
group.cloudDiskUse['s3'].sizeLimit = 10000
}else{
group.cloudDiskUse['s3'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
}
}
var loadAmazonS3ForUser = function(e){
// e = user
var ar = JSON.parse(e.details);
//Amazon S3
if(!s.group[e.ke].aws &&
!s.group[e.ke].aws_s3 &&
ar.aws_s3 !== '0' &&
ar.aws_accessKeyId !== ''&&
ar.aws_secretAccessKey &&
ar.aws_secretAccessKey !== ''&&
ar.aws_region &&
ar.aws_region !== ''&&
ar.aws_s3_bucket !== ''
){
if(!ar.aws_s3_dir || ar.aws_s3_dir === '/'){
ar.aws_s3_dir = ''
}
if(ar.aws_s3_dir !== ''){
ar.aws_s3_dir = s.checkCorrectPathEnding(ar.aws_s3_dir)
}
s.group[e.ke].aws = new require("aws-sdk")
s.group[e.ke].aws.config = new s.group[e.ke].aws.Config({
accessKeyId: ar.aws_accessKeyId,
secretAccessKey: ar.aws_secretAccessKey,
region: ar.aws_region
})
s.group[e.ke].aws_s3 = new s.group[e.ke].aws.S3();
}
}
var unloadAmazonS3ForUser = function(user){
s.group[user.ke].aws = null
s.group[user.ke].aws_s3 = null
}
var deleteVideoFromAmazonS3 = function(e,video,callback){
// e = user
try{
var videoDetails = JSON.parse(video.details)
}catch(err){
var videoDetails = video.details
}
if(!videoDetails.location){
videoDetails.location = video.href.split('.amazonaws.com')[1]
}
s.group[e.ke].aws_s3.deleteObject({
Bucket: s.group[e.ke].init.aws_s3_bucket,
Key: videoDetails.location,
}, function(err, data) {
if (err) console.log(err);
callback()
});
}
var uploadVideoToAmazonS3 = function(e,k){
//e = video object
//k = temporary values
if(!k)k={};
//cloud saver - amazon s3
if(s.group[e.ke].aws_s3 && s.group[e.ke].init.use_aws_s3 !== '0' && s.group[e.ke].init.aws_s3_save === '1'){
var ext = k.filename.split('.')
ext = ext[ext.length - 1]
var fileStream = fs.createReadStream(k.dir+k.filename);
fileStream.on('error', function (err) {
console.error(err)
})
var saveLocation = s.group[e.ke].init.aws_s3_dir+e.ke+'/'+e.mid+'/'+k.filename
s.group[e.ke].aws_s3.upload({
Bucket: s.group[e.ke].init.aws_s3_bucket,
Key: saveLocation,
Body:fileStream,
ACL:'public-read',
ContentType:'video/'+ext
},function(err,data){
if(err){
s.userLog(e,{type:lang['Amazon S3 Upload Error'],msg:err})
}
if(s.group[e.ke].init.aws_s3_log === '1' && data && data.Location){
var save = [
e.mid,
e.ke,
k.startTime,
1,
s.s({
type : 's3',
location : saveLocation
}),
k.filesize,
k.endTime,
data.Location
]
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
s.setCloudDiskUsedForGroup(e,{
amount : k.filesizeMB,
storageType : 's3'
})
s.purgeCloudDiskForGroup(e,'s3')
}
})
}
}
//Backblaze B2
var beforeAccountSaveForBackblazeB2 = function(d){
//d = save event
d.form.details.use_aws_s3=d.d.use_bb_b2
}
var cloudDiskUseStartupForBackblazeB2 = function(group,userDetails){
group.cloudDiskUse['b2'].name = 'Backblaze B2'
group.cloudDiskUse['b2'].sizeLimitCheck = (userDetails.use_bb_b2_size_limit === '1')
if(!userDetails.bb_b2_size_limit || userDetails.bb_b2_size_limit === ''){
group.cloudDiskUse['b2'].sizeLimit = 10000
}else{
group.cloudDiskUse['b2'].sizeLimit = parseFloat(userDetails.bb_b2_size_limit)
}
}
var loadBackblazeB2ForUser = function(e){
var ar = JSON.parse(e.details);
try{
if(!s.group[e.ke].bb_b2 &&
ar.bb_b2_accountId &&
ar.bb_b2_accountId !=='' &&
ar.bb_b2_applicationKey &&
ar.bb_b2_applicationKey !=='' &&
ar.bb_b2_bucket &&
ar.bb_b2_bucket !== ''
){
var B2 = require('backblaze-b2')
if(!ar.bb_b2_dir || ar.bb_b2_dir === '/'){
ar.bb_b2_dir = ''
}
if(ar.bb_b2_dir !== ''){
ar.bb_b2_dir = s.checkCorrectPathEnding(ar.bb_b2_dir)
}
var b2 = new B2({
accountId: ar.bb_b2_accountId,
applicationKey: ar.bb_b2_applicationKey
})
s.group[e.ke].bb_b2 = b2
var backblazeErr = function(err){
// console.log(err)
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
}
b2.authorize().then(function(resp){
s.group[e.ke].bb_b2_downloadUrl = resp.data.downloadUrl
b2.listBuckets().then(function(resp){
var buckets = resp.data.buckets
var bucketN = -2
buckets.forEach(function(item,n){
if(item.bucketName === ar.bb_b2_bucket){
bucketN = n
}
})
if(bucketN > -1){
s.group[e.ke].bb_b2_bucketId = buckets[bucketN].bucketId
}else{
b2.createBucket(
ar.bb_b2_bucket,
'allPublic'
).then(function(resp){
s.group[e.ke].bb_b2_bucketId = resp.data.bucketId
}).catch(backblazeErr)
}
}).catch(backblazeErr)
}).catch(backblazeErr)
}
}catch(err){
s.debugLog(err)
}
}
var unloadBackblazeB2ForUser = function(user){
s.group[user.ke].bb_b2 = null
}
var deleteVideoFromBackblazeB2 = function(e,video,callback){
// e = user
try{
var videoDetails = JSON.parse(video.details)
}catch(err){
var videoDetails = video.details
}
s.group[e.ke].bb_b2.deleteFileVersion({
fileId: videoDetails.fileId,
fileName: videoDetails.fileName
}).then(function(resp){
// console.log('deleteFileVersion',resp.data)
}).catch(function(err){
console.log('deleteFileVersion',err)
})
}
var uploadVideoToBackblazeB2 = function(e,k){
//e = video object
//k = temporary values
if(!k)k={};
//cloud saver - Backblaze B2
if(s.group[e.ke].bb_b2 && s.group[e.ke].init.use_bb_b2 !== '0' && s.group[e.ke].init.bb_b2_save === '1'){
var backblazeErr = function(err){
// console.log(err)
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
}
fs.readFile(k.dir+k.filename,function(err,data){
var backblazeSavePath = s.group[e.ke].init.bb_b2_dir+e.ke+'/'+e.mid+'/'+k.filename
var getUploadUrl = function(bucketId,callback){
s.group[e.ke].bb_b2.getUploadUrl(bucketId).then(function(resp){
callback(resp.data)
}).catch(backblazeErr)
}
getUploadUrl(s.group[e.ke].bb_b2_bucketId,function(req){
s.group[e.ke].bb_b2.uploadFile({
uploadUrl: req.uploadUrl,
uploadAuthToken: req.authorizationToken,
filename: backblazeSavePath,
data: data,
onUploadProgress: null
}).then(function(resp){
if(s.group[e.ke].init.bb_b2_log === '1' && resp.data.fileId){
var backblazeDownloadUrl = s.group[e.ke].bb_b2_downloadUrl + '/file/' + s.group[e.ke].init.bb_b2_bucket + '/' + backblazeSavePath
var save = [
e.mid,
e.ke,
k.startTime,
1,
s.s({
type : 'b2',
bucketId : resp.data.bucketId,
fileId : resp.data.fileId,
fileName : resp.data.fileName
}),
k.filesize,
k.endTime,
backblazeDownloadUrl
]
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
s.setCloudDiskUsedForGroup(e,{
amount : k.filesizeMB,
storageType : 'b2'
})
s.purgeCloudDiskForGroup(e,'b2')
}
}).catch(backblazeErr)
})
})
}
}
//SFTP
// var beforeAccountSaveForSftp = function(d){
// //d = save event
// d.form.details.use_sftp = d.d.use_sftp
// }
// var cloudDiskUseStartupForSftp = function(group,userDetails){
// group.cloudDiskUse['sftp'].name = 'SFTP'
// group.cloudDiskUse['sftp'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
// if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
// group.cloudDiskUse['sftp'].sizeLimit = 10000
// }else{
// group.cloudDiskUse['sftp'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
// }
// }
// var loadSftpForUser = function(e){
// // e = user
// var ar = JSON.parse(e.details);
// //SFTP
// if(!s.group[e.ke].sftp &&
// !s.group[e.ke].sftp &&
// ar.sftp !== '0' &&
// ar.sftp_accessKeyId !== ''&&
// ar.sftp_secretAccessKey &&
// ar.sftp_secretAccessKey !== ''&&
// ar.sftp_region &&
// ar.sftp_region !== ''&&
// ar.sftp_bucket !== ''
// ){
// if(!ar.sftp_dir || ar.sftp_dir === '/'){
// ar.sftp_dir = ''
// }
// if(ar.sftp_dir !== ''){
// ar.sftp_dir = s.checkCorrectPathEnding(ar.sftp_dir)
// }
// s.group[e.ke].sftp = new s.group[e.ke].sftp.S3();
// s.group[e.ke].sftp = new require('ssh2-sftp-client')();
// var connectionDetails = {
// host: ar.sftp_host,
// port: ar.sftp_port
// }
// if(!ar.sftp_port)ar.sftp_port = 22
// if(ar.sftp_username)connectionDetails.username = ar.sftp_username
// if(ar.sftp_password)connectionDetails.password = ar.sftp_password
// if(ar.sftp_privateKey)connectionDetails.privateKey = ar.sftp_privateKey
// sftp.connect(connectionDetails).then(() => {
// return sftp.list('/pathname');
// }).then((data) => {
// console.log(data, 'the data info');
// }).catch((err) => {
// console.log(err, 'catch error');
// });
// }
// }
// var unloadSftpForUser = function(user){
// s.group[user.ke].sftp = null
// }
// var deleteVideoFromSftp = function(e,video,callback){
// // e = user
// try{
// var videoDetails = JSON.parse(video.details)
// }catch(err){
// var videoDetails = video.details
// }
// s.group[e.ke].sftp.deleteObject({
// Bucket: s.group[e.ke].init.sftp_bucket,
// Key: videoDetails.location,
// }, function(err, data) {
// if (err) console.log(err);
// callback()
// });
// }
// var uploadVideoToSftp = function(e,k){
// //e = video object
// //k = temporary values
// if(!k)k={};
// //cloud saver - SFTP
// if(s.group[e.ke].sftp && s.group[e.ke].init.use_sftp !== '0' && s.group[e.ke].init.sftp_save === '1'){
// var fileStream = fs.createReadStream(k.dir+k.filename);
// fileStream.on('error', function (err) {
// console.error(err)
// })
// var saveLocation = s.group[e.ke].init.sftp_dir+e.ke+'/'+e.mid+'/'+k.filename
// s.group[e.ke].sftp.upload({
// Bucket: s.group[e.ke].init.sftp_bucket,
// Key: saveLocation,
// Body:fileStream,
// ACL:'public-read'
// },function(err,data){
// if(err){
// s.userLog(e,{type:lang['SFTP Upload Error'],msg:err})
// }
// if(s.group[e.ke].init.sftp_log === '1' && data && data.Location){
// var save = [
// e.mid,
// e.ke,
// k.startTime,
// 1,
// s.s({
// type : 'sftp',
// location : saveLocation
// }),
// k.filesize,
// k.endTime,
// data.Location
// ]
// s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
// s.setCloudDiskUsedForGroup(e,{
// amount : k.filesizeMB,
// storageType : 'sftp'
// })
// s.purgeCloudDiskForGroup(e,'sftp')
// }
// })
// }
// }
//add the extenders
//webdav
s.loadGroupAppExtender(loadWebDavForUser)
s.unloadGroupAppExtender(unloadWebDavForUser)
s.insertCompletedVideoExtender(uploadVideoToWebDav)
s.deleteVideoFromCloudExtensions['webdav'] = deleteVideoFromWebDav
s.cloudDiskUseStartupExtensions['webdav'] = cloudDiskUseStartupForWebDav
s.beforeAccountSave(beforeAccountSaveForWebDav)
s.onAccountSave(cloudDiskUseStartupForWebDav)
s.cloudDisksLoader('webdav')
//amazon s3
s.loadGroupAppExtender(loadAmazonS3ForUser)
s.unloadGroupAppExtender(unloadAmazonS3ForUser)
s.insertCompletedVideoExtender(uploadVideoToAmazonS3)
s.deleteVideoFromCloudExtensions['s3'] = deleteVideoFromAmazonS3
s.cloudDiskUseStartupExtensions['s3'] = cloudDiskUseStartupForAmazonS3
s.beforeAccountSave(beforeAccountSaveForAmazonS3)
s.onAccountSave(cloudDiskUseStartupForAmazonS3)
s.cloudDisksLoader('s3')
//backblaze b2
s.loadGroupAppExtender(loadBackblazeB2ForUser)
s.unloadGroupAppExtender(unloadBackblazeB2ForUser)
s.insertCompletedVideoExtender(uploadVideoToBackblazeB2)
s.deleteVideoFromCloudExtensions['b2'] = deleteVideoFromBackblazeB2
s.cloudDiskUseStartupExtensions['b2'] = cloudDiskUseStartupForBackblazeB2
s.beforeAccountSave(beforeAccountSaveForBackblazeB2)
s.onAccountSave(cloudDiskUseStartupForBackblazeB2)
s.cloudDisksLoader('b2')
//SFTP
// s.loadGroupAppExtender(loadSftpForUser)
// s.unloadGroupAppExtender(unloadSftpForUser)
// s.insertCompletedVideoExtender(uploadVideoToSftp)
// s.deleteVideoFromCloudExtensions['sftp'] = deleteVideoFromSftp
// s.cloudDiskUseStartupExtensions['sftp'] = cloudDiskUseStartupForSftp
// s.beforeAccountSave(beforeAccountSaveForSftp)
// s.onAccountSave(cloudDiskUseStartupForSftp)
// s.cloudDisksLoader('sftp')
}

55
libs/config.js Normal file
View file

@ -0,0 +1,55 @@
module.exports = function(s){
s.location = {
super : s.mainDirectory+'/super.json',
config : s.mainDirectory+'/conf.json',
languages : s.mainDirectory+'/languages'
}
var config = require(s.location.config);
if(!config.productType){
config.productType='CE'
}
//config defaults
if(config.cpuUsageMarker === undefined){config.cpuUsageMarker='%Cpu'}
if(config.customCpuCommand === undefined){config.customCpuCommand=null}
if(config.autoDropCache === undefined){config.autoDropCache=true}
if(config.doSnapshot === undefined){config.doSnapshot=true}
if(config.restart === undefined){config.restart={}}
if(config.systemLog === undefined){config.systemLog=true}
if(config.deleteCorruptFiles === undefined){config.deleteCorruptFiles=true}
if(config.restart.onVideoNotExist === undefined){config.restart.onVideoNotExist=true}
if(config.ip === undefined||config.ip===''||config.ip.indexOf('0.0.0.0')>-1){config.ip='localhost'}else{config.bindip=config.ip};
if(config.cron === undefined)config.cron={};
if(config.cron.enabled === undefined)config.cron.enabled=true;
if(config.cron.deleteOld === undefined)config.cron.deleteOld=true;
if(config.cron.deleteNoVideo === undefined)config.cron.deleteNoVideo=true;
if(config.cron.deleteNoVideoRecursion === undefined)config.cron.deleteNoVideoRecursion=false;
if(config.cron.deleteOverMax === undefined)config.cron.deleteOverMax=true;
if(config.cron.deleteOverMaxOffset === undefined)config.cron.deleteOverMaxOffset=0.9;
if(config.cron.deleteLogs === undefined)config.cron.deleteLogs=true;
if(config.cron.deleteEvents === undefined)config.cron.deleteEvents=true;
if(config.cron.deleteFileBins === undefined)config.cron.deleteFileBins=true;
if(config.cron.interval === undefined)config.cron.interval=1;
if(config.databaseType === undefined){config.databaseType='mysql'}
if(config.pluginKeys === undefined)config.pluginKeys={};
if(config.databaseLogs === undefined){config.databaseLogs=false}
if(config.useUTC === undefined){config.useUTC=false}
if(config.iconURL === undefined){config.iconURL = "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png"}
if(config.pipeAddition === undefined){config.pipeAddition=7}else{config.pipeAddition=parseInt(config.pipeAddition)}
if(config.hideCloudSaveUrls === undefined){config.hideCloudSaveUrls = true}
if(config.insertOrphans === undefined){config.insertOrphans = true}
if(config.orphanedVideoCheckMax === undefined){config.orphanedVideoCheckMax = 20}
//Child Nodes
if(config.childNodes === undefined)config.childNodes = {};
//enabled
if(config.childNodes.enabled === undefined)config.childNodes.enabled = false;
//mode, set value as `child` for all other machines in the cluster
if(config.childNodes.mode === undefined)config.childNodes.mode = 'master';
//child node connection port
if(config.childNodes.port === undefined)config.childNodes.port = 8288;
//child node connection key
if(config.childNodes.key === undefined)config.childNodes.key = [
'3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345'
];
return config
}

204
libs/detector.js Normal file
View file

@ -0,0 +1,204 @@
var P2P = require('pipe2pam');
var PamDiff = require('pam-diff');
module.exports = function(s,config){
s.createPamDiffEngine = function(e){
var width,
height,
globalSensitivity,
globalColorThreshold,
fullFrame = false
if(s.group[e.ke].mon_conf[e.id].details.detector_scale_x===''||s.group[e.ke].mon_conf[e.id].details.detector_scale_y===''){
width = s.group[e.ke].mon_conf[e.id].details.detector_scale_x;
height = s.group[e.ke].mon_conf[e.id].details.detector_scale_y;
}else{
width = e.width
height = e.height
}
if(e.details.detector_sensitivity===''){
globalSensitivity = 10
}else{
globalSensitivity = parseInt(e.details.detector_sensitivity)
}
if(e.details.detector_color_threshold===''){
globalColorThreshold = 9
}else{
globalColorThreshold = parseInt(e.details.detector_color_threshold)
}
globalThreshold = parseInt(e.details.detector_threshold) || 0
var regionJson
try{
regionJson = JSON.parse(s.group[e.ke].mon_conf[e.id].details.cords)
}catch(err){
regionJson = s.group[e.ke].mon_conf[e.id].details.cords
}
if(Object.keys(regionJson).length === 0 || e.details.detector_frame === '1'){
fullFrame = {
name:'FULL_FRAME',
sensitivity:globalSensitivity,
color_threshold:globalColorThreshold,
points:[
[0,0],
[0,height],
[width,height],
[width,0]
]
};
}
e.triggerTimer = {}
var regions = s.createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
s.group[e.ke].mon[e.id].pamDiff = new PamDiff({grayscale: 'luminosity', regions : regions.forPam});
s.group[e.ke].mon[e.id].p2p = new P2P();
var sendTrigger = function(trigger){
var detectorObject = {
f:'trigger',
id:e.id,
ke:e.ke,
name:trigger.name,
details:{
plug:'built-in',
name:trigger.name,
reason:'motion',
confidence:trigger.percent,
},
plates:[],
imgHeight:height,
imgWidth:width
}
var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
s.checkMaximumSensitivity(e, region, detectorObject, function() {
s.checkTriggerThreshold(e, region, detectorObject, function() {
detectorObject.doObjectDetection = (s.ocv && e.details.detector_use_detect_object === '1')
s.triggerEvent(detectorObject)
})
})
}
if(e.details.detector_noise_filter==='1'){
if(!s.group[e.ke].mon[e.id].noiseFilterArray)s.group[e.ke].mon[e.id].noiseFilterArray = {}
var noiseFilterArray = s.group[e.ke].mon[e.id].noiseFilterArray
Object.keys(regions.notForPam).forEach(function(name){
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
})
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
data.trigger.forEach(function(trigger){
s.filterTheNoise(e,noiseFilterArray,regions,trigger,function(){
sendTrigger(trigger)
})
})
})
}else{
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
data.trigger.forEach(sendTrigger)
})
}
}
s.createPamDiffRegionArray = function(regions,globalColorThreshold,globalSensitivity,fullFrame){
var pamDiffCompliantArray = [],
arrayForOtherStuff = [],
json
try{
json = JSON.parse(regions)
}catch(err){
json = regions
}
if(fullFrame){
json[fullFrame.name]=fullFrame;
}
Object.values(json).forEach(function(region){
if(!region)return false;
region.polygon = [];
region.points.forEach(function(points){
region.polygon.push({x:parseFloat(points[0]),y:parseFloat(points[1])})
})
if(region.sensitivity===''){
region.sensitivity = globalSensitivity
}else{
region.sensitivity = parseInt(region.sensitivity)
}
if(region.color_threshold===''){
region.color_threshold = globalColorThreshold
}else{
region.color_threshold = parseInt(region.color_threshold)
}
pamDiffCompliantArray.push({name: region.name, difference: region.color_threshold, percent: region.sensitivity, polygon:region.polygon})
arrayForOtherStuff[region.name] = region;
})
if(pamDiffCompliantArray.length===0){pamDiffCompliantArray = null}
return {forPam:pamDiffCompliantArray,notForPam:arrayForOtherStuff};
}
s.filterTheNoise = function(e,noiseFilterArray,regions,trigger,callback){
if(noiseFilterArray[trigger.name].length > 2){
var thePreviousTriggerPercent = noiseFilterArray[trigger.name][noiseFilterArray[trigger.name].length - 1];
var triggerDifference = trigger.percent - thePreviousTriggerPercent;
var noiseRange = e.details.detector_noise_filter_range
if(!noiseRange || noiseRange === ''){
noiseRange = 6
}
noiseRange = parseFloat(noiseRange)
if(((trigger.percent - thePreviousTriggerPercent) < noiseRange)||(thePreviousTriggerPercent - trigger.percent) > -noiseRange){
noiseFilterArray[trigger.name].push(trigger.percent);
}
}else{
noiseFilterArray[trigger.name].push(trigger.percent);
}
if(noiseFilterArray[trigger.name].length > 10){
noiseFilterArray[trigger.name] = noiseFilterArray[trigger.name].splice(1,10)
}
var theNoise = 0;
noiseFilterArray[trigger.name].forEach(function(v,n){
theNoise += v;
})
theNoise = theNoise / noiseFilterArray[trigger.name].length;
var triggerPercentWithoutNoise = trigger.percent - theNoise;
if(triggerPercentWithoutNoise > regions.notForPam[trigger.name].sensitivity){
callback(trigger)
}
}
s.checkMaximumSensitivity = function(monitor, region, detectorObject, success) {
var logName = detectorObject.id + ':' + detectorObject.name
var globalMaxSensitivity = parseInt(monitor.details.detector_max_sensitivity) || undefined
var maxSensitivity = parseInt(region.max_sensitivity) || globalMaxSensitivity
if (maxSensitivity === undefined || detectorObject.details.confidence <= maxSensitivity) {
success()
} else {
if (monitor.triggerTimer[detectorObject.name] !== undefined) {
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
monitor.triggerTimer[detectorObject.name] = undefined
}
}
}
s.checkTriggerThreshold = function(monitor, region, detectorObject, success){
var threshold = parseInt(region.threshold) || globalThreshold
if (threshold <= 1) {
success()
} else {
if (monitor.triggerTimer[detectorObject.name] === undefined) {
monitor.triggerTimer[detectorObject.name] = {
count : threshold,
timeout : null
}
}
if (--monitor.triggerTimer[detectorObject.name].count == 0) {
success()
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
monitor.triggerTimer[detectorObject.name] = undefined
} else {
var fps = parseFloat(monitor.details.detector_fps) || 2
if (monitor.triggerTimer[detectorObject.name].timeout !== null)
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
monitor.triggerTimer[detectorObject.name].timeout = setTimeout(function() {
monitor.triggerTimer[detectorObject.name] = undefined
}, ((threshold+0.5) * 1000) / fps)
}
}
}
}

384
libs/events.js Normal file
View file

@ -0,0 +1,384 @@
var moment = require('moment');
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var request = require('request');
module.exports = function(s,config,lang){
s.filterEvents = function(x,d){
switch(x){
case'archive':
d.videos.forEach(function(v,n){
s.video('archive',v)
})
break;
case'delete':
d.videos.forEach(function(v,n){
s.deleteVideo(v)
})
break;
case'execute':
exec(d.execute,{detached: true})
break;
}
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
extender(x,d)
})
}
s.triggerEvent = function(d){
var filter = {
halt : false,
addToMotionCounter : true,
useLock : true,
save : true,
webhook : true,
command : true,
record : true,
indifference : false
}
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
extender(d,filter)
})
if(s.group[d.ke].mon[d.id].open){
d.details.videoTime = s.group[d.ke].mon[d.id].open;
}
var detailString = JSON.stringify(d.details);
if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]){
return s.systemLog(lang['No Monitor Found, Ignoring Request'])
}
d.mon=s.group[d.ke].mon_conf[d.id];
var currentConfig = s.group[d.ke].mon[d.id].details
//read filters
if(
currentConfig.use_detector_filters === '1' &&
((currentConfig.use_detector_filters_object === '1' && d.details.matrices) ||
currentConfig.use_detector_filters_object !== '1')
){
var parseValue = function(key,val){
var newVal
switch(val){
case'':
newVal = filter[key]
break;
case'0':
newVal = false
break;
case'1':
newVal = true
break;
default:
newVal = val
break;
}
return newVal
}
var filters = currentConfig.detector_filters
Object.keys(filters).forEach(function(key){
var conditionChain = {}
var dFilter = filters[key]
dFilter.where.forEach(function(condition,place){
conditionChain[place] = {ok:false,next:condition.p4,matrixCount:0}
if(d.details.matrices)conditionChain[place].matrixCount = d.details.matrices.length
var modifyFilters = function(toCheck,matrixPosition){
var param = toCheck[condition.p1]
var pass = function(){
if(matrixPosition && dFilter.actions.halt === '1'){
delete(d.details.matrices[matrixPosition])
}else{
conditionChain[place].ok = true
}
}
switch(condition.p2){
case'indexOf':
if(param.indexOf(condition.p3) > -1){
pass()
}
break;
case'!indexOf':
if(param.indexOf(condition.p3) === -1){
pass()
}
break;
default:
if(eval('param '+condition.p2+' "'+condition.p3.replace(/"/g,'\\"')+'"')){
pass()
}
break;
}
}
switch(condition.p1){
case'tag':
case'x':
case'y':
case'height':
case'width':
if(d.details.matrices){
d.details.matrices.forEach(function(matrix,position){
modifyFilters(matrix,position)
})
}
break;
case'time':
var timeNow = new Date()
var timeCondition = new Date()
var doAtTime = condition.p3.split(':')
var atHour = parseInt(doAtTime[0]) - 1
var atHourNow = timeNow.getHours()
var atMinuteNow = timeNow.getMinutes()
var atSecondNow = timeNow.getSeconds()
if(atHour){
var atMinute = parseInt(doAtTime[1]) - 1 || timeNow.getMinutes()
var atSecond = parseInt(doAtTime[2]) - 1 || timeNow.getSeconds()
var nowAddedInSeconds = atHourNow * 60 * 60 + atMinuteNow * 60 + atSecondNow
var conditionAddedInSeconds = atHour * 60 * 60 + atMinute * 60 + atSecond
if(eval('nowAddedInSeconds '+condition.p2+' conditionAddedInSeconds')){
conditionChain[place].ok = true
}
}
break;
default:
modifyFilters(d.details)
break;
}
})
var conditionArray = Object.values(conditionChain)
var validationString = ''
conditionArray.forEach(function(condition,number){
validationString += condition.ok+' '
if(conditionArray.length-1 !== number){
validationString += condition.next+' '
}
})
if(eval(validationString)){
if(dFilter.actions.halt !== '1'){
delete(dFilter.actions.halt)
Object.keys(dFilter.actions).forEach(function(key){
var value = dFilter.actions[key]
filter[key] = parseValue(key,value)
})
}else{
filter.halt = true
}
}
})
if(d.details.matrices && d.details.matrices.length === 0 || filter.halt === true){
return
}else if(d.details.matrices && d.details.matrices.length > 0){
var reviewedMatrix = []
d.details.matrices.forEach(function(matrix){
if(matrix)reviewedMatrix.push(matrix)
})
d.details.matrices = reviewedMatrix
}
}
//motion counter
if(filter.addToMotionCounter && filter.record){
if(!s.group[d.ke].mon[d.id].detector_motion_count){
s.group[d.ke].mon[d.id].detector_motion_count=0
}
s.group[d.ke].mon[d.id].detector_motion_count+=1
}
if(filter.useLock){
if(s.group[d.ke].mon[d.id].motion_lock){
return
}
var detector_lock_timeout
if(!currentConfig.detector_lock_timeout||currentConfig.detector_lock_timeout===''){
detector_lock_timeout = 2000
}
detector_lock_timeout = parseFloat(currentConfig.detector_lock_timeout);
if(!s.group[d.ke].mon[d.id].detector_lock_timeout){
s.group[d.ke].mon[d.id].detector_lock_timeout=setTimeout(function(){
clearTimeout(s.group[d.ke].mon[d.id].detector_lock_timeout)
delete(s.group[d.ke].mon[d.id].detector_lock_timeout)
},detector_lock_timeout)
}else{
return
}
}
// check modified indifference
if(filter.indifference !== false && d.details.confidence < parseFloat(filter.indifference)){
// fails indifference check for modified indifference
return
}
//
if(d.doObjectDetection === true){
s.ocvTx({
f : 'frame',
mon : s.group[d.ke].mon_conf[d.id].details,
ke : d.ke,
id : d.id,
time : s.formattedTime(),
frame : s.group[d.ke].mon[d.id].lastJpegDetectorFrame
})
}else{
//save this detection result in SQL, only coords. not image.
if(filter.save && currentConfig.detector_save==='1'){
s.sqlQuery('INSERT INTO Events (ke,mid,details) VALUES (?,?,?)',[d.ke,d.id,detailString])
}
if(currentConfig.detector_notrigger === '1'){
var detector_notrigger_timeout
if(!currentConfig.detector_notrigger_timeout||currentConfig.detector_notrigger_timeout===''){
detector_notrigger_timeout = 10
}
detector_notrigger_timeout = parseFloat(currentConfig.detector_notrigger_timeout)*1000*60;
s.group[d.ke].mon[d.id].detector_notrigger_timeout = detector_notrigger_timeout;
clearInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout)
s.group[d.ke].mon[d.id].detector_notrigger_timeout = setInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout_function,detector_notrigger_timeout)
}
var detector_timeout
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
detector_timeout = 10
}else{
detector_timeout = parseFloat(currentConfig.detector_timeout)
}
if(filter.record && d.mon.mode=='start'&&currentConfig.detector_trigger==='1'&&currentConfig.detector_record_method==='sip'){
s.createEventBasedRecording(d)
}else if(filter.record && d.mon.mode!=='stop'&&currentConfig.detector_trigger=='1'&&currentConfig.detector_record_method==='hot'){
if(!d.auth){
d.auth=s.gid();
}
if(!s.group[d.ke].users[d.auth]){
s.group[d.ke].users[d.auth]={system:1,details:{},lang:lang}
}
d.urlQuery = []
d.url = 'http://'+config.ip+':'+config.port+'/'+d.auth+'/monitor/'+d.ke+'/'+d.id+'/record/'+detector_timeout+'/min';
if(currentConfig.watchdog_reset!=='0'){
d.urlQuery.push('reset=1')
}
if(currentConfig.detector_trigger_record_fps&&currentConfig.detector_trigger_record_fps!==''&&currentConfig.detector_trigger_record_fps!=='0'){
d.urlQuery.push('fps='+currentConfig.detector_trigger_record_fps)
}
if(d.urlQuery.length>0){
d.url+='?'+d.urlQuery.join('&')
}
request({url:d.url,method:'GET'},function(err,data){
if(err){
//could not start hotswap
}else{
delete(s.group[d.ke].users[d.auth])
d.cx.f='detector_record_engaged';
d.cx.msg = JSON.parse(data.body)
s.tx(d.cx,'GRP_'+d.ke);
}
})
}
d.currentTime = new Date()
d.currentTimestamp = s.timeObject(d.currentTime).format()
d.screenshotName = 'Motion_'+(d.mon.name.replace(/[^\w\s]/gi,''))+'_'+d.id+'_'+d.ke+'_'+s.formattedTime()
d.screenshotBuffer = null
s.onEventTriggerExtensions.forEach(function(extender){
extender(d,filter)
})
if(filter.webhook && currentConfig.detector_webhook === '1'){
var detector_webhook_url = currentConfig.detector_webhook_url
.replace(/{{TIME}}/g,d.currentTimestamp)
.replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{GROUP_KEY}}/g,d.ke)
.replace(/{{DETAILS}}/g,detailString)
if(d.details.confidence){
detector_webhook_url = detector_webhook_url
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
}
request({url:detector_webhook_url,method:'GET',encoding:null},function(err,data){
if(err){
s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
}
})
}
if(filter.command && currentConfig.detector_command_enable === '1' && !s.group[d.ke].mon[d.id].detector_command){
var detector_command_timeout
if(!currentConfig.detector_command_timeout||currentConfig.detector_command_timeout===''){
detector_command_timeout = 1000*60*10;
}else{
detector_command_timeout = parseFloat(currentConfig.detector_command_timeout)*1000*60;
}
s.group[d.ke].mon[d.id].detector_command=setTimeout(function(){
clearTimeout(s.group[d.ke].mon[d.id].detector_command);
delete(s.group[d.ke].mon[d.id].detector_command);
},detector_command_timeout);
var detector_command = currentConfig.detector_command
.replace(/{{TIME}}/g,d.currentTimestamp)
.replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{GROUP_KEY}}/g,d.ke)
.replace(/{{DETAILS}}/g,detailString)
if(d.details.confidence){
detector_command = detector_command
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
}
exec(detector_command,{detached: true})
}
}
//show client machines the event
d.cx={f:'detector_trigger',id:d.id,ke:d.ke,details:d.details,doObjectDetection:d.doObjectDetection};
s.tx(d.cx,'DETECTOR_'+d.ke+d.id);
}
s.createEventBasedRecording = function(d){
var currentConfig = s.group[d.ke].mon[d.id].details
var detector_timeout
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
detector_timeout = 10
}else{
detector_timeout = parseFloat(currentConfig.detector_timeout)
}
s.group[d.ke].mon[d.id].eventBasedRecording.timeout = setTimeout(function(){
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd=true;
},detector_timeout * 950 * 60)
if(!s.group[d.ke].mon[d.id].eventBasedRecording.process){
if(!d.auth){
d.auth = s.gid(60)
}
if(!s.api[d.auth]){
s.api[d.auth] = {
system: 1,
ip: '0.0.0.0',
details: {},
lang: lang
}
}
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = false;
var runRecord = function(){
var filename = s.formattedTime()+'.mp4'
s.userLog(d,{type:"Traditional Recording",msg:"Started"})
//-t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+'
s.group[d.ke].mon[d.id].eventBasedRecording.process = spawn(config.ffmpegDir,s.splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i http://'+config.ip+':'+config.port+'/'+d.auth+'/hls/'+d.ke+'/'+d.id+'/detectorStream.m3u8 -t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+' -c:v copy -strftime 1 "'+s.getVideoDirectory(d.mon) + filename + '"')))
var ffmpegError='';
var error
s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(data){
s.userLog(d,{type:"Traditional Recording",msg:data.toString()})
})
s.group[d.ke].mon[d.id].eventBasedRecording.process.on('close',function(){
if(!s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd){
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Process Exited Prematurely. Restarting."})
runRecord()
return
}
s.insertCompletedVideo(d.mon,{
file : filename
})
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Complete"})
delete(s.api[d.auth])
s.userLog(d,{type:"Traditional Recording",msg:'Clear Recorder Process'})
delete(s.group[d.ke].mon[d.id].eventBasedRecording.process)
delete(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
clearTimeout(s.group[d.ke].mon[d.id].recordingChecker)
})
}
runRecord()
}
}
s.closeEventBasedRecording = function(e){
if(s.group[e.ke].mon[e.id].eventBasedRecording.process){
clearTimeout(s.group[e.ke].mon[e.id].eventBasedRecording.timeout)
s.group[e.ke].mon[e.id].eventBasedRecording.allowEnd = true;
s.group[e.ke].mon[e.id].eventBasedRecording.process.kill('SIGTERM');
}
}
}

58
libs/extenders.js Normal file
View file

@ -0,0 +1,58 @@
module.exports = function(s,config){
////// USER //////
s.loadGroupAppExtensions = []
s.loadGroupAppExtender = function(callback){
s.loadGroupAppExtensions.push(callback)
}
//
s.unloadGroupAppExtensions = []
s.unloadGroupAppExtender = function(callback){
s.unloadGroupAppExtensions.push(callback)
}
//
s.cloudDisksLoaded = []
s.cloudDisksLoader = function(storageType){
s.cloudDisksLoaded.push(storageType)
}
//
s.onAccountSaveExtensions = []
s.onAccountSave = function(callback){
s.onAccountSaveExtensions.push(callback)
}
//
s.beforeAccountSaveExtensions = []
s.beforeAccountSave = function(callback){
s.beforeAccountSaveExtensions.push(callback)
}
//
s.onTwoFactorAuthCodeNotificationExtensions = []
s.onTwoFactorAuthCodeNotification = function(callback){
s.onTwoFactorAuthCodeNotificationExtensions.push(callback)
}
//
s.cloudDiskUseStartupExtensions = {}
////// EVENTS //////
s.onEventTriggerExtensions = []
s.onEventTrigger = function(callback){
s.onEventTriggerExtensions.push(callback)
}
s.onEventTriggerBeforeFilterExtensions = []
s.onEventTriggerBeforeFilter = function(callback){
s.onEventTriggerBeforeFilterExtensions.push(callback)
}
s.onFilterEventExtensions = []
s.onFilterEvent = function(callback){
s.onFilterEventExtensions.push(callback)
}
////// MONITOR //////
s.onMonitorInitExtensions = []
s.onMonitorInit = function(callback){
s.onMonitorInitExtensions.push(callback)
}
s.onDetectorNoTriggerTimeoutExtensions = []
s.onDetectorNoTriggerTimeout = function(callback){
s.onDetectorNoTriggerTimeoutExtensions.push(callback)
}
}

910
libs/ffmpeg.js Normal file
View file

@ -0,0 +1,910 @@
var fs = require('fs');
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
module.exports = function(s,config,onFinish){
var ffmpeg = {}
var downloadingFfmpeg = false;
//check local ffmpeg
ffmpeg.checkForWindows = function(failback){
if (s.isWin && fs.existsSync(s.mainDirectory+'/ffmpeg/ffmpeg.exe')) {
config.ffmpegDir = s.mainDirectory+'/ffmpeg/ffmpeg.exe'
}else{
failback()
}
}
//check local ffmpeg
ffmpeg.checkForUnix = function(failback){
if(s.isWin === false){
if (fs.existsSync('/usr/bin/ffmpeg')) {
config.ffmpegDir = '/usr/bin/ffmpeg'
}else{
if (fs.existsSync('/usr/local/bin/ffmpeg')) {
config.ffmpegDir = '/usr/local/bin/ffmpeg'
}else{
failback()
}
}
}else{
failback()
}
}
//check node module : ffmpeg-static
ffmpeg.checkForNpmStatic = function(failback){
try{
var staticFFmpeg = require('ffmpeg-static').path;
if (fs.statSync(staticFFmpeg)) {
config.ffmpegDir = staticFFmpeg
}else{
console.log('"ffmpeg-static" from NPM has failed to provide a compatible library or has been corrupted.')
console.log('Run "npm uninstall ffmpeg-static" to remove it.')
console.log('Run "npm install ffbinaries" to get a different static FFmpeg downloader.')
}
}catch(err){
console.log('No "ffmpeg-static".')
failback()
}
}
//check node module : ffbinaries
ffmpeg.checkForFfbinary = function(failback){
try{
ffbinaries = require('ffbinaries')
var ffbinaryDir = s.mainDirectory + '/ffmpeg/'
var downloadFFmpeg = function(){
downloadingFfmpeg = true
console.log('ffbinaries : Downloading FFmpeg. Please Wait...');
ffbinaries.downloadBinaries(['ffmpeg', 'ffprobe'], {
destination: ffbinaryDir,
version : '3.4'
},function () {
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
console.log('ffbinaries : FFmpeg Downloaded.');
ffmpeg.completeCheck()
})
}
if (!fs.existsSync(ffbinaryDir + 'ffmpeg')) {
downloadFFmpeg()
}else{
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
}
}catch(err){
console.log('No "ffbinaries". Continuing.')
console.log('Run "npm install ffbinaries" to get this static FFmpeg downloader.')
failback()
}
}
//ffmpeg version
ffmpeg.checkVersion = function(callback){
try{
s.ffmpegVersion = execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim()
if(s.ffmpegVersion.indexOf(': 2.')>-1){
s.systemLog('FFMPEG is too old : '+s.ffmpegVersion+', Needed : 3.2+',err)
throw (new Error())
}
}catch(err){
console.log('No FFmpeg found.')
// process.exit()
}
callback()
}
//check available hardware acceleration methods
ffmpeg.checkHwAccelMethods = function(callback){
if(config.availableHWAccels === undefined){
hwAccels = execSync(config.ffmpegDir+" -loglevel quiet -hwaccels").toString().split('\n')
hwAccels.shift()
availableHWAccels = []
hwAccels.forEach(function(method){
if(method && method !== '')availableHWAccels.push(method.trim())
})
config.availableHWAccels = availableHWAccels
config.availableHWAccels = ['auto'].concat(config.availableHWAccels)
console.log('Available Hardware Acceleration Methods : ',availableHWAccels.join(', '))
}
callback()
}
ffmpeg.completeCheck = function(){
ffmpeg.checkVersion(function(){
ffmpeg.checkHwAccelMethods(function(){
onFinish(ffmpeg)
})
})
}
//ffmpeg string cleaner, splits for use with spawn()
s.splitForFFPMEG = function (ffmpegCommandAsString) {
//this function ignores spaces inside quotes.
return ffmpegCommandAsString.replace(/\s+/g,' ').trim().match(/\\?.|^$/g).reduce((p, c) => {
if(c === '"'){
p.quote ^= 1;
}else if(!p.quote && c === ' '){
p.a.push('');
}else{
p.a[p.a.length-1] += c.replace(/\\(.)/,"$1");
}
return p;
}, {a: ['']}).a
};
s.createFFmpegMap = function(e,arrayOfMaps){
//`e` is the monitor object
var string = '';
if(e.details.input_maps && e.details.input_maps.length > 0){
if(arrayOfMaps && arrayOfMaps instanceof Array && arrayOfMaps.length>0){
arrayOfMaps.forEach(function(v){
if(v.map==='')v.map='0'
string += ' -map '+v.map
})
}else{
string += ' -map 0:0'
}
}
return string;
}
s.createInputMap = function(e,number,input){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
var x = {}
x.cust_input = ''
x.hwaccel = ''
if(input.cust_input&&input.cust_input!==''){x.cust_input+=' '+input.cust_input}
//input - analyze duration
if(input.aduration&&input.aduration!==''){x.cust_input+=' -analyzeduration '+input.aduration}
//input - probe size
if(input.probesize&&input.probesize!==''){x.cust_input+=' -probesize '+input.probesize}
//input - stream loop (good for static files/lists)
if(input.stream_loop === '1'){x.cust_input+=' -stream_loop -1'}
//input - fps
if(x.cust_input.indexOf('-r ')===-1&&input.sfps&&input.sfps!==''){
input.sfps=parseFloat(input.sfps);
if(isNaN(input.sfps)){input.sfps=1}
x.cust_input+=' -r '+input.sfps
}
//input - is mjpeg
if(input.type==='mjpeg'){
if(x.cust_input.indexOf('-f ')===-1){
x.cust_input+=' -f mjpeg'
}
//input - frames per second
x.cust_input+=' -reconnect 1'
}else
//input - is h264 has rtsp in address and transport method is chosen
if((input.type==='h264'||input.type==='mp4')&&input.fulladdress.indexOf('rtsp://')>-1&&input.rtsp_transport!==''&&input.rtsp_transport!=='no'){
x.cust_input += ' -rtsp_transport '+input.rtsp_transport
}else
if((input.type==='mp4'||input.type==='mjpeg')&&x.cust_input.indexOf('-re')===-1){
x.cust_input += ' -re'
}
//hardware acceleration
if(input.accelerator&&input.accelerator==='1'){
if(input.hwaccel&&input.hwaccel!==''){
x.hwaccel+=' -hwaccel '+input.hwaccel;
}
if(input.hwaccel_vcodec&&input.hwaccel_vcodec!==''&&input.hwaccel_vcodec!=='auto'&&input.hwaccel_vcodec!=='no'){
x.hwaccel+=' -c:v '+input.hwaccel_vcodec;
}
if(input.hwaccel_device&&input.hwaccel_device!==''){
switch(input.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+input.hwaccel_device+' -hwaccel_output_format vaapi';
break;
default:
x.hwaccel+=' -hwaccel_device '+input.hwaccel_device;
break;
}
}
}
//custom - input flags
return x.hwaccel+x.cust_input+' -i "'+input.fulladdress+'"';
}
//create sub stream channel
s.createStreamChannel = function(e,number,channel){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
var x = {
pipe:''
}
if(!number||number==''){
x.channel_sdir = e.sdir;
}else{
x.channel_sdir = e.sdir+'channel'+number+'/';
if (!fs.existsSync(x.channel_sdir)){
fs.mkdirSync(x.channel_sdir);
}
}
x.stream_video_filters=[]
//stream - frames per second
if(channel.stream_vcodec!=='copy'){
if(!channel.stream_fps||channel.stream_fps===''){
switch(channel.stream_type){
case'rtmp':
channel.stream_fps=30
break;
default:
// channel.stream_fps=5
break;
}
}
}
if(channel.stream_fps&&channel.stream_fps!==''){x.stream_fps=' -r '+channel.stream_fps}else{x.stream_fps=''}
//stream - hls vcodec
if(channel.stream_vcodec&&channel.stream_vcodec!=='no'){
if(channel.stream_vcodec!==''){x.stream_vcodec=' -c:v '+channel.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
}else{
x.stream_vcodec='';
}
//stream - hls acodec
if(channel.stream_acodec!=='no'){
if(channel.stream_acodec&&channel.stream_acodec!==''){x.stream_acodec=' -c:a '+channel.stream_acodec}else{x.stream_acodec=''}
}else{
x.stream_acodec=' -an';
}
//stream - resolution
if(channel.stream_scale_x&&channel.stream_scale_x!==''&&channel.stream_scale_y&&channel.stream_scale_y!==''){
x.dimensions = channel.stream_scale_x+'x'+channel.stream_scale_y;
}
//stream - hls segment time
if(channel.hls_time&&channel.hls_time!==''){x.hls_time=channel.hls_time}else{x.hls_time="2"}
//hls list size
if(channel.hls_list_size&&channel.hls_list_size!==''){x.hls_list_size=channel.hls_list_size}else{x.hls_list_size=2}
//stream - custom flags
if(channel.cust_stream&&channel.cust_stream!==''){x.cust_stream=' '+channel.cust_stream}else{x.cust_stream=''}
//stream - preset
if(channel.stream_type !== 'h265' && channel.preset_stream && channel.preset_stream!==''){x.preset_stream=' -preset '+channel.preset_stream;}else{x.preset_stream=''}
//hardware acceleration
if(e.details.accelerator&&e.details.accelerator==='1'){
if(e.details.hwaccel === 'auto')e.details.hwaccel = ''
if(e.details.hwaccel && e.details.hwaccel!==''){
x.hwaccel+=' -hwaccel '+e.details.hwaccel;
}
if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
}
if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
switch(e.details.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device+' -hwaccel_output_format vaapi';
break;
default:
x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
break;
}
}
// else{
// if(e.details.hwaccel==='vaapi'){
// x.hwaccel+=' -hwaccel_device 0';
// }
// }
}
if(channel.rotate_stream&&channel.rotate_stream!==""&&channel.rotate_stream!=="no"){
x.stream_video_filters.push('transpose='+channel.rotate_stream);
}
//stream - video filter
if(channel.svf&&channel.svf!==''){
x.stream_video_filters.push(channel.svf)
}
if(x.stream_video_filters.length>0){
var string = x.stream_video_filters.join(',').trim()
if(string===''){
x.stream_video_filters=''
}else{
x.stream_video_filters=' -vf '+string
}
}else{
x.stream_video_filters=''
}
if(e.details.input_map_choices&&e.details.input_map_choices.record){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices['stream_channel-'+(number-config.pipeAddition)])
}
if(channel.stream_vcodec !== 'copy' || channel.stream_type === 'mjpeg' || channel.stream_type === 'b64'){
x.cust_stream += x.stream_fps
}
switch(channel.stream_type){
case'mp4':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1'
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'rtmp':
x.rtmp_server_url=s.checkCorrectPathEnding(channel.rtmp_server_url);
if(channel.stream_vcodec!=='copy'){
if(channel.stream_vcodec==='libx264'){
channel.stream_vcodec = 'h264'
}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
if(channel.stream_v_br&&channel.stream_v_br!==''){x.cust_stream+=' -b:v '+channel.stream_v_br}
}
if(channel.stream_vcodec!=='no'&&channel.stream_vcodec!==''){
x.cust_stream+=' -vcodec '+channel.stream_vcodec
}
if(channel.stream_acodec!=='copy'){
if(!channel.stream_acodec||channel.stream_acodec===''||channel.stream_acodec==='no'){
channel.stream_acodec = 'aac'
}
if(!channel.stream_a_br||channel.stream_a_br===''){channel.stream_a_br='128k'}
x.cust_stream+=' -ab '+channel.stream_a_br
}
if(channel.stream_acodec!==''){
x.cust_stream+=' -acodec '+channel.stream_acodec
}
x.pipe+=' -f flv'+x.stream_video_filters+x.cust_stream+' "'+x.rtmp_server_url+channel.rtmp_stream_key+'"';
break;
case'h264':
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mpegts'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'flv':
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'hls':
if(channel.stream_vcodec!=='h264_vaapi'&&channel.stream_vcodec!=='copy'){
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.cust_stream+=x.stream_video_filters
}
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+x.channel_sdir+'s.m3u8"';
break;
case'mjpeg':
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -q:v '+channel.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:'+number;
break;
default:
x.pipe=''
break;
}
return x.pipe
}
ffmpeg.buildMainInput = function(e,x){
//e = monitor object
//x = temporary values
//check if CUDA is enabled
e.isStreamer = (e.type === 'dashcam'|| e.type === 'socket')
if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
e.cudaEnabled = true
}
//
x.hwaccel = ''
x.cust_input = ''
//input - frame rate (capture rate)
if(e.details.sfps && e.details.sfps!==''){x.input_fps=' -r '+e.details.sfps}else{x.input_fps=''}
//input - analyze duration
if(e.details.aduration&&e.details.aduration!==''){x.cust_input+=' -analyzeduration '+e.details.aduration};
//input - probe size
if(e.details.probesize&&e.details.probesize!==''){x.cust_input+=' -probesize '+e.details.probesize};
//input - stream loop (good for static files/lists)
if(e.details.stream_loop === '1' && (e.type === 'mp4' || e.type === 'local')){x.cust_input+=' -stream_loop -1'};
//input
if(e.details.cust_input.indexOf('-fflags') === -1){x.cust_input+=' -fflags +igndts'}
switch(e.type){
case'h264':
switch(e.protocol){
case'rtsp':
if(e.details.rtsp_transport&&e.details.rtsp_transport!==''&&e.details.rtsp_transport!=='no'){x.cust_input+=' -rtsp_transport '+e.details.rtsp_transport;}
break;
}
break;
}
//hardware acceleration
if(e.details.accelerator && e.details.accelerator==='1' && e.isStreamer === false){
if(e.details.hwaccel&&e.details.hwaccel!==''){
x.hwaccel+=' -hwaccel '+e.details.hwaccel;
}
if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
}
if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
switch(e.details.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device;
break;
default:
x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
break;
}
}
// else{
// if(e.details.hwaccel==='vaapi'){
// x.hwaccel+=' -hwaccel_device 0';
// }
// }
}
//logging - level
if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'}
//custom - input flags
if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;}
//add main input
if((e.type === 'mp4' || e.type === 'mjpeg') && x.cust_input.indexOf('-re') === -1){
x.cust_input += ' -re'
}
}
ffmpeg.buildMainStream = function(e,x){
//e = monitor object
//x = temporary values
x.stream_video_filters = []
x.pipe = ''
//stream - timestamp
if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'}
//position y
if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'}
//text color
if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'}
//box color
if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'}
//text size
if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'}
x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size);
}
//stream - watermark for -vf
if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
switch(e.details.stream_watermark_position){
case'tl'://top left
x.stream_watermark_position='10:10'
break;
case'tr'://top right
x.stream_watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.stream_watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
}
//stream - rotation
if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"&&e.details.stream_vcodec!=='copy'){
x.stream_video_filters.push('transpose='+e.details.rotate_stream);
}
//stream - hls vcodec
if(e.details.stream_vcodec&&e.details.stream_vcodec!=='no'){
if(e.details.stream_vcodec!==''){x.stream_vcodec=' -c:v '+e.details.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
}else{
x.stream_vcodec='';
}
//stream - hls acodec
if(e.details.stream_acodec!=='no'){
if(e.details.stream_acodec&&e.details.stream_acodec!==''){x.stream_acodec=' -c:a '+e.details.stream_acodec}else{x.stream_acodec=''}
}else{
x.stream_acodec=' -an';
}
//stream - hls segment time
if(e.details.hls_time&&e.details.hls_time!==''){x.hls_time=e.details.hls_time}else{x.hls_time="2"} //hls list size
if(e.details.hls_list_size&&e.details.hls_list_size!==''){x.hls_list_size=e.details.hls_list_size}else{x.hls_list_size=2}
//stream - custom flags
if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
//stream - preset
if(e.details.stream_type !== 'h265' && e.details.preset_stream && e.details.preset_stream !== ''){x.preset_stream=' -preset '+e.details.preset_stream;}else{x.preset_stream=''}
if(e.details.stream_vcodec==='h264_vaapi'){
x.stream_video_filters=[]
x.stream_video_filters.push('format=nv12,hwupload');
if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
x.stream_video_filters.push('scale_vaapi=w='+e.details.stream_scale_x+':h='+e.details.stream_scale_y)
}
}
if(e.cudaEnabled && (e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64')){
x.stream_video_filters.push('hwdownload,format=nv12')
}
//stream - video filter
if(e.details.svf && e.details.svf !== ''){
x.stream_video_filters.push(e.details.svf)
}
if(x.stream_video_filters.length>0){
x.stream_video_filters=' -vf "'+x.stream_video_filters.join(',')+'"'
}else{
x.stream_video_filters=''
}
//stream - pipe build
if(e.details.input_map_choices&&e.details.input_map_choices.stream){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.stream)
}
if(e.details.stream_vcodec !== 'copy' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64'){
x.cust_stream += x.stream_fps
}
switch(e.details.stream_type){
case'mp4':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1'
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'flv':
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'hls':
if(e.details.stream_vcodec!=='h264_vaapi'&&e.details.stream_vcodec!=='copy'){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.cust_stream+=x.stream_video_filters
}
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'s.m3u8"';
break;
case'mjpeg':
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
case'h265':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1'
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f hevc'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'b64':case'':case undefined:case null://base64
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
default:
x.pipe=''
break;
}
if(e.details.stream_channels){
e.details.stream_channels.forEach(function(v,n){
x.pipe += s.createStreamChannel(e,n+config.pipeAddition,v)
})
}
//api - snapshot bin/ cgi.bin (JPEG Mode)
if(e.details.snap === '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.snap){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.snap)
}
if(!e.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
if(e.details.snap_vf && e.details.snap_vf !== '' || e.cudaEnabled){
var snapVf = e.details.snap_vf.split(',')
if(e.details.snap_vf === '')snapVf.shift()
if(e.cudaEnabled){
snapVf.push('hwdownload,format=nv12')
}
//-vf "thumbnail_cuda=2,hwdownload,format=nv12"
x.snap_vf=' -vf "'+snapVf.join(',')+'"'
}else{
x.snap_vf=''
}
if(e.details.snap_scale_x && e.details.snap_scale_x !== '' && e.details.snap_scale_y && e.details.snap_scale_y !== ''){x.snap_ratio = ' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.snap_ratio=''}
if(e.details.cust_snap && e.details.cust_snap !== ''){x.cust_snap = ' '+e.details.cust_snap}else{x.cust_snap=''}
x.pipe+=' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.snap_ratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y';
}
//custom - output
if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;}
}
ffmpeg.buildMainRecording = function(e,x){
//e = monitor object
//x = temporary values
x.record_video_filters = []
x.record_string = ''
//record - resolution
if(e.width!==''&&e.height!==''&&!isNaN(e.width)&&!isNaN(e.height)){
x.record_dimensions=' -s '+e.width+'x'+e.height
}else{
x.record_dimensions=''
}
if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
x.dimensions = e.details.stream_scale_x+'x'+e.details.stream_scale_y;
}
//record - segmenting
x.segment=' -f segment -segment_atclocktime 1 -reset_timestamps 1 -strftime 1 -segment_list pipe:2 -segment_time '+(60*e.cutoff)+' "'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"';
//record - set defaults for extension, video quality
switch(e.ext){
case'mp4':
x.vcodec='libx264';x.acodec='aac';
if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -crf '+e.details.crf}
break;
case'webm':
x.acodec='libvorbis',x.vcodec='libvpx';
if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -q:v '+e.details.crf}else{x.vcodec+=' -q:v 1';}
break;
}
if(e.details.vcodec==='h264_vaapi'){
x.record_video_filters.push('format=nv12,hwupload');
}
//record - use custom video codec
if(e.details.vcodec&&e.details.vcodec!==''&&e.details.vcodec!=='default'){x.vcodec=e.details.vcodec}
//record - use custom audio codec
if(e.details.acodec&&e.details.acodec!==''&&e.details.acodec!=='default'){x.acodec=e.details.acodec}
if(e.details.cust_record){
if(x.acodec=='aac'&&e.details.cust_record.indexOf('-strict -2')===-1){e.details.cust_record+=' -strict -2';}
if(e.details.cust_record.indexOf('-threads')===-1){e.details.cust_record+=' -threads 1';}
}
// if(e.details.cust_input&&(e.details.cust_input.indexOf('-use_wallclock_as_timestamps 1')>-1)===false){e.details.cust_input+=' -use_wallclock_as_timestamps 1';}
//record - ready or reset codecs
if(x.acodec!=='no'){
if(x.acodec.indexOf('none')>-1){x.acodec=''}else{x.acodec=' -acodec '+x.acodec}
}else{
x.acodec=' -an'
}
if(x.vcodec.indexOf('none')>-1){x.vcodec=''}else{x.vcodec=' -vcodec '+x.vcodec}
//record - frames per second (fps)
if(e.fps&&e.fps!==''&&e.details.vcodec!=='copy'){x.record_fps=' -r '+e.fps}else{x.record_fps=''}
//stream - frames per second (fps)
if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
//record - timestamp options for -vf
if(e.details.timestamp&&e.details.timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.timestamp_font&&e.details.timestamp_font!==''){x.time_font=e.details.timestamp_font}else{x.time_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.timestamp_x&&e.details.timestamp_x!==''){x.timex=e.details.timestamp_x}else{x.timex='(w-tw)/2'}
//position y
if(e.details.timestamp_y&&e.details.timestamp_y!==''){x.timey=e.details.timestamp_y}else{x.timey='0'}
//text color
if(e.details.timestamp_color&&e.details.timestamp_color!==''){x.time_color=e.details.timestamp_color}else{x.time_color='white'}
//box color
if(e.details.timestamp_box_color&&e.details.timestamp_box_color!==''){x.time_box_color=e.details.timestamp_box_color}else{x.time_box_color='0x00000000@1'}
//text size
if(e.details.timestamp_font_size&&e.details.timestamp_font_size!==''){x.time_font_size=e.details.timestamp_font_size}else{x.time_font_size='10'}
x.record_video_filters.push('drawtext=fontfile='+x.time_font+':text=\'%{localtime}\':x='+x.timex+':y='+x.timey+':fontcolor='+x.time_color+':box=1:boxcolor='+x.time_box_color+':fontsize='+x.time_font_size);
}
//record - watermark for -vf
if(e.details.watermark&&e.details.watermark=="1"&&e.details.watermark_location&&e.details.watermark_location!==''){
switch(e.details.watermark_position){
case'tl'://top left
x.watermark_position='10:10'
break;
case'tr'://top right
x.watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.record_video_filters.push('movie='+e.details.watermark_location+'[watermark],[in][watermark]overlay='+x.watermark_position+'[out]');
}
//record - rotation
if(e.details.rotate_record&&e.details.rotate_record!==""&&e.details.rotate_record!=="no"&&e.details.stream_vcodec!=="copy"){
x.record_video_filters.push('transpose='+e.details.rotate_record);
}
//check custom record filters for -vf
if(e.details.vf&&e.details.vf!==''){
x.record_video_filters.push(e.details.vf)
}
//compile filter string for -vf
if(x.record_video_filters.length>0){
x.record_video_filters=' -vf '+x.record_video_filters.join(',')
}else{
x.record_video_filters=''
}
//build record string.
if(e.mode === 'record'){
if(e.details.input_map_choices&&e.details.input_map_choices.record){
//add input feed map
x.record_string += s.createFFmpegMap(e,e.details.input_map_choices.record)
}
//if h264, hls, mp4, or local add the audio codec flag
switch(e.type){
case'h264':case'hls':case'mp4':case'local':
x.record_string+=x.acodec;
break;
}
//custom flags
if(e.details.cust_record&&e.details.cust_record!==''){x.record_string+=' '+e.details.cust_record;}
//preset flag
if(e.details.preset_record&&e.details.preset_record!==''){x.record_string+=' -preset '+e.details.preset_record;}
//main string write
x.record_string+=x.vcodec+x.record_fps+x.record_video_filters+x.record_dimensions+x.segment;
}
}
ffmpeg.buildMainDetector = function(e,x){
//e = monitor object
//x = temporary values
x.cust_detect = ' '
//detector - plugins, motion
if(e.details.detector === '1' && e.details.detector_send_frames === '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
}
if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
x.detector_vf = ['fps='+e.details.detector_fps]
if(e.cudaEnabled){
x.detector_vf.push('hwdownload,format=nv12')
}
x.detector_vf = '-vf "'+x.detector_vf.join(',')+'"'
if(e.details.detector_pam==='1'){
if(e.cudaEnabled){
x.pipe += ' -vf "hwdownload,format=nv12"'
}
x.pipe+=' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
if(e.details.detector_use_detect_object === '1'){
//for object detection
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
x.pipe += ' -f singlejpeg '+x.detector_vf+x.cust_detect+x.dratio+' pipe:4';
}
}else{
x.pipe+=' -f image2pipe '+x.detector_vf+x.cust_detect+x.dratio+' pipe:3';
}
}
//Traditional Recording Buffer
if(e.details.detector=='1'&&e.details.detector_trigger=='1'&&e.details.detector_record_method==='sip'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector_sip_buffer){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector_sip_buffer)
}
x.detector_buffer_filters=[]
if(!e.details.detector_buffer_vcodec||e.details.detector_buffer_vcodec===''||e.details.detector_buffer_vcodec==='auto'){
switch(e.type){
case'h264':case'hls':case'mp4':
e.details.detector_buffer_vcodec = 'copy'
break;
default:
if(e.details.accelerator === '1' && e.cudaEnabled){
e.details.detector_buffer_vcodec = 'h264_nvenc'
}else{
e.details.detector_buffer_vcodec = 'libx264'
}
break;
}
}
if(!e.details.detector_buffer_acodec||e.details.detector_buffer_acodec===''||e.details.detector_buffer_acodec==='auto'){
switch(e.type){
case'mjpeg':case'jpeg':case'socket':
e.details.detector_buffer_acodec = 'no'
break;
case'h264':case'hls':case'mp4':
e.details.detector_buffer_acodec = 'copy'
break;
default:
e.details.detector_buffer_acodec = 'aac'
break;
}
}
if(e.details.detector_buffer_acodec === 'no'){
x.detector_buffer_acodec = ' -an'
}else{
x.detector_buffer_acodec = ' -c:a '+e.details.detector_buffer_acodec
}
if(!e.details.detector_buffer_tune||e.details.detector_buffer_tune===''){e.details.detector_buffer_tune='zerolatency'}
if(!e.details.detector_buffer_g||e.details.detector_buffer_g===''){e.details.detector_buffer_g='1'}
if(!e.details.detector_buffer_hls_time||e.details.detector_buffer_hls_time===''){e.details.detector_buffer_hls_time='2'}
if(!e.details.detector_buffer_hls_list_size||e.details.detector_buffer_hls_list_size===''){e.details.detector_buffer_hls_list_size='4'}
if(!e.details.detector_buffer_start_number||e.details.detector_buffer_start_number===''){e.details.detector_buffer_start_number='0'}
if(!e.details.detector_buffer_live_start_index||e.details.detector_buffer_live_start_index===''){e.details.detector_buffer_live_start_index='-3'}
if(e.details.detector_buffer_vcodec.indexOf('_vaapi')>-1){
if(x.hwaccel.indexOf('-vaapi_device')>-1){
x.detector_buffer_filters.push('format=nv12')
x.detector_buffer_filters.push('hwupload')
}else{
e.details.detector_buffer_vcodec='libx264'
}
}
if(e.details.detector_buffer_vcodec!=='copy'){
if(e.details.detector_buffer_fps&&e.details.detector_buffer_fps!==''){
x.detector_buffer_fps=' -r '+e.details.detector_buffer_fps
}else{
x.detector_buffer_fps=' -r 30'
}
}else{
x.detector_buffer_fps=''
}
if(x.detector_buffer_filters.length>0){
x.pipe+=' -vf '+x.detector_buffer_filters.join(',')
}
x.pipe+=x.detector_buffer_fps+x.detector_buffer_acodec+' -c:v '+e.details.detector_buffer_vcodec+' -f hls -tune '+e.details.detector_buffer_tune+' -g '+e.details.detector_buffer_g+' -hls_time '+e.details.detector_buffer_hls_time+' -hls_list_size '+e.details.detector_buffer_hls_list_size+' -start_number '+e.details.detector_buffer_start_number+' -live_start_index '+e.details.detector_buffer_live_start_index+' -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStream.m3u8"'
}
}
ffmpeg.assembleMainPieces = function(e,x){
//create executeable FFMPEG command
x.ffmpegCommandString = x.loglevel+x.input_fps;
//progress pipe
x.ffmpegCommandString += ' -progress pipe:5';
switch(e.type){
case'dashcam':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i -';
break;
case'socket':case'jpeg':case'pipe'://case'webpage':
x.ffmpegCommandString += ' -pattern_type glob -f image2pipe'+x.record_fps+' -vcodec mjpeg'+x.cust_input+x.hwaccel+' -i -';
break;
case'mjpeg':
x.ffmpegCommandString += ' -reconnect 1 -f mjpeg'+x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
case'h264':case'hls':case'mp4':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
case'local':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.path+'"';
break;
}
//add extra input maps
if(e.details.input_maps){
e.details.input_maps.forEach(function(v,n){
x.ffmpegCommandString += s.createInputMap(e,n+1,v)
})
}
//add recording and stream outputs
x.ffmpegCommandString += x.record_string+x.pipe
}
ffmpeg.createPipeArray = function(e,x){
//create additional pipes from ffmpeg
x.stdioPipes = [];
var times = config.pipeAddition;
if(e.details.stream_channels){
times+=e.details.stream_channels.length
}
for(var i=0; i < times; i++){
x.stdioPipes.push('pipe')
}
}
s.ffmpeg = function(e){
//set X for temporary values so we don't break our main monitor object.
var x = {tmp : ''}
//set some placeholding values to avoid "undefined" in ffmpeg string.
ffmpeg.buildMainInput(e,x)
ffmpeg.buildMainStream(e,x)
ffmpeg.buildMainRecording(e,x)
ffmpeg.buildMainDetector(e,x)
ffmpeg.assembleMainPieces(e,x)
ffmpeg.createPipeArray(e,x)
//hold ffmpeg command for log stream
s.group[e.ke].mon[e.mid].ffmpeg = x.ffmpegCommandString
//clean the string of spatial impurities and split for spawn()
x.ffmpegCommandString = s.splitForFFPMEG(x.ffmpegCommandString)
//launch that bad boy
return spawn(config.ffmpegDir,x.ffmpegCommandString,{detached: true,stdio:x.stdioPipes})
}
if(!config.ffmpegDir){
ffmpeg.checkForWindows(function(){
ffmpeg.checkForUnix(function(){
ffmpeg.checkForFfbinary(function(){
ffmpeg.checkForNpmStatic(function(){
console.log('No FFmpeg found.')
})
})
})
})
}
if(downloadingFfmpeg === false){
//not downloading ffmpeg
ffmpeg.completeCheck()
}
return ffmpeg
}

49
libs/folders.js Normal file
View file

@ -0,0 +1,49 @@
var fs = require('fs');
module.exports = function(s,config){
//directories
s.group={};
if(!config.windowsTempDir&&s.isWin===true){config.windowsTempDir='C:/Windows/Temp'}
if(!config.defaultMjpeg){config.defaultMjpeg=s.mainDirectory+'/web/libs/img/bg.jpg'}
//default stream folder check
if(!config.streamDir){
if(s.isWin===false){
config.streamDir='/dev/shm'
}else{
config.streamDir=config.windowsTempDir
}
if(!fs.existsSync(config.streamDir)){
config.streamDir=s.mainDirectory+'/streams/'
}else{
config.streamDir+='/streams/'
}
}
if(!config.videosDir){config.videosDir=s.mainDirectory+'/videos/'}
if(!config.binDir){config.binDir=s.mainDirectory+'/fileBin/'}
if(!config.addStorage){config.addStorage=[]}
s.dir={
videos:s.checkCorrectPathEnding(config.videosDir),
streams:s.checkCorrectPathEnding(config.streamDir),
fileBin:s.checkCorrectPathEnding(config.binDir),
addStorage:config.addStorage,
languages:s.location.languages+'/'
};
//streams dir
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
//videos dir
if(!fs.existsSync(s.dir.videos)){
fs.mkdirSync(s.dir.videos);
}
//fileBin dir
if(!fs.existsSync(s.dir.fileBin)){
fs.mkdirSync(s.dir.fileBin);
}
//additional storage areas
s.dir.addStorage.forEach(function(v,n){
v.path=s.checkCorrectPathEnding(v.path)
if(!fs.existsSync(v.path)){
fs.mkdirSync(v.path);
}
})
}

76
libs/health.js Normal file
View file

@ -0,0 +1,76 @@
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
module.exports = function(s,config,lang,io){
s.sendDiskUsedAmountToClients = function(e){
//send the amount used disk space to connected users
if(s.group[e.ke]&&s.group[e.ke].init){
s.tx({f:'diskUsed',size:s.group[e.ke].usedSpace,limit:s.group[e.ke].sizeLimit},'GRP_'+e.ke);
}
}
s.heartBeat = function(){
setTimeout(s.heartBeat, 8000);
io.sockets.emit('ping',{beat:1});
}
s.heartBeat()
s.cpuUsage = function(callback){
k={}
switch(s.platform){
case'win32':
k.cmd="@for /f \"skip=1\" %p in ('wmic cpu get loadpercentage') do @echo %p%"
break;
case'darwin':
k.cmd="ps -A -o %cpu | awk '{s+=$1} END {print s}'";
break;
case'linux':
k.cmd='LANG=C top -b -n 2 | grep "^'+config.cpuUsageMarker+'" | awk \'{print $2}\' | tail -n1';
break;
case'freebsd':
k.cmd='vmstat 1 2 | tail -1 | awk \'{print $17}\''
break;
}
if(config.customCpuCommand){
exec(config.customCpuCommand,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true) {
d = d.replace(/(\r\n|\n|\r)/gm, "").replace(/%/g, "")
}
callback(d)
});
} else if(k.cmd){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
}
callback(d)
});
} else {
callback(0)
}
}
s.ramUsage = function(callback){
k={}
switch(s.platform){
case'win32':
k.cmd = "wmic OS get FreePhysicalMemory /Value"
break;
case'darwin':
k.cmd = "vm_stat | awk '/^Pages free: /{f=substr($3,1,length($3)-1)} /^Pages active: /{a=substr($3,1,length($3-1))} /^Pages inactive: /{i=substr($3,1,length($3-1))} /^Pages speculative: /{s=substr($3,1,length($3-1))} /^Pages wired down: /{w=substr($4,1,length($4-1))} /^Pages occupied by compressor: /{c=substr($5,1,length($5-1)); print ((a+w)/(f+a+i+w+s+c))*100;}'"
break;
case'freebsd':
k.cmd = "echo \"scale=4; $(vmstat -H | tail -1 | awk '{print $5}')*1024*100/$(sysctl hw.physmem | awk '{print $2}')\" | bc"
break;
default:
k.cmd = "LANG=C free | grep Mem | awk '{print $7/$2 * 100.0}'";
break;
}
if(k.cmd){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=(parseInt(d.split('=')[1])/(s.totalmem/1000))*100
}
callback(d)
});
}else{
callback(0)
}
}
}

61
libs/language.js Normal file
View file

@ -0,0 +1,61 @@
module.exports = function(s,config){
if(!config.language){
config.language='en_CA'
}
try{
var lang = require(s.location.languages+'/'+config.language+'.json');
}catch(er){
console.error(er)
console.log('There was an error loading your language file.')
var lang = require(s.location.languages+'/en_CA.json');
}
s.location.definitions = s.mainDirectory+'/definitions'
try{
var definitions = require(s.location.definitions+'/'+config.language+'.json');
}catch(er){
console.error(er)
console.log('There was an error loading your language file.')
var definitions = require(s.location.definitions+'/en_CA.json');
}
//load languages dynamically
s.loadedLanguages={}
s.loadedLanguages[config.language]=lang;
s.getLanguageFile = function(rule){
if(rule && rule !== ''){
var file = s.loadedLanguages[file]
if(!file){
try{
s.loadedLanguages[rule] = require(s.location.languages+'/'+rule+'.json')
s.loadedLanguages[rule] = Object.assign(lang,s.loadedLanguages[rule])
file = s.loadedLanguages[rule]
}catch(err){
file = lang
}
}
}else{
file = lang
}
return file
}
//load defintions dynamically
s.loadedDefinitons={}
s.loadedDefinitons[config.language]=definitions;
s.getDefinitonFile = function(rule){
if(rule && rule !== ''){
var file = s.loadedDefinitons[file]
if(!file){
try{
s.loadedDefinitons[rule] = require(s.location.definitions+'/'+rule+'.json')
s.loadedDefinitons[rule] = Object.assign(definitions,s.loadedDefinitons[rule])
file = s.loadedDefinitons[rule]
}catch(err){
file = definitions
}
}
}else{
file = definitions
}
return file
}
return lang
}

1321
libs/monitor.js Normal file

File diff suppressed because it is too large Load diff

318
libs/notification.js Normal file
View file

@ -0,0 +1,318 @@
var Discord = require("discord.js")
module.exports = function(s,config,lang){
//discord bot
if(config.discordBot === true){
try{
s.discordMsg = function(data,files,groupKey){
if(!data)data = {};
var bot = s.group[groupKey].discordBot
if(!bot){
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordFailedText,msg:lang.DiscordNotEnabledText})
return
}
var sendBody = Object.assign({
color: 3447003,
title: 'Alert from Shinobi',
description: "",
fields: [],
timestamp: new Date(),
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},data)
bot.channels.get(s.group[groupKey].init.discordbot_channel).send({
embed: sendBody,
files: files
}).catch(err => {
if(err){
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordErrorText,msg:err})
s.group[groupKey].discordBot = null
s.loadGroupApps({ke:groupKey})
}
})
}
var onEventTriggerBeforeFilterForDiscord = function(d,filter){
filter.discord = true
}
var onEventTriggerForDiscord = function(d,filter){
// d = event object
//discord bot
if(filter.discord && d.mon.details.detector_discordbot === '1' && !s.group[d.ke].mon[d.id].detector_discordbot){
var detector_discordbot_timeout
if(!d.mon.details.detector_discordbot_timeout||d.mon.details.detector_discordbot_timeout===''){
detector_discordbot_timeout = 1000*60*10;
}else{
detector_discordbot_timeout = parseFloat(d.mon.details.detector_discordbot_timeout)*1000*60;
}
//lock mailer so you don't get emailed on EVERY trigger event.
s.group[d.ke].mon[d.id].detector_discordbot=setTimeout(function(){
//unlock so you can mail again.
clearTimeout(s.group[d.ke].mon[d.id].detector_discordbot);
delete(s.group[d.ke].mon[d.id].detector_discordbot);
},detector_discordbot_timeout);
var files = []
var sendAlert = function(){
s.discordMsg({
author: {
name: s.group[d.ke].mon_conf[d.id].name,
icon_url: config.iconURL
},
title: lang.Event+' - '+d.screenshotName,
description: lang.EventText1+' '+d.currentTimestamp,
fields: [],
timestamp: d.currentTime,
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},files,d.ke)
}
if(d.mon.details.detector_discordbot_send_video === '1'){
s.mergeDetectorBufferChunks(d,function(mergedFilepath,filename){
s.discordMsg({
author: {
name: s.group[d.ke].mon_conf[d.id].name,
icon_url: config.iconURL
},
title: filename,
fields: [],
timestamp: d.currentTime,
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},[
{
attachment: mergedFilepath,
name: filename
}
],d.ke)
})
}
s.getRawSnapshotFromMonitor(d.mon,function(data){
if((data[data.length-2] === 0xFF && data[data.length-1] === 0xD9)){
d.screenshotBuffer = data
files.push({
attachment: d.screenshotBuffer,
name: d.screenshotName+'.jpg'
})
}
sendAlert()
})
}
}
var onTwoFactorAuthCodeNotificationForDiscord = function(r){
// r = user
if(r.details.factor_discord === '1'){
s.discordMsg({
author: {
name: r.lang['2-Factor Authentication'],
icon_url: config.iconURL
},
title: r.lang['Enter this code to proceed'],
description: '**'+s.factorAuth[r.ke][r.uid].key+'** '+r.lang.FactorAuthText1,
fields: [],
timestamp: new Date(),
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},[],r.ke)
}
}
var loadDiscordBotForUser = function(user){
ar=JSON.parse(user.details);
//discordbot
if(!s.group[user.ke].discordBot &&
config.discordBot === true &&
ar.discordbot === '1' &&
ar.discordbot_token !== ''
){
s.group[user.ke].discordBot = new Discord.Client()
s.group[user.ke].discordBot.on('ready', () => {
console.log(`${user.mail} : Discord Bot Logged in as ${s.group[user.ke].discordBot.user.tag}!`)
})
s.group[user.ke].discordBot.login(ar.discordbot_token)
}
}
var unloadDiscordBotForUser = function(user){
if(s.group[user.ke].discordBot && s.group[user.ke].discordBot.destroy){
s.group[user.ke].discordBot.destroy()
delete(s.group[user.ke].discordBot)
}
}
s.loadGroupAppExtender(loadDiscordBotForUser)
s.unloadGroupAppExtender(unloadDiscordBotForUser)
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForDiscord)
s.onEventTrigger(onEventTriggerForDiscord)
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForDiscord)
}catch(err){
console.log(err)
console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.')
s.discordMsg = function(){}
}
}
// mailing with nodemailer
try{
if(config.mail){
if(config.mail.from === undefined){config.mail.from = '"ShinobiCCTV" <no-reply@shinobi.video>'}
s.nodemailer = require('nodemailer').createTransport(config.mail);
}
var onDetectorNoTriggerTimeoutForEmail = function(e){
//e = monitor object
if(config.mail && e.details.detector_notrigger_mail === '1'){
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(err,r){
r = r[0]
var mailOptions = {
from: config.mail.from, // sender address
to: r.mail, // list of receivers
subject: lang.NoMotionEmailText1+' '+e.name+' ('+e.id+')', // Subject line
html: '<i>'+lang.NoMotionEmailText2+' '+e.details.detector_notrigger_timeout+' '+lang.minutes+'.</i>',
}
mailOptions.html+='<div><b>'+lang['Monitor Name']+' </b> : '+e.name+'</div>'
mailOptions.html+='<div><b>'+lang['Monitor ID']+' </b> : '+e.id+'</div>'
s.nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog('detector:notrigger:sendMail',error)
s.tx({f:'error',ff:'detector_notrigger_mail',id:e.id,ke:e.ke,error:error},'GRP_'+e.ke);
return ;
}
s.tx({f:'detector_notrigger_mail',id:e.id,ke:e.ke,info:info},'GRP_'+e.ke);
})
})
}
}
var onTwoFactorAuthCodeNotificationForEmail = function(r){
// r = user object
if(r.details.factor_mail !== '0'){
var mailOptions = {
from: config.mail.from,
to: r.mail,
subject: r.lang['2-Factor Authentication'],
html: r.lang['Enter this code to proceed']+' <b>'+s.factorAuth[r.ke][r.uid].key+'</b>. '+r.lang.FactorAuthText1,
};
s.nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog(r.lang.MailError,error)
return
}
})
}
}
var onFilterEventForEmail = function(x,d){
// x = filter function
// d = filter event object
if(x === 'email'){
if(d.videos && d.videos.length > 0){
d.mailOptions = {
from: config.mail.from, // sender address
to: d.mail, // list of receivers
subject: lang['Filter Matches']+' : '+d.name, // Subject line
html: lang.FilterMatchesText1+' '+d.videos.length+' '+lang.FilterMatchesText2,
};
if(d.execute&&d.execute!==''){
d.mailOptions.html+='<div><b>'+lang.Executed+' :</b> '+d.execute+'</div>'
}
if(d.delete==='1'){
d.mailOptions.html+='<div><b>'+lang.Deleted+' :</b> '+lang.Yes+'</div>'
}
d.mailOptions.html+='<div><b>'+lang.Query+' :</b> '+d.query+'</div>'
d.mailOptions.html+='<div><b>'+lang['Filter ID']+' :</b> '+d.id+'</div>'
s.nodemailer.sendMail(d.mailOptions, (error, info) => {
if (error) {
s.tx({f:'error',ff:'filter_mail',ke:d.ke,error:error},'GRP_'+d.ke);
return ;
}
s.tx({f:'filter_mail',ke:d.ke,info:info},'GRP_'+d.ke);
})
}
}
}
var onEventTriggerBeforeFilterForEmail = function(d,filter){
filter.mail = true
}
var onEventTriggerForEmail = function(d,filter){
if(filter.mail && config.mail && !s.group[d.ke].mon[d.id].detector_mail && d.mon.details.detector_mail === '1'){
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[d.ke,'%"sub"%'],function(err,r){
r=r[0];
var detector_mail_timeout
if(!d.mon.details.detector_mail_timeout||d.mon.details.detector_mail_timeout===''){
detector_mail_timeout = 1000*60*10;
}else{
detector_mail_timeout = parseFloat(d.mon.details.detector_mail_timeout)*1000*60;
}
//lock mailer so you don't get emailed on EVERY trigger event.
s.group[d.ke].mon[d.id].detector_mail=setTimeout(function(){
//unlock so you can mail again.
clearTimeout(s.group[d.ke].mon[d.id].detector_mail);
delete(s.group[d.ke].mon[d.id].detector_mail);
},detector_mail_timeout);
var files = []
var mailOptions = {
from: config.mail.from, // sender address
to: r.mail, // list of receivers
subject: lang.Event+' - '+d.screenshotName, // Subject line
html: '<i>'+lang.EventText1+' '+d.currentTimestamp+'.</i>',
attachments: files
}
var sendMail = function(){
Object.keys(d.details).forEach(function(v,n){
mailOptions.html+='<div><b>'+v+'</b> : '+d.details[v]+'</div>'
})
s.nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog(lang.MailError,error)
return false;
}
})
}
if(d.mon.details.detector_mail_send_video === '1'){
s.mergeDetectorBufferChunks(d,function(mergedFilepath,filename){
s.nodemailer.sendMail({
from: config.mail.from,
to: r.mail,
subject: filename,
html: '',
attachments: [
{
filename: filename,
content: fs.readFileSync(mergedFilepath)
}
]
}, (error, info) => {
if (error) {
s.systemLog(lang.MailError,error)
return false;
}
})
})
}
if(d.screenshotBuffer){
files.push({
filename: d.screenshotName+'.jpg',
content: d.screenshotBuffer
})
sendMail()
}else{
s.getRawSnapshotFromMonitor(d.mon,function(data){
d.screenshotBuffer = data
files.push({
filename: d.screenshotName+'.jpg',
content: data
})
sendMail()
})
}
})
}
}
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForEmail)
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForEmail)
s.onEventTrigger(onEventTriggerForEmail)
s.onFilterEvent(onFilterEventForEmail)
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForEmail)
}catch(err){
console.log(err)
}
}

105
libs/plugins.js Normal file
View file

@ -0,0 +1,105 @@
var socketIOclient = require('socket.io-client');
module.exports = function(s,config,lang){
//function for receiving detector data
s.pluginEventController=function(d){
switch(d.f){
case'trigger':
s.triggerEvent(d)
break;
case's.tx':
s.tx(d.data,d.to)
break;
case's.sqlQuery':
s.sqlQuery(d.query,d.values)
break;
case'log':
s.systemLog('PLUGIN : '+d.plug+' : ',d)
break;
}
}
//multi plugin connections
s.connectedPlugins={}
s.pluginInitiatorSuccess=function(mode,d,cn){
s.systemLog('pluginInitiatorSuccess',d)
if(mode==='client'){
//is in client mode (camera.js is client)
cn.pluginEngine=d.plug
if(!s.connectedPlugins[d.plug]){
s.connectedPlugins[d.plug]={plug:d.plug}
}
s.systemLog('Connected to plugin : Detector - '+d.plug+' - '+d.type)
switch(d.type){
default:case'detector':
s.ocv={started:s.timeObject(),id:cn.id,plug:d.plug,notice:d.notice,isClientPlugin:true};
cn.ocv=1;
s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU')
break;
}
}else{
//is in host mode (camera.js is client)
switch(d.type){
default:case'detector':
s.ocv={started:s.timeObject(),id:"host",plug:d.plug,notice:d.notice,isHostPlugin:true};
break;
}
}
s.connectedPlugins[d.plug].plugged=true
s.tx({f:'readPlugins',ke:d.ke},'CPU')
s.ocvTx({f:'api_key',key:d.plug})
s.api[d.plug]={pluginEngine:d.plug,permissions:{},details:{},ip:'0.0.0.0'};
}
s.pluginInitiatorFail=function(mode,d,cn){
if(s.connectedPlugins[d.plug])s.connectedPlugins[d.plug].plugged=false
if(mode==='client'){
//is in client mode (camera.js is client)
cn.disconnect()
}else{
//is in host mode (camera.js is client)
}
}
if(config.plugins&&config.plugins.length>0){
config.plugins.forEach(function(v){
s.connectedPlugins[v.id]={plug:v.id}
if(v.enabled===false){return}
if(v.mode==='host'){
//is in host mode (camera.js is client)
if(v.https===true){
v.https='https://'
}else{
v.https='http://'
}
if(!v.port){
v.port=80
}
var socket = socketIOclient(v.https+v.host+':'+v.port)
s.connectedPlugins[v.id].tx = function(x){return socket.emit('f',x)}
socket.on('connect', function(cn){
s.systemLog('Connected to plugin (host) : '+v.id)
s.connectedPlugins[v.id].tx({f:'init_plugin_as_host',key:v.key})
});
socket.on('init',function(d){
s.systemLog('Initialize Plugin : Host',d)
if(d.ok===true){
s.pluginInitiatorSuccess("host",d)
}else{
s.pluginInitiatorFail("host",d)
}
});
socket.on('ocv',s.pluginEventController);
socket.on('disconnect', function(){
s.connectedPlugins[v.id].plugged=false
delete(s.api[v.id])
s.systemLog('Plugin Disconnected : '+v.id)
s.connectedPlugins[v.id].reconnector = setInterval(function(){
if(socket.connected===true){
clearInterval(s.connectedPlugins[v.id].reconnector)
}else{
socket.connect()
}
},1000*2)
});
s.connectedPlugins[v.id].ws = socket;
}
})
}
}

33
libs/process.js Normal file
View file

@ -0,0 +1,33 @@
var os = require('os')
module.exports = function(process,__dirname){
process.send = process.send || function () {};
process.on('uncaughtException', function (err) {
console.error('Uncaught Exception occured!');
console.error(err.stack);
});
// [CTRL] + [C] = exit
process.on('SIGINT', function() {
console.log('Shinobi is Exiting...')
process.exit();
});
// s = Shinobi
s = {
//Total Memory
coreCount : os.cpus().length,
//Total Memory
totalmem : os.totalmem(),
//Check Platform
platform : os.platform(),
//JSON stringify short-hand
s : JSON.stringify,
//Pretty Print JSON
prettyPrint : function(obj){return JSON.stringify(obj,null,3)},
//Check if Windows
isWin : (process.platform === 'win32' || process.platform === 'win64'),
//UTC Offset
utcOffset : require('moment')().utcOffset(),
//directory path for this file
mainDirectory : __dirname
}
return s
}

1433
libs/socketio.js Normal file

File diff suppressed because it is too large Load diff

88
libs/sql.js Normal file
View file

@ -0,0 +1,88 @@
module.exports = function(s,config){
//sql/database connection with knex
s.databaseOptions = {
client: config.databaseType,
connection: config.db,
}
if(s.databaseOptions.client.indexOf('sqlite')>-1){
s.databaseOptions.client = 'sqlite3';
s.databaseOptions.useNullAsDefault = true;
}
if(s.databaseOptions.client === 'sqlite3' && s.databaseOptions.connection.filename === undefined){
s.databaseOptions.connection.filename = s.mainDirectory+"/shinobi.sqlite"
}
s.mergeQueryValues = function(query,values){
if(!values){values=[]}
var valuesNotFunction = true;
if(typeof values === 'function'){
var values = [];
valuesNotFunction = false;
}
if(values&&valuesNotFunction){
var splitQuery = query.split('?')
var newQuery = ''
splitQuery.forEach(function(v,n){
newQuery += v
var value = values[n]
if(value){
if(isNaN(value) || value instanceof Date){
newQuery += "'"+value+"'"
}else{
newQuery += value
}
}
})
}else{
newQuery = query
}
return newQuery
}
s.stringToSqlTime = function(value){
newValue = new Date(value.replace('T',' '))
return newValue
}
s.sqlQuery = function(query,values,onMoveOn,hideLog){
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(!onMoveOn){onMoveOn=function(){}}
var mergedQuery = s.mergeQueryValues(query,values)
s.debugLog('s.sqlQuery QUERY',mergedQuery)
return s.databaseEngine
.raw(query,values)
.asCallback(function(err,r){
if(err && !hideLog){
console.log('s.sqlQuery QUERY ERRORED',query)
console.log('s.sqlQuery ERROR',err)
}
if(onMoveOn && typeof onMoveOn === 'function'){
switch(s.databaseOptions.client){
case'sqlite3':
if(!r)r=[]
break;
default:
if(r)r=r[0]
break;
}
onMoveOn(err,r)
}
})
}
s.preQueries = function(){
//add Cloud Videos table, will remove in future
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Videos` (`mid` varchar(50) NOT NULL,`ke` varchar(50) DEFAULT NULL,`href` text NOT NULL,`size` float DEFAULT NULL,`time` timestamp NULL DEFAULT NULL,`end` timestamp NULL DEFAULT NULL,`status` int(1) DEFAULT \'0\' COMMENT \'0:Complete,1:Read,2:Archive\',`details` text) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
// if(err)console.log(err)
},true)
//create Files table
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Files` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`name` tinytext NOT NULL,`size` float NOT NULL DEFAULT \'0\',`details` text NOT NULL,`status` int(1) NOT NULL DEFAULT \'0\') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
// if(err)console.log(err)
//add time column to Files table
s.sqlQuery('ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;',[],function(err){
// if(err)console.log(err)
},true)
},true)
delete(s.preQueries)
}
}

165
libs/startup.js Normal file
View file

@ -0,0 +1,165 @@
var fs = require('fs');
var moment = require('moment');
var crypto = require('crypto');
var exec = require('child_process').exec;
var execSync = require('child_process').execSync;
module.exports = function(s,config,lang,io){
console.log('FFmpeg version : '+s.ffmpegVersion)
console.log('Node.js version : '+execSync("node -v"))
s.processReady = function(){
s.systemLog(lang.startUpText5)
process.send('ready')
}
var loadedAccounts = []
var loadMonitors = function(callback){
s.systemLog(lang.startUpText4)
//preliminary monitor start
s.sqlQuery('SELECT * FROM Monitors', function(err,monitors) {
if(err){s.systemLog(err)}
if(monitors && monitors[0]){
var loadCompleted = 0
var orphanedVideosForMonitors = {}
var loadMonitor = function(monitor){
if(!orphanedVideosForMonitors[monitor.ke])orphanedVideosForMonitors[monitor.ke] = {}
if(!orphanedVideosForMonitors[monitor.ke][monitor.mid])orphanedVideosForMonitors[monitor.ke][monitor.mid] = 0
s.initiateMonitorObject(monitor)
s.orphanedVideoCheck(monitor,2,function(orphanedFilesCount){
if(orphanedFilesCount){
orphanedVideosForMonitors[monitor.ke][monitor.mid] += orphanedFilesCount
}
s.group[monitor.ke].mon_conf[monitor.mid] = monitor
var monObj = Object.assign(monitor,{id : monitor.mid})
s.camera(monitor.mode,monObj)
++loadCompleted
if(monitors[loadCompleted]){
loadMonitor(monitors[loadCompleted])
}else{
s.systemLog(lang.startUpText6+' : '+s.s(orphanedVideosForMonitors))
callback()
}
})
}
loadMonitor(monitors[loadCompleted])
}else{
callback()
}
})
}
var loadDiskUseForUser = function(user,callback){
s.systemLog(user.mail+' : '+lang.startUpText0)
var userDetails = JSON.parse(user.details)
user.size = 0
user.limit = userDetails.size
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=?',[user.ke,0],function(err,videos){
if(videos && videos[0]){
videos.forEach(function(video){
user.size += video.size
})
}
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+videos.length,user.size)
callback()
})
}
var loadCloudDiskUseForUser = function(user,callback){
var userDetails = JSON.parse(user.details)
user.cloudDiskUse = {}
user.size = 0
user.limit = userDetails.size
s.cloudDisksLoaded.forEach(function(storageType){
user.cloudDiskUse[storageType] = {
usedSpace : 0,
firstCount : 0
}
if(s.cloudDiskUseStartupExtensions[storageType])s.cloudDiskUseStartupExtensions[storageType](user,userDetails)
})
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE ke=? AND status!=?',[user.ke,0],function(err,videos){
if(videos && videos[0]){
videos.forEach(function(video){
var storageType = JSON.parse(video.details).type
if(!storageType)storageType = 's3'
user.cloudDiskUse[storageType].usedSpace += (video.size /1000000)
++user.cloudDiskUse[storageType].firstCount
})
s.cloudDisksLoaded.forEach(function(storageType){
var firstCount = user.cloudDiskUse[storageType].firstCount
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+firstCount,storageType,user.cloudDiskUse[storageType].usedSpace)
delete(user.cloudDiskUse[storageType].firstCount)
})
}
s.group[user.ke].cloudDiskUse = user.cloudDiskUse
callback()
})
}
var loadAdminUsers = function(callback){
//get current disk used for each isolated account (admin user) on startup
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,users){
if(users && users[0]){
var loadLocalDiskUse = function(callback){
var count = users.length
var countFinished = 0
users.forEach(function(user){
loadedAccounts.push(user.ke)
loadDiskUseForUser(user,function(){
s.loadGroup(user)
s.loadGroupApps(user)
++countFinished
if(countFinished === count){
callback()
}
})
})
}
var loadCloudDiskUse = function(callback){
var count = users.length
var countFinished = 0
users.forEach(function(user){
loadCloudDiskUseForUser(user,function(){
++countFinished
if(countFinished === count){
callback()
}
})
})
}
loadLocalDiskUse(function(){
loadCloudDiskUse(function(){
callback()
})
})
}else{
s.processReady()
}
})
}
//check disk space every 20 minutes
if(config.autoDropCache===true){
setInterval(function(){
exec('echo 3 > /proc/sys/vm/drop_caches',{detached: true})
},60000*20)
}
//master node - startup functions
setInterval(function(){
s.cpuUsage(function(cpu){
s.ramUsage(function(ram){
s.tx({f:'os',cpu:cpu,ram:ram},'CPU');
})
})
},10000)
//run prerequsite queries, load users and monitors
if(config.childNodes.mode !== 'child'){
//sql/database connection with knex
s.databaseEngine = require('knex')(s.databaseOptions)
//run prerequsite queries
s.preQueries()
setTimeout(function(){
//load administrators (groups)
loadAdminUsers(function(){
//load monitors (for groups)
loadMonitors(function(){
s.processReady()
})
})
},1500)
}
}

235
libs/user.js Normal file
View file

@ -0,0 +1,235 @@
var fs = require('fs');
var events = require('events');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
module.exports = function(s,config){
s.purgeDiskForGroup = function(e){
if(config.cron.deleteOverMax === true){
s.group[e.ke].sizePurgeQueue.push(1)
if(s.group[e.ke].sizePurging !== true){
s.group[e.ke].sizePurging = true
var finish = function(){
//remove value just used from queue
s.group[e.ke].sizePurgeQueue.shift()
//do next one
if(s.group[e.ke].sizePurgeQueue.length > 0){
checkQueue()
}else{
s.group[e.ke].sizePurging=false
s.sendDiskUsedAmountToClients(e)
}
}
var checkQueue = function(){
//get first in queue
var currentPurge = s.group[e.ke].sizePurgeQueue[0]
var deleteVideos = function(){
//run purge command
if(s.group[e.ke].usedSpace > (s.group[e.ke].sizeLimit*config.cron.deleteOverMaxOffset)){
s.sqlQuery('SELECT * FROM Videos WHERE status != 0 AND details NOT LIKE \'%"archived":"1"%\' AND ke=? ORDER BY `time` ASC LIMIT 3',[e.ke],function(err,videos){
var videosToDelete = []
var queryValues = [e.ke]
var completedCheck = 0
if(videos){
videos.forEach(function(video){
video.dir = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
videosToDelete.push('(mid=? AND `time`=?)')
queryValues.push(video.mid)
queryValues.push(video.time)
fs.chmod(video.dir,0o777,function(err){
fs.unlink(video.dir,function(err){
++completedCheck
if(err){
fs.stat(video.dir,function(err){
if(!err){
s.file('delete',video.dir)
}
})
}
if(videosToDelete.length === completedCheck){
videosToDelete = videosToDelete.join(' OR ')
s.sqlQuery('DELETE FROM Videos WHERE ke =? AND ('+videosToDelete+')',queryValues,function(){
deleteVideos()
})
}
})
})
s.setDiskUsedForGroup(e,-(video.size/1000000))
s.tx({
f: 'video_delete',
ff: 'over_max',
filename: s.formattedTime(video.time)+'.'+video.ext,
mid: video.mid,
ke: video.ke,
time: video.time,
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
},'GRP_'+e.ke)
})
}else{
console.log(err)
}
if(videosToDelete.length === 0){
finish()
}
})
}else{
finish()
}
}
deleteVideos()
}
checkQueue()
}
}else{
s.sendDiskUsedAmountToClients(e)
}
}
s.setDiskUsedForGroup = function(e,bytes){
//`bytes` will be used as the value to add or substract
if(s.group[e.ke] && s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter.emit('set',bytes)
}
}
s.purgeCloudDiskForGroup = function(e,storageType){
if(s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter.emit('purgeCloud',storageType)
}
}
s.setCloudDiskUsedForGroup = function(e,usage){
//`bytes` will be used as the value to add or substract
if(s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter.emit('setCloud',usage)
}
}
s.sendDiskUsedAmountToClients = function(e){
//send the amount used disk space to connected users
if(s.group[e.ke]&&s.group[e.ke].init){
s.tx({f:'diskUsed',size:s.group[e.ke].usedSpace,limit:s.group[e.ke].sizeLimit},'GRP_'+e.ke);
}
}
//user log
s.userLog = function(e,x){
if(e.id && !e.mid)e.mid = e.id
if(!x||!e.mid){return}
if((e.details&&e.details.sqllog==='1')||e.mid.indexOf('$')>-1){
s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',[e.ke,e.mid,s.s(x)]);
}
s.tx({f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRPLOG_'+e.ke);
}
s.loadGroup = function(e){
if(!s.group[e.ke]){
s.group[e.ke]={}
}
if(!s.group[e.ke].init){
s.group[e.ke].init={}
}
if(!s.group[e.ke].fileBin){s.group[e.ke].fileBin={}};
if(!s.group[e.ke].users){s.group[e.ke].users={}}
if(!s.group[e.ke].dashcamUsers){s.group[e.ke].dashcamUsers={}}
if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]}
if(!e.limit||e.limit===''){e.limit=10000}else{e.limit=parseFloat(e.limit)}
//save global space limit for group key (mb)
s.group[e.ke].sizeLimit=e.limit;
//save global used space as megabyte value
s.group[e.ke].usedSpace=e.size/1000000;
//emit the changes to connected users
s.sendDiskUsedAmountToClients(e)
}
s.loadGroupApps = function(e){
// e = user
if(!s.group[e.ke].init){
s.group[e.ke].init={};
}
s.sqlQuery('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(ar,r){
if(r&&r[0]){
r=r[0];
ar=JSON.parse(r.details);
//load extenders
s.loadGroupAppExtensions.forEach(function(extender){
extender(r)
})
//disk Used Emitter
if(!s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter = new events.EventEmitter()
s.group[e.ke].diskUsedEmitter.on('setCloud',function(currentChange){
var amount = currentChange.amount
var storageType = currentChange.storageType
var cloudDisk = s.group[e.ke].cloudDiskUse[storageType]
//validate current values
if(!cloudDisk.usedSpace){
cloudDisk.usedSpace = 0
}else{
cloudDisk.usedSpace = parseFloat(cloudDisk.usedSpace)
}
if(cloudDisk.usedSpace < 0 || isNaN(cloudDisk.usedSpace)){
cloudDisk.usedSpace = 0
}
//change global size value
cloudDisk.usedSpace = cloudDisk.usedSpace + amount
})
s.group[e.ke].diskUsedEmitter.on('purgeCloud',function(storageType){
if(config.cron.deleteOverMax === true){
//set queue processor
var finish=function(){
// s.sendDiskUsedAmountToClients(e)
}
var deleteVideos = function(){
//run purge command
var cloudDisk = s.group[e.ke].cloudDiskUse[storageType]
if(cloudDisk.sizeLimitCheck && cloudDisk.usedSpace > (cloudDisk.sizeLimit*config.cron.deleteOverMaxOffset)){
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE status != 0 AND ke=? AND details LIKE \'%"type":"'+storageType+'"%\' ORDER BY `time` ASC LIMIT 2',[e.ke],function(err,videos){
var videosToDelete = []
var queryValues = [e.ke]
if(!videos)return console.log(err)
videos.forEach(function(video){
video.dir = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
videosToDelete.push('(mid=? AND `time`=?)')
queryValues.push(video.mid)
queryValues.push(video.time)
s.setCloudDiskUsedForGroup(e,{
amount : -(video.size/1000000),
storageType : storageType
})
s.deleteVideoFromCloudExtensionsRunner(e,storageType,video)
})
if(videosToDelete.length > 0){
videosToDelete = videosToDelete.join(' OR ')
s.sqlQuery('DELETE FROM `Cloud Videos` WHERE ke =? AND ('+videosToDelete+')',queryValues,function(){
deleteVideos()
})
}else{
finish()
}
})
}else{
finish()
}
}
deleteVideos()
}else{
// s.sendDiskUsedAmountToClients(e)
}
})
//s.setDiskUsedForGroup
s.group[e.ke].diskUsedEmitter.on('set',function(currentChange){
//validate current values
if(!s.group[e.ke].usedSpace){
s.group[e.ke].usedSpace=0
}else{
s.group[e.ke].usedSpace=parseFloat(s.group[e.ke].usedSpace)
}
if(s.group[e.ke].usedSpace<0||isNaN(s.group[e.ke].usedSpace)){
s.group[e.ke].usedSpace=0
}
//change global size value
s.group[e.ke].usedSpace += currentChange
//remove value just used from queue
s.sendDiskUsedAmountToClients(e)
})
}
Object.keys(ar).forEach(function(v){
s.group[e.ke].init[v]=ar[v]
})
}
});
}
}

329
libs/videos.js Normal file
View file

@ -0,0 +1,329 @@
var fs = require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
module.exports = function(s,config,lang){
/**
* Gets the video directory of the supplied video or monitor database row.
* @constructor
* @param {object} e - Monitor object or Video object. Object is a database row.
*/
s.getVideoDirectory = function(e){
if(e.mid&&!e.id){e.id=e.mid};
s.checkDetails(e)
if(e.details&&e.details.dir&&e.details.dir!==''){
return s.checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'/'
}else{
return s.dir.videos+e.ke+'/'+e.id+'/';
}
}
/**
* Creates available API based URLs for streaming
* @constructor
* @param {object} videos - Array of video objects
* @param {object} options - Contains middle parameter of URL and auth key
* @param [options.auth] {string} - API Key
* @param [options.videoParam] {string} - currently only `videos` and `cloudVideos` available.
*/
s.buildVideoLinks = function(videos,options){
videos.forEach(function(v){
var details = JSON.parse(v.details)
var queryString = []
if(details.isUTC === true){
queryString.push('isUTC=true')
}else{
v.time = s.utcToLocal(v.time)
v.end = s.utcToLocal(v.end)
}
if(queryString.length > 0){
queryString = '?'+queryString.join('&')
}else{
queryString = ''
}
if(!v.ext && v.href){
v.ext = v.href.split('.')
v.ext = v.ext[v.ext.length - 1]
}
v.filename = s.formattedTime(v.time)+'.'+v.ext;
if(!options.videoParam)options.videoParam = 'videos'
var href = '/'+options.auth+'/'+options.videoParam+'/'+v.ke+'/'+v.mid+'/'+v.filename;
v.actionUrl = href
v.links = {
deleteVideo : href+'/delete' + queryString,
changeToUnread : href+'/status/1' + queryString,
changeToRead : href+'/status/2' + queryString
}
if(!v.href || options.hideRemote === true)v.href = href + queryString
v.details = details
})
}
//extender for "s.insertCompletedVideo"
s.insertCompletedVideoExtensions = []
s.insertCompletedVideoExtender = function(callback){
s.insertCompletedVideoExtensions.push(callback)
}
s.insertDatabaseRow = function(e,k,callback){
s.checkDetails(e)
//save database row
k.details = {}
if(e.details&&e.details.dir&&e.details.dir!==''){
k.details.dir = e.details.dir
}
if(config.useUTC === true)k.details.isUTC = config.useUTC;
var save = [
e.mid,
e.ke,
k.startTime,
e.ext,
1,
s.s(k.details),
k.filesize,
k.endTime,
]
s.sqlQuery('INSERT INTO Videos (mid,ke,time,ext,status,details,size,end) VALUES (?,?,?,?,?,?,?,?)',save,function(err){
if(callback)callback(err)
fs.chmod(k.dir+k.file,0o777,function(err){
})
})
}
//on video completion
s.insertCompletedVideo = function(e,k,callback){
//e = monitor object
//k = video insertion object
s.checkDetails(e)
if(!k)k={};
e.dir = s.getVideoDirectory(e)
k.dir = e.dir.toString()
if(s.group[e.ke].mon[e.id].childNode){
s.cx({f:'insertCompleted',d:s.group[e.ke].mon_conf[e.id],k:k},s.group[e.ke].mon[e.id].childNodeId);
}else{
//get file directory
k.fileExists = fs.existsSync(k.dir+k.file)
if(k.fileExists!==true){
k.dir = s.dir.videos+'/'+e.ke+'/'+e.id+'/'
k.fileExists = fs.existsSync(k.dir+k.file)
if(k.fileExists !== true){
s.dir.addStorage.forEach(function(v){
if(k.fileExists !== true){
k.dir = s.checkCorrectPathEnding(v.path)+e.ke+'/'+e.id+'/'
k.fileExists = fs.existsSync(k.dir+k.file)
}
})
}
}
if(k.fileExists===true){
//close video row
k.stat = fs.statSync(k.dir+k.file)
k.filesize = k.stat.size
k.filesizeMB = parseFloat((k.filesize/1000000).toFixed(2))
k.startTime = new Date(s.nameToTime(k.file))
k.endTime = new Date(k.stat.mtime)
if(config.useUTC === true){
fs.rename(k.dir+k.file, k.dir+s.formattedTime(k.startTime)+'.'+e.ext, (err) => {
if (err) return console.error(err);
});
k.filename = s.formattedTime(k.startTime)+'.'+e.ext
}else{
k.filename = k.file
}
if(!e.ext){e.ext = k.filename.split('.')[1]}
//send event for completed recording
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
fs.createReadStream(k.dir+k.filename,{ highWaterMark: 500 })
.on('data',function(data){
s.cx({
f:'created_file_chunk',
mid:e.mid,
ke:e.ke,
chunk:data,
filename:k.filename,
d:s.cleanMonitorObject(e),
filesize:e.filesize,
time:s.timeObject(k.startTime).format(),
end:s.timeObject(k.endTime).format()
})
})
.on('close',function(){
clearTimeout(s.group[e.ke].mon[e.id].recordingChecker)
clearTimeout(s.group[e.ke].mon[e.id].streamChecker)
s.cx({
f:'created_file',
mid:e.id,
ke:e.ke,
filename:k.filename,
d:s.cleanMonitorObject(e),
filesize:k.filesize,
time:s.timeObject(k.startTime).format(),
end:s.timeObject(k.endTime).format()
})
})
}else{
var href = '/videos/'+e.ke+'/'+e.mid+'/'+k.filename
if(config.useUTC === true)href += '?isUTC=true';
s.txWithSubPermissions({
f:'video_build_success',
hrefNoAuth:href,
filename:k.filename,
mid:e.mid,
ke:e.ke,
time:k.startTime,
size:k.filesize,
end:k.endTime
},'GRP_'+e.ke,'video_view')
//purge over max
s.purgeDiskForGroup(e)
//send new diskUsage values
s.setDiskUsedForGroup(e,k.filesizeMB)
s.insertDatabaseRow(e,k,callback)
s.insertCompletedVideoExtensions.forEach(function(extender){
extender(e,k)
})
}
}
}
}
s.deleteVideo = function(e){
//e = video object
s.checkDetails(e)
e.dir = s.getVideoDirectory(e)
if(!e.filename && e.time){
e.filename = s.formattedTime(e.time)
}
var filename,
time
if(e.filename.indexOf('.')>-1){
filename = e.filename
}else{
filename = e.filename+'.'+e.ext
}
if(e.filename && !e.time){
time = s.nameToTime(filename)
}else{
time = e.time
}
time = new Date(time)
var queryValues = [e.id,e.ke,time];
s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',queryValues,function(err,r){
if(r && r[0]){
r = r[0]
fs.chmod(e.dir+filename,0o777,function(err){
s.tx({
f: 'video_delete',
filename: filename,
mid: e.id,
ke: e.ke,
time: s.nameToTime(filename),
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
},'GRP_'+e.ke);
s.setDiskUsedForGroup(e,-(r.size / 1000000))
s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',queryValues,function(err){
if(err){
s.systemLog(lang['File Delete Error'] + ' : '+e.ke+' : '+' : '+e.id,err)
}
})
fs.unlink(e.dir+filename,function(err){
fs.stat(e.dir+filename,function(err){
if(!err){
s.file('delete',e.dir+filename)
}
})
})
})
var videoSnap = s.dir.videoSnaps + e.ke + '/' + e.mid + '/' + filename.split('.')[0] + '.jpg'
fs.chmod(videoSnap,0o777,function(err){
if(!err){
fs.unlink(videoSnap,function(err){})
}
})
}else{
console.log(lang['Database row does not exist'],queryValues)
}
})
}
s.deleteVideoFromCloudExtensions = {}
s.deleteVideoFromCloudExtensionsRunner = function(e,storageType,video){
// e = user
if(!storageType){
var videoDetails = JSON.parse(r.details)
videoDetails.type = videoDetails.type || 's3'
}
if(s.deleteVideoFromCloudExtensions[storageType]){
s.deleteVideoFromCloudExtensions[storageType](e,video,function(){
s.tx({
f: 'video_delete_cloud',
mid: e.mid,
ke: e.ke,
time: e.time,
end: e.end
},'GRP_'+e.ke);
})
}
}
s.deleteVideoFromCloud = function(e){
// e = video object
s.checkDetails(e)
var videoSelector = [e.id,e.ke,new Date(e.time)]
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE `mid`=? AND `ke`=? AND `time`=?',videoSelector,function(err,r){
if(r&&r[0]){
r = r[0]
s.sqlQuery('DELETE FROM `Cloud Videos` WHERE `mid`=? AND `ke`=? AND `time`=?',videoSelector,function(){
s.deleteVideoFromCloudExtensionsRunner(e,r)
})
}else{
// console.log('Delete Failed',e)
// console.error(err)
}
})
}
s.orphanedVideoCheck = function(monitor,checkMax,callback,forceCheck){
var finish = function(orphanedFilesCount){
if(callback)callback(orphanedFilesCount)
}
if(forceCheck === true || config.insertOrphans === true){
if(!checkMax){
checkMax = config.orphanedVideoCheckMax
}
var videosDirectory = s.getVideoDirectory(monitor)// + s.formattedTime(video.time) + '.' + video.ext
fs.readdir(videosDirectory,function(err,files){
if(files && files.length > 0){
var fiveRecentFiles = files.slice(files.length - checkMax,files.length)
var completedFile = 0
var orphanedFilesCount = 0
var fileComplete = function(){
++completedFile
if(fiveRecentFiles.length === completedFile){
finish(orphanedFilesCount)
}
}
fiveRecentFiles.forEach(function(filename){
if(/T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(filename)){
var queryValues = [
monitor.ke,
monitor.mid,
s.nameToTime(filename)
]
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND time=? LIMIT 1',queryValues,function(err,rows){
if(!err && (!rows || !rows[0])){
++orphanedFilesCount
var video = rows[0]
s.insertCompletedVideo(monitor,{
file : filename
},function(){
fileComplete()
})
}else{
fileComplete()
}
})
}
})
}else{
finish()
}
})
}else{
finish()
}
}
}

35
libs/webServer.js Normal file
View file

@ -0,0 +1,35 @@
var fs = require('fs');
var http = require('http');
var https = require('https');
var express = require('express');
var app = express()
module.exports = function(s,config,lang,io){
var server = http.createServer(app);
//SSL options
if(config.ssl&&config.ssl.key&&config.ssl.cert){
config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8')
config.ssl.cert=fs.readFileSync(s.checkRelativePath(config.ssl.cert),'utf8')
if(config.ssl.port === undefined){
config.ssl.port=443
}
if(config.ssl.bindip === undefined){
config.ssl.bindip=config.bindip
}
if(config.ssl.ca&&config.ssl.ca instanceof Array){
config.ssl.ca.forEach(function(v,n){
config.ssl.ca[n]=fs.readFileSync(s.checkRelativePath(v),'utf8')
})
}
var serverHTTPS = https.createServer(config.ssl,app);
serverHTTPS.listen(config.ssl.port,config.bindip,function(){
console.log('SSL '+lang.Shinobi+' : SSL Web Server Listening on '+config.ssl.port);
});
io.attach(serverHTTPS);
}
//start HTTP
server.listen(config.port,config.bindip,function(){
console.log(lang.Shinobi+' : Web Server Listening on '+config.port);
});
io.attach(server);
return app
}

428
libs/webServerAdminPaths.js Normal file
View file

@ -0,0 +1,428 @@
var fs = require('fs');
var os = require('os');
var moment = require('moment')
var request = require('request')
var jsonfile = require("jsonfile")
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
module.exports = function(s,config,lang,app){
var closeResponse = function(res,endData){
res.setHeader('Content-Type', 'application/json')
res.end(s.prettyPrint(endData))
}
/**
* API : Administrator : Edit Sub-Account (Account to share cameras with)
*/
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/edit', function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
closeResponse(res,endData)
return
}
var form = s.getPostData(req)
var uid = s.getPostData(req,'uid',false)
var mail = s.getPostData(req,'mail',false)
if(form){
var keys = Object.keys(form)
var condition = []
var value = []
keys.forEach(function(v){
condition.push(v+'=?')
if(form[v] instanceof Object)form[v] = JSON.stringify(form[v])
value.push(form[v])
})
value = value.concat([req.params.ke,uid])
s.sqlQuery("UPDATE Users SET "+condition.join(',')+" WHERE ke=? AND uid=?",value)
s.tx({
f: 'edit_sub_account',
ke: req.params.ke,
uid: uid,
mail: mail,
form: form
},'ADM_'+req.params.ke)
endData.ok = true
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,uid],function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
delete(s.api[row.code])
})
}
})
}else{
endData.msg = lang.postDataBroken
}
closeResponse(res,endData)
},res,req)
})
/**
* API : Administrator : Delete Sub-Account (Account to share cameras with)
*/
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/delete', function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
closeResponse(res,endData)
return
}
var uid = s.getPostData(req,'uid',false)
var mail = s.getPostData(req,'mail',false)
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[uid,req.params.ke,mail])
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,uid],function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
delete(s.api[row.code])
})
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[uid,req.params.ke])
}
})
s.tx({
f: 'delete_sub_account',
ke: req.params.ke,
uid: uid,
mail: mail
},'ADM_'+req.params.ke)
endData.ok = true
closeResponse(res,endData)
},res,req)
})
/**
* API : Administrator : Add Sub-Account (Account to share cameras with)
*/
app.post([
config.webPaths.adminApiPrefix+':auth/accounts/:ke/register',
//these two routes are for backwards compatibility
config.webPaths.adminApiPrefix+':auth/register/:ke/:uid',
config.webPaths.apiPrefix+':auth/register/:ke/:uid'
],function (req,res){
endData = {
ok : false
}
res.setHeader('Content-Type', 'application/json');
s.auth(req.params,function(user){
if(user.details.sub){
endData.msg = user.lang['Not an Administrator Account']
closeResponse(res,endData)
return
}
var form = s.getPostData(req)
if(form.mail !== '' && form.pass !== ''){
if(form.pass === form.password_again || form.pass === form.pass_again){
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[form.mail],function(err,r) {
if(r&&r[0]){
//found one exist
endData.msg = 'Email address is in use.'
}else{
//create new
endData.msg = 'New Account Created'
endData.ok = true
var newId = s.gid()
var details = s.s({
sub: "1",
allmonitors: "1"
})
s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[req.params.ke,newId,form.mail,s.createHash(form.pass),details])
s.tx({
f: 'add_sub_account',
details: details,
ke: req.params.ke,
uid: newId,
mail: form.mail
},'ADM_'+req.params.ke)
}
res.end(s.prettyPrint(endData))
})
}else{
endData.msg = user.lang["Passwords Don't Match"]
}
}else{
endData.msg = user.lang['Fields cannot be empty']
}
if(endData.msg){
res.end(s.prettyPrint(endData))
}
},res,req)
})
/**
* API : Administrator : Monitor : Add, Edit, and Delete
*/
app.all([
config.webPaths.apiPrefix+':auth/configureMonitor/:ke/:id',
config.webPaths.apiPrefix+':auth/configureMonitor/:ke/:id/:f',
config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id',
config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id/:f'
], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
if(req.params.f !== 'delete'){
var form = s.getPostData(req)
if(!form){
req.ret.msg = user.lang.monitorEditText1;
res.end(s.prettyPrint(req.ret))
return
}
if(!user.details.sub ||
user.details.allmonitors === '1' ||
hasRestrictions && user.details.monitor_edit.indexOf(form.mid) >- 1 ||
hasRestrictions && user.details.monitor_create === '1'){
if(form&&form.mid&&form.name){
req.set=[],req.ar=[];
form.mid=req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,'');
try{
JSON.parse(form.details)
}catch(er){
if(!form.details||!form.details.stream_type){
req.ret.msg=user.lang.monitorEditText2;
res.end(s.prettyPrint(req.ret))
return
}else{
form.details=JSON.stringify(form.details)
}
}
form.ke=req.params.ke
req.logObject={details:JSON.parse(form.details),ke:req.params.ke,mid:req.params.id}
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[form.ke,form.mid],function(er,r){
req.tx={f:'monitor_edit',mid:form.mid,ke:form.ke,mon:form};
if(r&&r[0]){
req.tx.new=false;
Object.keys(form).forEach(function(v){
if(form[v]&&form[v]!==''){
req.set.push(v+'=?'),req.ar.push(form[v]);
}
})
req.set=req.set.join(',');
req.ar.push(form.ke),req.ar.push(form.mid);
s.userLog(form,{type:'Monitor Updated',msg:'by user : '+user.uid});
req.ret.msg=user.lang['Monitor Updated by user']+' : '+user.uid;
s.sqlQuery('UPDATE Monitors SET '+req.set+' WHERE ke=? AND mid=?',req.ar)
req.finish=1;
}else{
if(!s.group[form.ke].init.max_camera||s.group[form.ke].init.max_camera==''||Object.keys(s.group[form.ke].mon).length <= parseInt(s.group[form.ke].init.max_camera)){
req.tx.new=true;
req.st=[];
Object.keys(form).forEach(function(v){
if(form[v]&&form[v]!==''){
req.set.push(v),req.st.push('?'),req.ar.push(form[v]);
}
})
// req.set.push('ke'),req.st.push('?'),req.ar.push(form.ke);
req.set=req.set.join(','),req.st=req.st.join(',');
s.userLog(form,{type:'Monitor Added',msg:'by user : '+user.uid});
req.ret.msg=user.lang['Monitor Added by user']+' : '+user.uid;
s.sqlQuery('INSERT INTO Monitors ('+req.set+') VALUES ('+req.st+')',req.ar)
req.finish=1;
}else{
req.tx.f='monitor_edit_failed';
req.tx.ff='max_reached';
req.ret.msg=user.lang.monitorEditFailedMaxReached;
}
}
if(req.finish===1){
form.details=JSON.parse(form.details)
req.ret.ok=true;
s.initiateMonitorObject({mid:form.mid,ke:form.ke});
s.group[form.ke].mon_conf[form.mid]=s.cleanMonitorObject(form);
if(form.mode==='stop'){
s.camera('stop',form);
}else{
s.camera('stop',form);setTimeout(function(){s.camera(form.mode,form);},5000)
};
s.tx(req.tx,'STR_'+form.ke);
};
s.tx(req.tx,'GRP_'+form.ke);
res.end(s.prettyPrint(req.ret))
})
}else{
req.ret.msg=user.lang.monitorEditText1;
res.end(s.prettyPrint(req.ret))
}
}else{
req.ret.msg=user.lang['Not Permitted'];
res.end(s.prettyPrint(req.ret))
}
}else{
if(!user.details.sub || user.details.allmonitors === '1' || user.details.monitor_edit.indexOf(req.params.id) > -1 || hasRestrictions && user.details.monitor_create === '1'){
s.userLog(s.group[req.params.ke].mon_conf[req.params.id],{type:'Monitor Deleted',msg:'by user : '+user.uid});
req.params.delete=1;s.camera('stop',req.params);
s.tx({f:'monitor_delete',uid:user.uid,mid:req.params.id,ke:req.params.ke},'GRP_'+req.params.ke);
s.sqlQuery('DELETE FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
// s.sqlQuery('DELETE FROM Files WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
if(req.query.deleteFiles === 'true'){
//videos
s.dir.addStorage.forEach(function(v,n){
var videosDir = v.path+req.params.ke+'/'+req.params.id+'/'
fs.stat(videosDir,function(err,stat){
if(!err){
s.file('deleteFolder',videosDir)
}
})
})
var videosDir = s.dir.videos+req.params.ke+'/'+req.params.id+'/'
fs.stat(videosDir,function(err,stat){
if(!err){
s.file('deleteFolder',videosDir)
}
})
//fileBin
var binDir = s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'
fs.stat(binDir,function(err,stat){
if(!err){
s.file('deleteFolder',binDir)
}
})
}
req.ret.ok=true;
req.ret.msg='Monitor Deleted by user : '+user.uid
res.end(s.prettyPrint(req.ret))
}else{
req.ret.msg=user.lang['Not Permitted'];
res.end(s.prettyPrint(req.ret))
}
}
},res,req)
})
/**
* API : Add API Key, binded to the user who created it
*/
app.all([
config.webPaths.adminApiPrefix+':auth/api/:ke/add',
config.webPaths.apiPrefix+':auth/api/:ke/add',
],function (req,res){
var endData = {ok:false}
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var endData = {
ok : false
}
var form = s.getPostData(req)
if(form){
var insert = {
ke : req.params.ke,
uid : user.uid,
code : s.gid(30),
ip : form.ip,
details : s.stringJSON(form.details)
}
var escapes = []
Object.keys(insert).forEach(function(column){
escapes.push('?')
});
s.sqlQuery('INSERT INTO API ('+Object.keys(insert).join(',')+') VALUES ('+escapes.join(',')+')',Object.values(insert),function(err,r){
insert.time = s.formattedTime(new Date,'YYYY-DD-MM HH:mm:ss');
if(!err){
s.tx({
f: 'api_key_added',
uid: user.uid,
form: insert
},'GRP_' + req.params.ke)
endData.ok = true
}
closeResponse(res,endData)
})
}else{
endData.msg = lang.postDataBroken
closeResponse(res,endData)
}
},res,req)
})
/**
* API : Delete API Key
*/
app.all([
config.webPaths.adminApiPrefix+':auth/api/:ke/delete',
config.webPaths.apiPrefix+':auth/api/:ke/delete',
],function (req,res){
var endData = {ok:false}
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var endData = {
ok : false
}
var form = s.getPostData(req)
if(form){
if(!form.code){
s.tx({
f:'form_incomplete',
uid: user.uid,
form:'APIs'
},'GRP_' + req.params.ke)
endData.msg = lang.postDataBroken
closeResponse(res,endData)
return
}
var row = {
ke : req.params.ke,
uid : user.uid,
code : form.code
}
var where = []
Object.keys(row).forEach(function(column){
where.push(column+'=?')
})
s.sqlQuery('DELETE FROM API WHERE '+where.join(' AND '),Object.values(row),function(err,r){
if(!err){
s.tx({
f: 'api_key_deleted',
uid: user.uid,
form: row
},'GRP_' + req.params.ke)
endData.ok = true
delete(s.api[row.code])
}
closeResponse(res,endData)
})
}else{
endData.msg = lang.postDataBroken
closeResponse(res,endData)
}
},res,req)
})
/**
* API : List API Keys for Authenticated user
*/
app.get([
config.webPaths.adminApiPrefix+':auth/api/:ke/list',
config.webPaths.apiPrefix+':auth/api/:ke/list',
],function (req,res){
var endData = {ok:false}
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var endData = {
ok : false
}
var row = {
ke : req.params.ke,
uid : user.uid
}
var where = []
Object.keys(row).forEach(function(column){
where.push(column+'=?')
})
s.sqlQuery('SELECT * FROM API WHERE '+where.join(' AND '),Object.values(row),function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
row.details = JSON.parse(row.details)
})
endData.ok = true
endData.uid = user.uid
endData.ke = user.ke
endData.keys = rows
}
closeResponse(res,endData)
})
},res,req)
})
}

1843
libs/webServerPaths.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,358 @@
var express = require('express');
var fs = require('fs');
var bodyParser = require('body-parser');
var os = require('os');
var moment = require('moment');
var request = require('request');
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({})
var ejs = require('ejs');
var CircularJSON = require('circular-json');
module.exports = function(s,config,lang,app){
/**
* Page : Get Embed Stream
*/
app.get([config.webPaths.apiPrefix+':auth/embed/:ke/:id',config.webPaths.apiPrefix+':auth/embed/:ke/:id/:addon'], function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.params.protocol=req.protocol;
s.auth(req.params,function(user){
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
if(s.group[req.params.ke].mon[req.params.id].isStarted === true){
req.params.uid=user.uid;
res.render(config.renderPaths.embed,{data:req.params,baseUrl:req.protocol+'://'+req.hostname,config:config,lang:user.lang,mon:CircularJSON.parse(CircularJSON.stringify(s.group[req.params.ke].mon_conf[req.params.id])),originalURL:s.getOriginalUrl(req)});
res.end()
}else{
res.end(user.lang['Cannot watch a monitor that isn\'t running.'])
}
}else{
res.end(user.lang['No Monitor Exists with this ID.'])
}
},res,req);
});
/**
* API : Get Poseidon MP4 Stream
*/
app.get([config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.ts',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.ts'], function (req, res) {
s.auth(req.params,function(user){
if(!s.group[req.params.ke] || !s.group[req.params.ke].mon[req.params.id]){
res.status(404);
res.end('404 : Monitor not found');
return
}
s.checkChildProxy(req.params,function(){
var Channel = 'MAIN'
if(req.params.channel){
Channel = parseInt(req.params.channel)+config.pipeAddition
}
var mp4frag = s.group[req.params.ke].mon[req.params.id].mp4frag[Channel];
var errorMessage = 'MP4 Stream is not enabled'
if(!mp4frag){
res.status(503);
res.end('503 : initialization : '+errorMessage);
}else{
var init = mp4frag.initialization;
if (!init) {
res.status(503);
res.end('404 : Not Found : '+errorMessage);
} else {
res.locals.mp4frag = mp4frag
res.set('Access-Control-Allow-Origin', '*')
res.set('Connection', 'close')
res.set('Cache-Control', 'private, no-cache, no-store, must-revalidate')
res.set('Expires', '-1')
res.set('Pragma', 'no-cache')
res.set('Content-Type', 'video/mp4')
res.status(200);
res.write(init);
mp4frag.pipe(res);
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
res.on('close', () => {
try{
mp4frag.unpipe(res)
}catch(err){}
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
}
}
},res,req);
},res,req);
});
/**
* API and Page : Get MJPEG Stream or Page
* @param {string} full - if `true` page will load the MJPEG iframe page
*/
app.get([config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id',config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id/:channel'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
if(req.query.full=='true'){
res.render(config.renderPaths.mjpeg,{url:'/'+req.params.auth+'/mjpeg/'+req.params.ke+'/'+req.params.id,originalURL:s.getOriginalUrl(req)});
res.end()
}else{
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
var Emitter
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
}
res.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace; boundary=shinobi',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache'
});
var contentWriter,content = fs.readFileSync(config.defaultMjpeg,'binary');
res.write("--shinobi\r\n");
res.write("Content-Type: image/jpeg\r\n");
res.write("Content-Length: " + content.length + "\r\n");
res.write("\r\n");
res.write(content,'binary');
res.write("\r\n");
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
Emitter.on('data',contentWriter=function(d){
content = d;
res.write(content,'binary');
})
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
});
}else{
res.end();
}
},res,req);
},res,req);
}
});
/**
* API : Get HLS Stream
*/
app.get([config.webPaths.apiPrefix+':auth/hls/:ke/:id/:file',config.webPaths.apiPrefix+':auth/hls/:ke/:id/:channel/:file'], function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
s.checkChildProxy(req.params,function(){
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/'
if(req.params.channel){
req.dir+='channel'+(parseInt(req.params.channel)+config.pipeAddition)+'/'+req.params.file;
}else{
req.dir+=req.params.file;
}
res.on('finish',function(){res.end();});
if (fs.existsSync(req.dir)){
fs.createReadStream(req.dir).pipe(res);
}else{
res.end(lang['File Not Found'])
}
},res,req)
}
s.auth(req.params,req.fn,res,req);
})
/**
* API : Get JPEG Snapshot
*/
app.get(config.webPaths.apiPrefix+':auth/jpeg/:ke/:id/s.jpg', function(req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/s.jpg';
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
});
res.on('finish',function(){res.end();});
if (fs.existsSync(req.dir)){
fs.createReadStream(req.dir).pipe(res);
}else{
fs.createReadStream(config.defaultMjpeg).pipe(res);
}
},res,req);
},res,req);
});
/**
* API : Get FLV Stream
*/
app.get([config.webPaths.apiPrefix+':auth/flv/:ke/:id/s.flv',config.webPaths.apiPrefix+':auth/flv/:ke/:id/:channel/s.flv'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
var Emitter,chunkChannel
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
chunkChannel = parseInt(req.params.channel)+config.pipeAddition
}
if(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel]){
//variable name of contentWriter
var contentWriter
//set headers
res.setHeader('Content-Type', 'video/x-flv');
res.setHeader('Access-Control-Allow-Origin','*');
//write first frame on stream
res.write(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel])
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
//write new frames as they happen
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
//remove contentWriter when client leaves
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
}else{
res.setHeader('Content-Type', 'application/json');
res.end(s.prettyPrint({ok:false,msg:'FLV not started or not ready'}))
}
},res,req)
},res,req)
})
/**
* API : Get H.265/h265 HEVC stream
*/
app.get([config.webPaths.apiPrefix+':auth/h265/:ke/:id/s.hevc',config.webPaths.apiPrefix+':auth/h265/:ke/:id/:channel/s.hevc'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
var Emitter,chunkChannel
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
chunkChannel = parseInt(req.params.channel)+config.pipeAddition
}
//variable name of contentWriter
var contentWriter
//set headers
res.setHeader('Content-Type', 'video/mp4');
res.setHeader('Access-Control-Allow-Origin','*');
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
//write new frames as they happen
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
//remove contentWriter when client leaves
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
},res,req)
},res,req)
})
/**
* API : Get H.264 over HTTP
*/
app.get([
config.webPaths.apiPrefix+':auth/mpegts/:ke/:id/:feed/:file',
config.webPaths.apiPrefix+':auth/mpegts/:ke/:id/:feed/',
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed/:file',
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed',
config.webPaths.apiPrefix+':auth/h264/:ke/:id'
], function (req, res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(!req.query.feed){req.query.feed='1'}
var Emitter
if(!req.params.feed){
Emitter = s.group[req.params.ke].mon[req.params.id].streamIn[req.query.feed]
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.feed)+config.pipeAddition]
}
var contentWriter
var date = new Date();
res.writeHead(200, {
'Date': date.toUTCString(),
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Content-Type': 'video/mp4',
'Server': 'Shinobi H.264 Test Stream',
})
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
},res,req);
},res,req);
});
}

411
libs/webServerSuperPaths.js Normal file
View file

@ -0,0 +1,411 @@
var fs = require('fs');
var os = require('os');
var moment = require('moment')
var request = require('request')
var jsonfile = require("jsonfile")
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
module.exports = function(s,config,lang,app){
/**
* API : Superuser : Get Logs
*/
app.all([config.webPaths.supersuperApiPrefix+':auth/logs'], function (req,res){
req.ret={ok:false};
s.superAuth(req.params,function(resp){
req.sql='SELECT * FROM Logs WHERE ke=?';req.ar=['$'];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1||req.params.id.indexOf('$')>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
if(req.query.start||req.query.end){
if(!req.query.startOperator||req.query.startOperator==''){
req.query.startOperator='>='
}
if(!req.query.endOperator||req.query.endOperator==''){
req.query.endOperator='<='
}
if(req.query.start && req.query.start !== '' && req.query.end && req.query.end !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
req.query.end = s.stringToSqlTime(req.query.end)
req.sql+=' AND `time` '+req.query.startOperator+' ? AND `time` '+req.query.endOperator+' ?';
req.ar.push(req.query.start)
req.ar.push(req.query.end)
}else if(req.query.start && req.query.start !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
req.sql+=' AND `time` '+req.query.startOperator+' ?';
req.ar.push(req.query.start)
}
}
if(!req.query.limit||req.query.limit==''){req.query.limit=50}
req.sql+=' ORDER BY `time` DESC LIMIT '+req.query.limit+'';
s.sqlQuery(req.sql,req.ar,function(err,r){
if(err){
err.sql=req.sql;
res.end(s.prettyPrint(err));
return
}
if(!r){r=[]}
r.forEach(function(v,n){
r[n].info=JSON.parse(v.info)
})
res.end(s.prettyPrint(r))
})
},res,req)
})
/**
* API : Superuser : Log delete.
*/
app.all(config.webPaths.superApiPrefix+':auth/logs/delete', function (req,res){
s.superAuth(req.params,function(resp){
s.sqlQuery('DELETE FROM Logs WHERE ke=?',['$'],function(){
var endData = {
ok : true
}
res.end(s.prettyPrint(endData))
})
},res,req)
})
/**
* API : Superuser : Update Shinobi
*/
app.all(config.webPaths.superApiPrefix+':auth/system/update', function (req,res){
s.superAuth(req.params,function(resp){
s.ffmpegKill()
s.systemLog('Shinobi ordered to update',{
by: resp.$user.mail,
ip: resp.ip
})
var updateProcess = spawn('sh',(s.mainDirectory+'/UPDATE.sh').split(' '),{detached: true})
updateProcess.stderr.on('data',function(data){
s.systemLog('Update Info',data.toString())
})
updateProcess.stdout.on('data',function(data){
s.systemLog('Update Info',data.toString())
})
var endData = {
ok : true
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Restart Shinobi
*/
app.all(config.webPaths.superApiPrefix+':auth/system/restart/:script', function (req,res){
s.superAuth(req.params,function(resp){
var check = function(x){return req.params.script.indexOf(x)>-1}
var endData = {
ok : true
}
if(check('system')){
s.systemLog('Shinobi ordered to restart',{by:resp.$user.mail,ip:resp.ip})
s.ffmpegKill()
endData.systemOuput = execSync('pm2 restart '+s.mainDirectory+'/camera.js')
}
if(check('cron')){
s.systemLog('Shinobi CRON ordered to restart',{by:resp.$user.mail,ip:resp.ip})
endData.cronOuput = execSync('pm2 restart '+s.mainDirectory+'/cron.js')
}
if(check('logs')){
s.systemLog('Flush PM2 Logs',{by:resp.$user.mail,ip:resp.ip})
endData.logsOuput = execSync('pm2 flush')
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Modify Configuration (conf.json)
*/
app.all(config.webPaths.superApiPrefix+':auth/system/configure', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
var postBody = s.getPostData(req)
if(!postBody){
endData.ok = false
endData.msg = lang.postDataBroken
}else{
s.systemLog('conf.json Modified',{
by: resp.$user.mail,
ip: resp.ip,
old:jsonfile.readFileSync(s.location.config)
})
jsonfile.writeFile(s.location.config,postBody,{spaces: 2},function(){
s.tx({f:'save_configuration'},'$')
})
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Get users in system
*/
app.all([
config.webPaths.superApiPrefix+':auth/accounts/list',
config.webPaths.superApiPrefix+':auth/accounts/list/:type',
], function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
searchQuery = 'SELECT ke,uid,auth,mail,details FROM Users'
queryVals = []
switch(req.params.type){
case'admin':case'administrator':
searchQuery += ' WHERE details NOT LIKE ?'
queryVals.push('%"sub"%')
break;
case'sub':case'subaccount':
searchQuery += ' WHERE details LIKE ?'
queryVals.push('%"sub"%')
break;
}
// ' WHERE details NOT LIKE ?'
s.sqlQuery(searchQuery,queryVals,function(err,users) {
endData.users = users
res.end(s.prettyPrint(endData))
})
},res,req)
})
/**
* API : Superuser : Save Superuser Preferences
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/saveSettings', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
var form = s.getPostData(req)
if(form){
var currentSuperUserList = jsonfile.readFileSync(s.location.super)
var currentSuperUser = {}
var currentSuperUserPosition = -1
//find this user in current list
currentSuperUserList.forEach(function(user,pos){
if(user.mail === resp.$user.mail){
currentSuperUser = user
currentSuperUserPosition = pos
}
})
var logDetails = {
by : resp.$user.mail,
ip : resp.ip
}
//check if pass and pass_again match, if not remove password
if(form.pass !== '' && form.pass === form.pass_again){
form.pass = s.createHash(form.pass)
}else{
delete(form.pass)
}
//delete pass_again from object
delete(form.pass_again)
//set new values
currentSuperUser = Object.assign(currentSuperUser,form)
//reset email and log change of email
if(form.mail !== resp.$user.mail){
logDetails.newEmail = form.mail
logDetails.oldEmail = resp.$user.mail
}
//log this change
s.systemLog('super.json Modified',logDetails)
//modify or add account in temporary master list
if(currentSuperUserList[currentSuperUserPosition]){
currentSuperUserList[currentSuperUserPosition] = currentSuperUser
}else{
currentSuperUserList.push(currentSuperUser)
}
//update master list in system
jsonfile.writeFile(s.location.super,currentSuperUserList,{spaces: 2},function(){
s.tx({f:'save_preferences'},'$')
})
}else{
endData.ok = false
endData.msg = lang.postDataBroken
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Create Admin account (Account to manage cameras)
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/registerAdmin', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : false
}
var close = function(){
res.end(s.prettyPrint(endData))
}
var isCallbacking = false
var form = s.getPostData(req)
if(form){
if(form.mail !== '' && form.pass !== ''){
if(form.pass === form.password_again || form.pass === form.pass_again){
isCallbacking = true
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[form.mail],function(err,r) {
if(r&&r[0]){
//found address already exists
endData.msg = lang['Email address is in use.'];
}else{
endData.ok = true
//create new
//user id
form.uid = s.gid()
//check to see if custom key set
if(!form.ke||form.ke===''){
form.ke=s.gid()
}else{
form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
}
//check if "details" is object
if(form.details instanceof Object){
form.details = JSON.stringify(form.details)
}
//write user to db
s.sqlQuery(
'INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',
[
form.ke,
form.uid,
form.mail,
s.createHash(form.pass),
form.details
]
)
s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$')
//init user
s.loadGroup(form)
}
close()
})
}else{
endData.msg = lang["Passwords Don't Match"]
}
}else{
endData.msg = lang['Email and Password fields cannot be empty']
}
}else{
endData.msg = lang.postDataBroken
}
if(isCallbacking === false)close()
},res,req)
})
/**
* API : Superuser : Edit Admin account (Account to manage cameras)
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/editAdmin', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : false
}
var close = function(){
res.end(s.prettyPrint(endData))
}
var form = s.getPostData(req)
if(form){
var account = s.getPostData(req,'account')
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[account.mail],function(err,r) {
if(r && r[0]){
r = r[0]
var details = JSON.parse(r.details)
if(form.pass && form.pass !== ''){
if(form.pass === form.password_again){
form.pass = s.createHash(form.pass);
}else{
endData.msg = lang["Passwords Don't Match"]
close()
return
}
}else{
delete(form.pass);
}
delete(form.password_again);
var keys = Object.keys(form)
var set = []
var values = []
keys.forEach(function(v,n){
if(set==='ke'||set==='password_again'||!form[v]){return}
set.push(v+'=?')
if(v === 'details'){
form[v] = s.stringJSON(Object.assign(details,s.parseJSON(form[v])))
}
values.push(form[v])
})
values.push(account.mail)
s.sqlQuery('UPDATE Users SET '+set.join(',')+' WHERE mail=?',values,function(err,r) {
if(err){
console.log(err)
endData.error = err
endData.msg = lang.AccountEditText1
}else{
endData.ok = true
s.tx({f:'edit_account',form:form,ke:account.ke,uid:account.uid},'$')
delete(s.group[account.ke].init);
s.loadGroupApps(account)
}
close()
})
}
})
}else{
endData.msg = lang.postDataBroken
close()
}
},res,req)
})
/**
* API : Superuser : Delete Admin account (Account to manage cameras)
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/deleteAdmin', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
var close = function(){
res.end(s.prettyPrint(endData))
}
var account = s.getPostData(req,'account')
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[account.uid,account.ke,account.mail])
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[account.uid,account.ke])
if(s.getPostData(req,'deleteSubAccounts',false) === '1'){
s.sqlQuery('DELETE FROM Users WHERE ke=?',[account.ke])
}
if(s.getPostData(req,'deleteMonitors',false) == '1'){
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[account.ke],function(err,monitors){
if(monitors && monitors[0]){
monitors.forEach(function(monitor){
s.camera('stop',monitor)
})
s.sqlQuery('DELETE FROM Monitors WHERE ke=?',[account.ke])
}
})
}
if(s.getPostData(req,'deleteVideos',false) == '1'){
s.sqlQuery('DELETE FROM Videos WHERE ke=?',[account.ke])
fs.chmod(s.dir.videos+account.ke,0o777,function(err){
fs.unlink(s.dir.videos+account.ke,function(err){})
})
}
if(s.getPostData(req,'deleteEvents',false) == '1'){
s.sqlQuery('DELETE FROM Events WHERE ke=?',[account.ke])
}
s.tx({f:'delete_account',ke:account.ke,uid:account.uid,mail:account.mail},'$')
close()
},res,req)
})
}

View file

@ -6,7 +6,7 @@
"main": "camera.js",
"bin": "camera.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "node test.js",
"start": "chmod +x INSTALL/start.sh && INSTALL/start.sh"
},
"repository": {
@ -34,14 +34,14 @@
"pipe2pam": "^0.6.2",
"nodemailer": "^4.0.1",
"node-onvif": "^0.1.4",
"onvif-nvt": "0.2.8",
"path": "^0.12.7",
"request": "^2.79.0",
"socket.io": "^1.7.1",
"socket.io-client": "^1.7.2",
"http-proxy": "^1.17.0",
"webdav": "^0.3.1",
"webdav-fs": "^1.11.0",
"discord.js": "^11.3.2",
"backblaze-b2": "^1.0.4",
"ldapauth-fork": "^4.0.2"
},
"devDependencies": {}

77
test.js Normal file
View file

@ -0,0 +1,77 @@
//
// Shinobi
// Copyright (C) 2016 Moe Alam, moeiscool
//
//
// # Donate
//
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
var io = new (require('socket.io'))()
//library loader
var loadLib = function(lib){
return require(__dirname+'/libs/'+lib+'.js')
}
//process handlers
var s = loadLib('process')(process,__dirname)
//configuration loader
var config = loadLib('config')(s)
//********* test.js >
config.port = 9999
if(config.childNodes && config.childNodes.enabled === true && config.childNodes.mode === 'master'){
config.childNodes.port = 9998
}
//********* test.js />
//language loader
var lang = loadLib('language')(s,config)
//basic functions
loadLib('basic')(s,config)
//load extender functions
loadLib('extenders')(s,config)
//video processing engine
loadLib('ffmpeg')(s,config,function(ffmpeg){
//********* test.js >
s.ffmpegFunctions = ffmpeg
//********* test.js />
//database connection : mysql, sqlite3..
loadLib('sql')(s,config)
//working directories : videos, streams, fileBin..
loadLib('folders')(s,config)
//authenticator functions : API, dashboard login..
loadLib('auth')(s,config,lang)
//express web server with ejs
var app = loadLib('webServer')(s,config,lang,io)
//web server routes : page handling..
loadLib('webServerPaths')(s,config,lang,app)
//web server routes for streams : streams..
loadLib('webServerStreamPaths')(s,config,lang,app)
//web server admin routes : create sub accounts, share monitors, share videos
loadLib('webServerAdminPaths')(s,config,lang,app)
//web server superuser routes : create admin accounts and manage system functions
loadLib('webServerSuperPaths')(s,config,lang,app)
//websocket connection handlers : login and streams..
loadLib('socketio')(s,config,lang,io)
//user and group functions
loadLib('user')(s,config)
//monitor/camera handlers
loadLib('monitor')(s,config,lang)
//event functions : motion, object matrix handler
loadLib('events')(s,config,lang)
//built-in detector functions : pam-diff..
loadLib('detector')(s,config)
//recording functions
loadLib('videos')(s,config,lang)
//plugins : websocket connected services..
loadLib('plugins')(s,config,lang)
//health : cpu and ram trackers..
loadLib('health')(s,config,lang,io)
//cluster module
loadLib('childNode')(s,config,lang,app,io)
//cloud uploaders : amazon s3, webdav, backblaze b2..
loadLib('cloudUploaders')(s,config,lang)
//notifiers : discord..
loadLib('notification')(s,config,lang)
//on-start actions, daemon(s) starter
require(__dirname+'/test/run.js')(s,config,lang,app,io)
})

130
test/run.js Normal file
View file

@ -0,0 +1,130 @@
module.exports = function(s,config,lang,app,io){
var checkResult = function(functionName,expectedResult,testResult){
if(expectedResult !== testResult){
console.log(expectedResult,testResult)
throw new Error('x ' + functionName + ' : Failed!')
}else{
console.log('- ' + functionName + ' : Success')
}
}
var sampleMonitorObject = require('./testMonitor-WatchOnly.json')
var test = {
"basic.js" : {
checkRelativePath : function(){
var expectedResult = s.mainDirectory + '/'
var testResult = s.checkRelativePath('')
checkResult('checkRelativePath',expectedResult,testResult)
},
parseJSON : function(){
var expectedResult = {}
var testResult = s.parseJSON('{}')
checkResult('parseJSON',JSON.stringify(expectedResult),JSON.stringify(testResult))
},
stringJSON : function(){
var expectedResult = '{}'
var testResult = s.stringJSON({})
checkResult('stringJSON',expectedResult,testResult)
},
addUserPassToUrl : function(){
var expectedResult = 'http://user:pass@url.com'
var testResult = s.addUserPassToUrl('http://url.com','user','pass')
checkResult('addUserPassToUrl',expectedResult,testResult)
},
checkCorrectPathEnding : function(){
var expectedResult = '/'
var testResult = s.checkCorrectPathEnding('')
checkResult('checkCorrectPathEnding',expectedResult,testResult)
},
md5 : function(){
var expectedResult = '5f4dcc3b5aa765d61d8327deb882cf99'
var testResult = s.md5('password')
checkResult('md5',expectedResult,testResult)
},
sha256 : function(){
var expectedResult = '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
var testResult = require('crypto').createHash('sha256').update('test').digest("hex")
checkResult('createHash/sha256',expectedResult,testResult)
},
nameToTime : function(){
var expectedResult = '2018-10-22 23:00:00'
var testResult = s.nameToTime('2018-10-22T23-00-00.mp4')
checkResult('nameToTime',expectedResult,testResult)
},
ipRange : function(){
var expectedResult = [
'192.168.1.1',
'192.168.1.2',
'192.168.1.3'
]
var testResult = s.ipRange('192.168.1.1','192.168.1.3')
checkResult('ipRange',JSON.stringify(expectedResult),JSON.stringify(testResult))
},
portRange : function(){
var expectedResult = [
8000,
8001,
8002,
]
var testResult = s.portRange(8000,8002)
checkResult('portRange',JSON.stringify(expectedResult),JSON.stringify(testResult))
},
getFunctionParamNames : function(){
var testing = function(arg1,arg2){}
var expectedResult = [
'arg1',
'arg2',
]
var testResult = s.getFunctionParamNames(testing)
checkResult('getFunctionParamNames',JSON.stringify(expectedResult),JSON.stringify(testResult))
}
},
"ffmpeg.js" : {
splitForFFPMEG : function(){
var expectedResult = [
'flag1',
'flag2',
'fl ag3',
]
var testResult = s.splitForFFPMEG('flag1 flag2 "fl ag3"')
checkResult('splitForFFPMEG',JSON.stringify(expectedResult),JSON.stringify(testResult))
},
"ffmpeg" : function(){
//command string builder
var x = {tmp : ''}
s.checkDetails(sampleMonitorObject)
sampleMonitorObject.url = s.buildMonitorUrl(sampleMonitorObject)
var expectedResult = '-loglevel warning -progress pipe:5 -analyzeduration 1000000 -probesize 1000000 -stream_loop -1 -fflags +igndts -re -i "https://cdn.shinobi.video:/videos/bears.mp4" -f mp4 -an -c:v copy -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1 pipe:1'
s.ffmpegFunctions.buildMainInput(sampleMonitorObject,x)
s.ffmpegFunctions.buildMainStream(sampleMonitorObject,x)
s.ffmpegFunctions.buildMainRecording(sampleMonitorObject,x)
s.ffmpegFunctions.buildMainDetector(sampleMonitorObject,x)
s.ffmpegFunctions.assembleMainPieces(sampleMonitorObject,x)
var testResult = x.ffmpegCommandString
checkResult('ffmpeg',expectedResult,testResult)
//check pipe builder
var expectedResult = []
var times = config.pipeAddition
if(sampleMonitorObject.details.stream_channels){
times += sampleMonitorObject.details.stream_channels.length
}
for(var i=0; i < times; i++){
expectedResult.push('pipe')
}
s.ffmpegFunctions.createPipeArray(sampleMonitorObject,x)
var testResult = x.stdioPipes
checkResult('ffmpeg.createPipeArray',JSON.stringify(expectedResult),JSON.stringify(testResult))
}
}
}
console.log('----- Function Test Starting')
Object.keys(test).forEach(function(libkey){
var library = test[libkey]
console.log('--- Testing ' + libkey + '...')
Object.keys(library).forEach(function(key){
var functionTest = library[key]
functionTest()
})
console.log('-- Completed ' + libkey + '...')
})
console.log('---- Function Test Ended')
}

7
test/testAdminUser.json Normal file
View file

@ -0,0 +1,7 @@
{
"mail": "user@shinobi.video",
"ke": "",
"pass": "password",
"password_again": "password",
"details": "{\"factorAuth\":\"0\",\"size\":\"\",\"days\":\"\",\"event_days\":\"\",\"log_days\":\"\",\"max_camera\":\"\",\"permissions\":\"all\",\"edit_size\":\"1\",\"edit_days\":\"1\",\"edit_event_days\":\"1\",\"edit_log_days\":\"1\",\"use_admin\":\"1\",\"use_aws_s3\":\"1\",\"use_webdav\":\"1\",\"use_discordbot\":\"1\",\"use_ldap\":\"1\"}"
}

View file

@ -0,0 +1,11 @@
{
"account": {
"mail": "[LOGIN ADDRESS]",
"ke": "[GROUP KEY]",
"uid": "[USER ID]"
},
"deleteSubAccounts": "1",
"deleteMonitors": "1",
"deleteVideos": "1",
"deleteEvents": "1"
}

View file

@ -0,0 +1,31 @@
{
"data": {
"mail": "[GROUP KEY]",
"ke": "[GROUP KEY]",
"pass": "[PASSWORD]",
"password_again": "[PASSWORD AGAIN]",
"details": {
"factorAuth": "0",
"size": "10000",
"days": "5",
"event_days": "10",
"log_days": "10",
"max_camera": "",
"permissions": "all",
"edit_size": "1",
"edit_days": "1",
"edit_event_days": "1",
"edit_log_days": "1",
"use_admin": "1",
"use_aws_s3": "1",
"use_webdav": "1",
"use_discordbot": "1",
"use_ldap": "1"
}
},
"account": {
"mail": "[GROUP KEY]",
"ke": "[GROUP KEY]",
"uid": "[USER ID]"
}
}

View file

@ -0,0 +1,26 @@
{
"data": {
"mail": "[LOGIN ADDRESS]",
"ke": "[GROUP KEY]",
"pass": "[PASSWORD]",
"password_again": "[PASSWORD AGAIN]",
"details": {
"factorAuth": "0",
"size": "10000",
"days": "5",
"event_days": "10",
"log_days": "10",
"max_camera": "",
"permissions": "all",
"edit_size": "1",
"edit_days": "1",
"edit_event_days": "1",
"edit_log_days": "1",
"use_admin": "1",
"use_aws_s3": "1",
"use_webdav": "1",
"use_discordbot": "1",
"use_ldap": "1"
}
}
}

15
test/testApiAdd.json Normal file
View file

@ -0,0 +1,15 @@
{
"data": {
"ip": "[IP ADDRESS]",
"details": {
"auth_socket": "1",
"get_monitors": "1",
"control_monitors": "1",
"get_logs": "1",
"watch_stream": "1",
"watch_snapshot": "1",
"watch_videos": "1",
"delete_videos": "1"
}
}
}

5
test/testApiDelete.json Normal file
View file

@ -0,0 +1,5 @@
{
"data": {
"code": "[API KEY]"
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
{
"uid" : "[SUB-ACCOUNT USER ID]",
"mail" : "[SUB-ACCOUNT LOGIN ADDRESS]"
}

10
test/testSubUserEdit.json Normal file
View file

@ -0,0 +1,10 @@
{
"uid": "[SUB-ACCOUNT USER ID]",
"mail": "[SUB-ACCOUNT LOGIN ADDRESS]",
"data": {
"details": {
"sub": "1",
"allmonitors": "1"
}
}
}

View file

@ -0,0 +1,7 @@
{
"data": {
"mail": "[SUB-ACCOUNT LOGIN ADDRESS]",
"pass": "[SUB-ACCOUNT PASSWORD]",
"password_again": "[SUB-ACCOUNT PASSWORD]"
}
}

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@
position: relative;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
padding-left: 15px;
}
.col-5ths {
@ -24,7 +24,7 @@
-ms-flex: 0 0 20%;
flex: 0 0 20%;
max-width: 20%;
}
}
}
@media (min-width: 768px) {
@ -34,7 +34,7 @@
-ms-flex: 0 0 20%;
flex: 0 0 20%;
max-width: 20%;
}
}
}
::-webkit-scrollbar-thumb:hover {
background-color:#bd9565;
@ -90,6 +90,9 @@ img{max-width:100%}
}
.monitor_item .stream-hud{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}
.monitor_item .stream-hud .camera_cpu_usage{position:absolute;top:0;left:0;width: 100%;}
.monitor_item .stream-hud .camera_cpu_usage .progress{width: 100%;}
.monitor_item .stream-hud .camera_cpu_usage:hover .progress{height:20px;transition:0.2s}
.monitor_item .stream-hud .controls{position:absolute;top:10px;left:10px;}
.monitor_item .stream-hud:hover{opacity:1}
.monitor_item .stream-hud .bottom-text{position:absolute;bottom:0;left:0;width:100%;padding:5px;text-shadow: 0 0 10px #333;}
@ -114,8 +117,7 @@ img{max-width:100%}
.monitor_item.fullscreen img.stream-element{height:100%;width:auto}
.monitor_item.fullscreen canvas.stream-element{height:auto;width:auto;background-color:black;}
.monitor_item .stream-element{border: 0;object-fit: fill;height: 100%;width:100%}
.monitor_item{position:relative;padding:0;}
.monitor_item{transition:none;}
.monitor_item{position:relative;padding:0;transition:none;background:#000}
.monitor_item .mdl-card{min-height:auto;border:1px solid #272727;border-radius:0px;overflow:hidden}
.monitor_item .mdl-card__media{position:relative;padding:0!important;display:block!important;background:#000;}
.monitor_item.selected .stream-element{height:600px}
@ -124,7 +126,7 @@ img{max-width:100%}
.monitor_item.detector_triggered .detector-fade{opacity:1}
.monitor_item .detector-fade{opacity:0}
.monitor_item .indifference{position:absolute;width:100%;left:0;top:0;transition:0.2s;}
.monitor_item .indifference .progress{width:100%;background:#333;box-shadow:0;}
.monitor_item .progress{width:100%;background:#333;box-shadow:0;}
.monitor_item .indifference:hover .progress{height:20px;transition:0.2s}
.hide_indifference .indifference{display:none!important}
.hide_indifference [class_toggle="hide_indifference"]{color:#d9534f!important}
@ -330,9 +332,16 @@ form.modal-body{margin:0}
.timelapse_hud .controlBar{position: absolute;background:rgba(22,22,22,0.8);width:100%;left:0;bottom:0;}
.timelapse_hud .hover-hide{opacity:0}
.timelapse_hud:hover .hover-hide{opacity:1;z-index:5}
.video_grid{overflow: auto;height: 100%;display: block;}
.video_grid .col-md-2{padding-left:5px;padding-right:5px;padding-bottom:10px}
.video_grid .thumb{width:100%;height:150px;display:inline-block;background-size:cover;position:relative;overflow:hidden;border-radius:4px;border:1px solid #000;box-shadow:0 0 10px #151515}
.video_grid .thumb .title-strip, .video_grid .thumb .button-strip{width:100%;position:absolute;left:0;background:rgba(0,0,0,0.7);color:#fff;padding:4px}
.video_grid .thumb .title-strip{top:0;opacity:0.5}
.video_grid .thumb .button-strip{bottom:0;opacity:0}
.video_grid .thumb:hover .title-strip, .video_grid .thumb:hover .button-strip{opacity:1}
.table-striped>tbody>tr>td{vertical-align:middle}
.table-striped .thumbnail{width:100px;height:80px;border-radius:5px;margin:0;display:inline-block;}
#motion_list{height:155px;overflow:auto;border-radius:5px;border:1px solid #444;position:relative;background: #222;margin:0}
.dark .list-group-item{border-color: #444;background:#222}
.dark .list-group-item.active{background:#c49a68;border-color:#a7865f}
@ -456,7 +465,7 @@ ul.msg_list li .message {
.nav>li>a:focus, .nav>li>a:hover,.nav .open>a, .nav .open>a:focus, .nav .open>a:hover{background:#867560}
.mdl-js-layout.hide-side:not(.is-small-screen){
}
@media screen and (min-width: 1025px){
.mdl-js-layout.hide-side:not(.is-small-screen)>.mdl-layout__drawer {
@ -639,7 +648,7 @@ ul.msg_list li .message {
}
/*animations*/
@keyframes blink {
@keyframes blink {
0% { opacity:1 }
50% { opacity:0 }
100% { opacity:1 }
@ -730,4 +739,4 @@ ul.msg_list li .message {
.bg-hexagon {
background-color: #054e9f;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='hexagons' fill='%23fdfdfd' fill-opacity='0.4' fill-rule='nonzero'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
}

View file

@ -0,0 +1,6 @@
.navbar.bg-primary,.nav.bg-primary {
background-color: #903619 !important;
}
.navbar.bg-success {
background-color: #1f791b !important;
}

36
web/libs/js/basic.js Normal file
View file

@ -0,0 +1,36 @@
var tool = {}
tool.getVideoImage = function (path, secs, callback) {
var me = this, video = document.createElement('video');
var backCalled = false
var finish = function(err,data){
if(!backCalled){
backCalled = true
callback(err,data)
clearTimeout(timeout)
}
}
var timeout = setTimeout(function(){
finish(new Error('Failed Getting Snap from Video'))
},5000)
video.onloadedmetadata = function() {
video.play()
this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration)
video.pause()
};
video.onseeked = function(e) {
var canvas = document.createElement('canvas')
canvas.height = video.videoHeight
canvas.width = video.videoWidth
var ctx = canvas.getContext('2d')
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
var base64 = canvas.toDataURL()
finish(null, base64)
delete(ctx)
delete(video)
delete(canvas)
};
video.onerror = function(e) {
finish(e)
};
video.src = path;
}

11
web/libs/js/extra.js Normal file
View file

@ -0,0 +1,11 @@
var browserCheck = {}
browserCheck.forLastPass = function(){
var lastpass = navigator.plugins['LastPass'];
if (lastpass === undefined) {
return false;
}
return true;
}
if(browserCheck.forLastPass){
alert('It appears you are using LastPass. Be aware that LastPass is known to cause issues with the form fields.')
}

File diff suppressed because it is too large Load diff

View file

@ -4192,4 +4192,4 @@
return _moment;
}));
}));

View file

@ -19,7 +19,8 @@ var Poseidon = function () {
uid: options.uid,
ke: options.ke,
id: options.id,
channel: options.channel
channel: options.channel,
errorCallback: options.onError
};
_classCallCheck(this, Poseidon);
@ -28,10 +29,11 @@ var Poseidon = function () {
} else {
this._callback = function (err, msg) {
if (err) {
console.error('Poseidon Error: ' + err);
if(_monitor.errorCallback)_monitor.errorCallback(err)
console.error('Poseidon Error: ' + err,options);
return;
}
console.log('Poseidon Message: ' + msg);
console.log('Poseidon Message: ' + msg,options);
};
}
if (!options.video || !(options.video instanceof HTMLVideoElement)) {
@ -467,7 +469,7 @@ var Poseidon = function () {
}, {
key: '_onSocketDisconnect',
value: function _onSocketDisconnect(event) {
this._callback(null, 'socket disconnect "' + event + '"');
this._callback(true, 'socket disconnect "' + event + '"');
this.stop();
}
}, {
@ -502,6 +504,7 @@ var Poseidon = function () {
}, {
key: '_onSegment',
value: function _onSegment(data) {
if(!this._mediaSource)this_.socket.disconnect()
if (this._sourceBuffer.buffered.length) {
var lag = this._sourceBuffer.buffered.end(0) - this._video.currentTime;
if (lag > 0.5) {
@ -605,4 +608,4 @@ var Poseidon = function () {
//first request codec string to test against browser and then feed first into source
//then request init-segment to feed
//then request media segments until we run into pause, stop, close, error, buffer not ready, etc
//change poster on video element based on current status, error, not ready, etc
//change poster on video element based on current status, error, not ready, etc

View file

@ -1,32 +1,18 @@
<% include blocks/header %>
<script>$user=<%-JSON.stringify($user)%></script>
<style>
.shinobi-bg {
background: url(/libs/img/bg.jpg);
background-size: cover;
background-position: center;
position: relative;
}.shinobi-bg-shade {
position: absolute;
height: 100%;
width: 100%;
background: rgba(0,0,0,0.5);
}
nav{margin-top:20px}
</style>
<link rel="stylesheet" href="<%-originalURL%>libs/css/pnotify.custom.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/vbox.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/circles.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/admin-page.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/bootstrap.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/font-awesome.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/fullcalendar.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/bootstrap-table.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/main.dash2.css">
<body class="shinobi-bg">
<div class="shinobi-bg-shade">
<div class="container-fluid">
<div class="container">
<nav class="navbar navbar-default">
<nav class="navbar navbar-default navbar-forestgreen">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
@ -51,18 +37,18 @@
</ul>
</div><!-- /.container-fluid -->
</nav>
<div class="row">
<div class="col-md-6">
<div class="form-group-group grey">
<div class="form-group-group forestgreen">
<h4>Sub-Accounts</h4>
<table class="table table-striped" id="sub_accounts"></table>
</div>
</div>
<div class="col-md-6">
<form id="add_new">
<div class="form-group-group grey">
<div class="form-group-group forestgreen">
<h4>Add<small id="msg" class="pull-right" style="color:#fff"></small></h4>
<div class="form-group">
<label><div><span>Email</span></div>
@ -93,7 +79,6 @@
</div>
<% include blocks/confirm.ejs %>
<% include blocks/subpermissions.ejs %>
</div>
</body>
<script><% include ../libs/js/socket.io.js %></script>
<script><% include ../libs/js/pnotify.custom.min.js %></script>
@ -109,6 +94,7 @@ $.ccio.cx=function(x){if(!x.ke){x.ke=$user.ke;};if(!x.uid){x.uid=$user.uid;};ret
$.ccio.ws.on('connect',function(d){
$.ccio.cx({f:'init',auth:$user.auth_token});
})
PNotify.prototype.options.styling = "fontawesome";
$.ccio.ws.on('f',function(d){
console.log(d);
switch(d.f){
@ -117,13 +103,30 @@ $.ccio.ws.on('f',function(d){
$.each(d.form,function(n,v){
account[n]=v;
});
account.detailsJSON=JSON.parse(account.details);
account.detailsJSON=JSON.parse(account.details)
new PNotify({
title : 'Account Edited',
text : '<b>' + account.mail + '</b> has been updated.',
type : 'success'
})
break;
case'add_sub_account':
$.ccio.tm(0,d,'#sub_accounts')
var account = $.ccio.subs[d.uid]
new PNotify({
title : 'Account Added',
text : '<b>' + account.mail + '</b> has been added.',
type : 'success'
})
break;
case'delete_sub_account':
$('#sub_accounts tr[uid="'+d.uid+'"]').remove()
var account = $.ccio.subs[d.uid]
new PNotify({
title : 'Account Deleted',
text : '<b>' + account.mail + '</b> has been deleted.',
type : 'info'
})
break;
}
})
@ -146,30 +149,36 @@ $subs=<%-JSON.stringify($subs)%>;
$.each($subs,function(n,v){
$.ccio.tm(0,v,'#sub_accounts')
})
//add new
$.aN={e:$('#add_new')};
$.aN.e.submit(function(e){
e.preventDefault();
e.s=$.aN.e.serializeObject();e.m=$('#msg').empty()
$.post($user.auth_token+'/register/'+$user.ke+'/'+$user.uid,e.s,function(d){
e.s = $.aN.e.serializeObject()
e.m = $('#msg').empty()
$.post('<%=originalURL%><%=config.webPaths.adminApiPrefix%>'+$user.auth_token+'/accounts/'+$user.ke+'/register',{data:e.s},function(d){
if(d.msg){
e.m.text(d.msg)
};
}
});
return false;
});
//sub simple lister
$.sU={e:$('#sub_accounts')};
$.sU.e.on('click','.delete',function(e){
e.e=$(this).parents('tr');e.m=e.e.find('.mail').text();e.u=e.e.attr('uid');
$.confirm.e.modal('show');
$.confirm.title.html('Delete Sub-Account <small>'+e.u+'</small>')
e.html='Do you want to delete <b>'+e.m+'</b>? You cannot recover this account.'
$.confirm.body.html(e.html)
var el = $(this).parents('tr')
var subAccountEmail = el.find('.mail').text()
var subAccountUid = el.attr('uid')
$.confirm.e.modal('show')
$.confirm.title.html('Delete Sub-Account <small>'+subAccountUid+'</small>')
var html = 'Do you want to delete <b>'+subAccountEmail+'</b>? You cannot recover this account.'
$.confirm.body.html(html)
$.confirm.click({title:'Delete',class:'btn-danger'},function(){
$.ccio.cx({f:'accounts',ff:'delete',$uid:e.u,auth:$user.auth_token,mail:e.m})
});
$.post('<%=originalURL%><%=config.webPaths.adminApiPrefix%>'+$user.auth_token+'/accounts/'+$user.ke+'/delete',{
uid: subAccountUid,
mail: subAccountEmail
},function(data){})
})
})
$.sU.e.on('click','.permission',function(e){
e.e=$(this).parents('tr');e.m=e.e.find('.mail').text();e.u=e.e.attr('uid');
@ -235,15 +244,28 @@ $.pR.e.on('change','[monitor]',function(e){
e.details.val(JSON.stringify(e.detail))
});
$.pR.f.submit(function(e){
e.preventDefault();
e.s=$(this).serializeObject()
$.ccio.cx({f:'accounts',ff:'edit',$uid:$.pR.user,auth:$user.auth_token,mail:$.ccio.subs[$.pR.user].mail,form:e.s})
$.pR.e.modal('hide')
return false;
e.preventDefault()
var form = $(this).serializeObject()
$.post('<%=originalURL%><%=config.webPaths.adminApiPrefix%>'+$user.auth_token+'/accounts/'+$user.ke+'/edit',{
uid: $.pR.user,
mail: $.ccio.subs[$.pR.user].mail,
data: form
},function(data){
if(data.ok){
$.pR.e.modal('hide')
}else{
new PNotify({
title : 'Failed to Add Account',
text : data.msg,
type : 'error'
})
}
})
return false
})
$('body')
.on('click','.logout',function(e){
localStorage.removeItem('ShinobiLogin_'+location.host);location.href=location.href;
})
</script>
</script>

View file

@ -92,4 +92,4 @@
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,39 @@
<form>
<nav class="navbar navbar-rounded navbar-expand-lg bg-primary">
<div class="container">
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link submit">
<p><i class="fa fa-check"></i> <%-lang.Save%></p>
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="form-group">
<label>
<div><span><%-lang.Email%></span></div>
<div><input class="form-control" type="email" name="mail" value="<%- $user.mail %>"></div>
</label>
</div>
<!-- <div class="form-group">
<label>
<div><span><%-lang.Username%></span></div>
<div><input class="form-control" type="text" name="pass"></div>
</label>
</div> -->
<div class="form-group">
<label>
<div><span><%-lang.Password%></span></div>
<div><input class="form-control" type="password" name="pass"></div>
</label>
</div>
<div class="form-group">
<label>
<div><span><%-lang['Password Again']%></span></div>
<div><input class="form-control" type="password" name="pass_again"></div>
</label>
</div>
</form>

View file

@ -30,15 +30,15 @@
<div class="form-group-group blue where">
<h4>Conditions
<div class="pull-right">
<a class="btn btn-success btn-xs add">&nbsp;<i class="fa fa-plus"></i>&nbsp;</a>
<a class="btn btn-danger btn-xs remove">&nbsp;<i class="fa fa-trash-o"></i>&nbsp;</a>
<a class="btn btn-success btn-xs add">&nbsp;<i class="fa fa-plus"></i>&nbsp;</a>
<a class="btn btn-danger btn-xs remove">&nbsp;<i class="fa fa-trash-o"></i>&nbsp;</a>
</div>
</h4>
<div id="detector_filters_where"></div>
</div>
<div class="form-group-group red actions">
<h4><%- lang['Action for Selected'] %></h4>
<%
<%
var possibleActions = [
{
"name": "halt",
@ -210,6 +210,14 @@
}
]
},
{
"name": "indifference",
"field": "Modify Indifference",
"description": "Modify minimum indifference required for event.",
"placeholder": "",
"default": "",
"example": ""
},
].forEach(function(item){ %>
<%
var name = 'actions='+item.name
@ -226,11 +234,15 @@
</span>
</div>
<div>
<% if(item.possible){ %>
<select <%- name %> class="form-control">
<% item.possible.forEach(function(option){ %>
<option value="<%-option.value%>"><%-option.name%></option>
<% }) %>
</select>
<% }else{ %>
<input <%- name %> class="form-control" value="<%- item.default %>" placeholder="<%- item.placeholder %>">
<% } %>
</div>
</label>
</div>
@ -246,4 +258,4 @@
</div>
</form>
</div>
</div>
</div>

View file

@ -1,3 +1,5 @@
<link rel="icon" href="<%-originalURL%>libs/img/icon/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="<%-originalURL%>libs/img/icon/favicon.ico" type="image/x-icon" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Shinobi">
@ -9,4 +11,4 @@
<link rel="apple-touch-icon" sizes="120x120" href="<%-originalURL%>libs/img/icon/apple-touch-icon-120x120.png" />
<link rel="apple-touch-icon" sizes="144x144" href="<%-originalURL%>libs/img/icon/apple-touch-icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="<%-originalURL%>libs/img/icon/apple-touch-icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="<%-originalURL%>libs/img/icon/apple-touch-icon-180x180.png" />
<link rel="apple-touch-icon" sizes="180x180" href="<%-originalURL%>libs/img/icon/apple-touch-icon-180x180.png" />

View file

@ -8,8 +8,6 @@
<link rel="stylesheet" href="<%-originalURL%>libs/css/material.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/material.style.css">
<meta name="mobile-web-app-capable" content="yes">
<link rel="icon" href="<%-originalURL%>libs/img/icon/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="<%-originalURL%>libs/img/icon/favicon.ico" type="image/x-icon" />
<% include header-favicon.ejs %>
<script src="<%-originalURL%>libs/js/jquery.min.js"></script>
<script src="<%-originalURL%>libs/js/jquery-ui.min.js"></script>
@ -18,4 +16,4 @@
<% cleanLang = function(string){
if(!string){string=''}
return string.replace(/'/g,"\\'")
}%>
}%>

View file

@ -34,7 +34,7 @@
<label><div><span><%-lang['2-Factor Authentication']%> (<%-lang.Email%>)</span></div>
<div><select class="form-control" detail="factorAuth">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
@ -66,8 +66,8 @@
<div class="form-group">
<label><div><span><%-lang['Permissions']%></span></div>
<div><select class="form-control" detail="permissions" selector="h_l">
<option value="all" selected><%-lang['All Privileges']%></option>
<option value="limited"><%-lang.Limited%></option>
<option value="all" selected><%-lang['All Privileges']%></option>
<option value="limited"><%-lang.Limited%></option>
</select></div>
</label>
</div>
@ -75,7 +75,7 @@
<label><div><span><%-lang['Can edit Max Storage']%></span></div>
<div><select class="form-control" detail="edit_size">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -83,7 +83,7 @@
<label><div><span><%-lang['Can edit Max Days']%></span></div>
<div><select class="form-control" detail="edit_days">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -91,7 +91,7 @@
<label><div><span><%-lang['Can edit how long to keep Events']%> <small><%-lang['in Days']%></small></span></div>
<div><select class="form-control" detail="edit_event_days">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -99,7 +99,7 @@
<label><div><span><%-lang['Can edit how long to keep Logs']%> <small><%-lang['in Days']%></small></span></div>
<div><select class="form-control" detail="edit_log_days">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -107,7 +107,7 @@
<label><div><span><%-lang['Can use Admin Panel']%></span></div>
<div><select class="form-control" detail="use_admin">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -115,7 +115,7 @@
<label><div><span><%-lang['Can use Amazon S3']%></span></div>
<div><select class="form-control" detail="use_aws_s3">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -123,7 +123,7 @@
<label><div><span><%-lang['Can use WebDAV']%></span></div>
<div><select class="form-control" detail="use_webdav">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -131,7 +131,7 @@
<label><div><span><%-lang['Can use Discord Bot']%></span></div>
<div><select class="form-control" detail="use_discordbot">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
@ -139,15 +139,15 @@
<label><div><span><%-lang['Can use LDAP']%></span></div>
<div><select class="form-control" detail="use_ldap">
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default pull-left" data-dismiss="modal"><i class="fa fa-times"></i> <%-lang.Close%></button>
<div class="pull-left">
<div class="pull-left" style="display:none">
<div class="marc-toggle display-inline">
<input type="checkbox" id="edit"/><label for="edit">Add or Edit</label>
</div>
@ -164,16 +164,32 @@ $.aN={e:$('#add_edit')};$.aN.f=$.aN.e.find('form')
$.aN.modeIsEdit = false
$.aN.f.submit(function(e){
e.preventDefault();
e.s=$.aN.f.serializeObject()
e.cx={f:'accounts',ff:'register',form:e.s};
if($.aN.modeIsEdit){
e.cx.ff='edit';
e.cx.account=$.aN.selected;
var formValues = $.aN.f.serializeObject()
var postData = {
data: formValues
}
$.ccio.cx(e.cx)
$.aN.e.modal('hide')
var webPath = 'registerAdmin'
if($.aN.modeIsEdit){
webPath = 'editAdmin'
postData.account = $.aN.selected
}
$.post('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/accounts/'+webPath,postData,function(data){
console.log(data)
if(data.ok === true){
$.aN.e.modal('hide')
}
})
return false;
});
})
//client side email check
$.aN.e.on('change','[name="mail"]',function(){
var thisVal = $(this).val()
$.each(users,function(n,user){
if($.aN.selected && user.ke !== $.aN.selected.ke && thisVal.toLowerCase() === user.mail.toLowerCase()){
new PNotify({text:"<%=lang['Email address is in use.']%>",type:'error'})
}
})
})
$.aN.e.on('change','[detail]',function(){
e = {}
e.ar = {}
@ -181,7 +197,7 @@ $.aN.e.on('change','[detail]',function(){
try{
e.ar = Object.assign(JSON.parse($.aN.selected.details),{})
}catch(err){
}
}
$.each($.aN.e.find('[detail]'),function(n,v){
@ -194,7 +210,7 @@ $('#edit').change(function(e){
if($('#edit').is(':checked')){
$.aN.modeIsEdit = true
$('#title').text("<%-lang['Edit']%>")
$.aN.e.find('[name="mail"],[name="ke"]').prop('disabled',true)
$.aN.e.find('[name="ke"]').prop('disabled',true)
}else{
$.aN.modeIsEdit = false
$('#title').text("<%-lang['Add New']%>")
@ -224,7 +240,19 @@ $.aC.e.on('click','.delete',function(e){
e.html='Do you want to delete <b>'+e.account.mail+'</b>? You cannot recover this account. Files will remain in the filesystem. If you choose to create an account with the same Group Key it will have the previous events activated in that account.'
$.confirm.body.html(e.html)
$.confirm.click({title:'Delete',class:'btn-danger'},function(){
$.ccio.cx({f:'accounts',ff:'delete',account:e.account})
// $.ccio.cx({f:'accounts',ff:'delete',account:e.account})
$.post('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/accounts/deleteAdmin',{
account : e.account,
// "deleteSubAccounts" : "1",
// "deleteMonitors" : "1",
// "deleteVideos" : "1",
// "deleteEvents" : "1"
},function(data){
console.log(data)
if(data.ok === true){
$.aN.e.modal('hide')
}
})
});
})
$.aC.e.on('click','.permission',function(e){
@ -236,13 +264,15 @@ $.aC.e.on('click','.permission',function(e){
$.aN.e.find('input').val('');
$.each(e.account,function(n,v){
if(n=='pass'){return}
$.aN.e.find('[name="'+n+'"]').val(v).change()
var el = $.aN.e.find('[name="'+n+'"]')
el.val(v)
if(n !== 'mail')el.change()
})
$.each(JSON.parse(e.account.details),function(n,v){
$.aN.e.find('[detail="'+n+'"]').val(v)
})
$('#edit').prop('checked',true).change().parent().addClass('is-checked')
// $.pR.e.modal('show');
})
</script>
</script>

View file

@ -233,14 +233,26 @@
<div class="form-group">
<label><div><span><%-lang.hwaccel%></span></div>
<div><select class="form-control" detail="hwaccel">
<option value="auto"><%-lang.Auto%></option>
<option value="cuvid"><%-lang.cuvid%></option>
<option value="vaapi"><%-lang.vaapi%></option>
<option value="qsv"><%-lang.qsv%></option>
<option value="vdpau"><%-lang.vdpau%></option>
<option value="dxva2"><%-lang.dxva2%></option>
<option value="vda"><%-lang.vda%></option>
<option value="videotoolbox"><%-lang.videotoolbox%></option>
<% if(config.availableHWAccels) {
var methods = {
auto: {label:lang['Auto'],value:'auto'},
drm: {label:lang['drm'],value:'drm'},
cuvid: {label:lang['cuvid'],value:'cuvid'},
vaapi: {label:lang['vaapi'],value:'vaapi'},
qsv: {label:lang['qsv'],value:'qsv'},
vdpau: {label:lang['vdpau'],value:'vdpau'},
dxva2: {label:lang['dxva2'],value:'dxva2'},
vdpau: {label:lang['vdpau'],value:'vdpau'},
videotoolbox: {label:lang['videotoolbox'],value:'videotoolbox'}
}
config.availableHWAccels.forEach(function(availibleMethod){
if(methods[availibleMethod]){
var method = methods[availibleMethod] %>
<option value="<%= method.value %>"><%= method.label %></option>
<% }
})
}
%>
</select></div>
</label>
</div>
@ -1012,6 +1024,11 @@
</select></div>
</label>
</div>
<div class="form-group h_det_discord_input h_det_discord_1">
<label><div><span><%-lang['Allow Next Discord Alert']%></span></div>
<div><input class="form-control" detail="detector_discordbot_timeout" placeholder="10"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Detector Filters']%></span></div>
<div><select class="form-control" detail="use_detector_filters" selector="h_det_fil">
@ -1028,11 +1045,6 @@
</select></div>
</label>
</div>
<div class="form-group h_det_discord_input h_det_discord_1">
<label><div><span><%-lang['Allow Next Discord Alert']%></span></div>
<div><input class="form-control" detail="detector_discordbot_timeout" placeholder="10"></div>
</label>
</div>
<div class="hidden">
<input detail="cords">
<input detail="detector_filters">
@ -1058,6 +1070,21 @@
<div><input class="form-control" detail="detector_sensitivity" placeholder="0.5"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Max Indifference']%></span></div>
<div><input class="form-control" detail="detector_max_sensitivity" placeholder=""></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Trigger Threshold']%></span></div>
<div><input class="form-control" detail="detector_threshold" placeholder="1"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Color Threshold']%></span></div>
<div><input class="form-control" detail="detector_color_threshold" placeholder="9"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Full Frame Detection']%></span></div>
<div><select class="form-control" detail="detector_frame">
@ -1114,7 +1141,7 @@
</div>
</div>
</div>
<div class="form-group-group orange shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector-pythonyolo shinobi-detector_plug" section id="monSectionDetectorObject">
<div class="form-group-group orange shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector-pythonyolo shinobi-detector-yolo shinobi-detector-pythondlib shinobi-detector_plug" section id="monSectionDetectorObject">
<h4><%-lang['Object Detection']%> <small><%-lang['Plugin']%> : <b class="shinobi-detector_name"></b> <b class="shinobi-detector-invert"><%-lang['Not Connected']%></b><b class="shinobi-detector" style="display:none"><%-lang['Connected']%></b></small></h4>
<div class="form-group">
<label><div><span><%-lang['Detect Objects']%></span></div>

View file

@ -14,8 +14,8 @@
<div class="form-group-group orange where">
<h4><span class="cord_name"></span>
<div class="pull-right">
<a class="btn btn-success btn-xs add">&nbsp;<i class="fa fa-plus"></i>&nbsp;</a>
<a class="btn btn-danger btn-xs erase">&nbsp;<i class="fa fa-trash-o"></i>&nbsp;</a>
<a class="btn btn-success btn-xs add">&nbsp;<i class="fa fa-plus"></i>&nbsp;</a>
<a class="btn btn-danger btn-xs erase">&nbsp;<i class="fa fa-trash-o"></i>&nbsp;</a>
</div>
</h4>
<div class="form-group">
@ -24,19 +24,35 @@
<div><select id="regions_list" class="form-control"></select></div>
</label>
</div>
<div class="row">
<div class="form-group col-md-12">
<label>
<div><span><%-lang['Region Name']%></span></div>
<div><input class="form-control" name="name"></div>
</label>
</div>
<div class="form-group col-md-12">
<label>
<div><span><%-lang.Indifference%></span></div>
<div><input class="form-control" name="sensitivity"></div>
</label>
</div>
<div class="form-group">
<label>
<div><span><%-lang['Region Name']%></span></div>
<div><input class="form-control" name="name"></div>
</label>
</div>
<div class="form-group">
<label>
<div><span><%-lang['Indifference']%></span></div>
<div><input class="form-control" name="sensitivity"></div>
</label>
</div>
<div class="form-group">
<label>
<div><span><%-lang['Max Indifference']%></span></div>
<div><input class="form-control" name="max_sensitivity"></div>
</label>
</div>
<div class="form-group">
<label>
<div><span><%-lang['Trigger Threshold']%></span></div>
<div><input class="form-control" name="threshold"></div>
</label>
</div>
<div class="form-group">
<label>
<div><span><%-lang['Color Threshold']%></span></div>
<div><input class="form-control" name="color_threshold"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang.Points%></span></div></label>
@ -64,4 +80,4 @@
</div>
</form>
</div>
</div>
</div>

View file

@ -30,14 +30,14 @@
<label><div><span><%-lang['Enabled']%></span></div>
<div><select class="form-control" detail="factorAuth">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang.Email%></span></div>
<div><select class="form-control" detail="factor_mail">
<option value="1" selected><%-lang.Yes%></option>
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
@ -46,7 +46,7 @@
<label><div><span><%-lang.Discord%></span></div>
<div><select class="form-control" detail="factor_discord">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
@ -144,7 +144,7 @@
<label><div><span><%-lang['Popout Monitor on Event']%></span></div>
<div><select class="form-control" detail="event_mon_pop">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
@ -194,11 +194,34 @@
<div class="form-group">
<label><div><span><%-lang.Autosave%></span></div>
<div><select class="form-control" detail="webdav_save">
<option value="0"><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Save Links to Database']%></span></div>
<div><select class="form-control" detail="webdav_log" selector="h_webdavsld">
<option value="0"><%-lang.No%></option>
<option value="1" selected><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="h_webdavsld_input h_webdavsld_1">
<div class="form-group">
<label><div><span><%-lang['Use Max Storage Amount']%></span></div>
<div><select class="form-control" detail="use_webdav_size_limit" selector="h_webdavzl">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group h_webdavzl_input h_webdavzl_1">
<label><div><span><%-lang['Max Storage Amount']%></span></div>
<div><input class="form-control" placeholder="10000" detail="webdav_size_limit"></div>
</label>
</div>
</div>
<div class="form-group">
<label><div><span><%-lang['Save Directory']%></span></div>
<div><input class="form-control" placeholder="/" detail="webdav_dir"></div>
@ -246,26 +269,41 @@
<option value="eu-west-1">EU (Ireland)</option>
<option value="eu-west-2">EU (London)</option>
<option value="eu-west-3">EU (Paris)</option>
<option value="sa-east-1">South America (São Paulo)</option>
<option value="sa-east-1">South America (São Paulo)</option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang.Autosave%></span></div>
<div><select class="form-control" detail="aws_s3_save">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Save Links to Database']%></span></div>
<div><select class="form-control" detail="aws_s3_log">
<option value="0"><%-lang.No%></option>
<option value="1" selected><%-lang.Yes%></option>
<div><select class="form-control" detail="aws_s3_log" selector="h_s3sld">
<option value="0"><%-lang.No%></option>
<option value="1" selected><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="h_s3sld_input h_s3sld_1">
<div class="form-group">
<label><div><span><%-lang['Use Max Storage Amount']%></span></div>
<div><select class="form-control" detail="use_aws_s3_size_limit" selector="h_s3zl">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group h_s3zl_input h_s3zl_1">
<label><div><span><%-lang['Max Storage Amount']%></span></div>
<div><input class="form-control" placeholder="10000" detail="aws_s3_size_limit"></div>
</label>
</div>
</div>
<div class="form-group">
<label><div><span><%-lang['Save Directory']%></span></div>
<div><input class="form-control" placeholder="" detail="aws_s3_dir"></div>
@ -273,14 +311,72 @@
</div>
</div>
<% } %>
<% if(details.use_bb_b2!=='0'){ %>
<div class="form-group-group forestgreen">
<h4><%-lang['Backblaze B2']%></h4>
<div class="form-group">
<label><div><span><%-lang.Bucket%></span></div>
<div><input class="form-control" detail="bb_b2_bucket" placeholder="Example : slippery-seal"></div>
</label>
</div>
<div class="row">
<div class="form-group col-md-12">
<label><div><span><%-lang.accountId%></span></div>
<div><input class="form-control" detail="bb_b2_accountId"></div>
</label>
</div>
<div class="form-group col-md-12">
<label><div><span><%-lang.applicationKey%></span></div>
<div><input class="form-control" type="password" detail="bb_b2_applicationKey"></div>
</label>
</div>
</div>
<div class="form-group">
<label><div><span><%-lang.Autosave%></span></div>
<div><select class="form-control" detail="bb_b2_save">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Save Links to Database']%></span></div>
<div><select class="form-control" detail="bb_b2_log" selector="h_b2sld">
<option value="0"><%-lang.No%></option>
<option value="1" selected><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="h_b2sld_input h_b2sld_1">
<div class="form-group">
<label><div><span><%-lang['Use Max Storage Amount']%></span></div>
<div><select class="form-control" detail="use_bb_b2_size_limit" selector="h_b2zl">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group h_b2zl_input h_b2zl_1">
<label><div><span><%-lang['Max Storage Amount']%></span></div>
<div><input class="form-control" placeholder="10000" detail="bb_b2_size_limit"></div>
</label>
</div>
</div>
<div class="form-group">
<label><div><span><%-lang['Save Directory']%></span></div>
<div><input class="form-control" placeholder="" detail="bb_b2_dir"></div>
</label>
</div>
</div>
<% } %>
<% if(details.use_discordbot!=='0'){ %>
<div class="form-group-group forestgreen">
<h4><%-lang['Discord Bot']%></h4>
<div class="form-group">
<label><div><span><%-lang.Enabled%></span></div>
<div><select class="form-control" detail="discordbot" selector="u_discord_bot">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
@ -304,8 +400,8 @@
<div class="form-group">
<label><div><span><%-lang.Enable%></span></div>
<div><select class="form-control" detail="ldap_enable" selector="ldap_i">
<option value="0"><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
@ -349,7 +445,7 @@
<div class="form-group">
<label><div><span><%-lang['Force Monitors Per Row']%></span></div>
<div><select class="form-control" localStorage="montage_use" selector="st_force_mon_rows">
<option value="1"><%-lang.Yes%></option>
<option value="1"><%-lang.Yes%></option>
<option value="0" selected><%-lang.No%></option>
</select></div>
</label>
@ -362,15 +458,15 @@
<div class="form-group">
<label><div><span><%-lang['Browser Console Log']%></span></div>
<div><select class="form-control" localStorage="browserLog">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Get Logs to Client']%></span></div>
<div><select class="form-control" detail="get_server_log">
<option value="1" selected><%-lang.Yes%></option>
<option value="1" selected><%-lang.Yes%></option>
<option value="0"><%-lang.No%></option>
</select></div>
</label>
@ -398,4 +494,4 @@
</div>
</form>
</div>
</div>
</div>

View file

@ -1,5 +1,5 @@
<!--Videos List Window-->
<div class="modal full fade" id="videos_viewer" role="dialog" aria-labelledby="videos_viewerLabel" aria-hidden="true">
<div class="modal full fade dark" id="videos_viewer" role="dialog" aria-labelledby="videos_viewerLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content">
<div class="modal-header">
@ -21,6 +21,16 @@
<div><input id="videos_viewer_limit" value="100" class="form-control" placeholder="0 for No Limit" localStorage="videosViewerLimit" /></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Video Set']%></span></div>
<div>
<select class="form-control" id="videos_viewer_set">
<option value="local">Local</option>
<option value="cloud">Cloud</option>
</select>
</div>
</label>
</div>
<div id="videos_viewer_preview" class="text-center"></div>
</div>
<div class="col-md-9">
@ -31,8 +41,8 @@
<div class="modal-footer">
<div class="row">
<div class="col-md-4 text-left">
<a class="btn btn-danger delete_selected"><i class="fa fa-trash-o"></i> &nbsp; <%-lang['Delete']%></a>
<a class="btn btn-default export_selected"><i class="fa fa-folder-o"></i> &nbsp; <%-lang['Zip and Download']%></a>
<a class="btn btn-danger delete_selected"><i class="fa fa-trash-o"></i> &nbsp; <%-lang['Delete']%></a>
<a class="btn btn-default export_selected"><i class="fa fa-folder-o"></i> &nbsp; <%-lang['Zip and Download']%></a>
</div>
<div class="col-md-4">
<div class="text-center" id="videos_viewer_pages"></div>
@ -66,4 +76,4 @@
</div>
</div>
</div>
</div>
</div>

View file

@ -175,7 +175,7 @@ $(document).ready(function(){
$.shinobi.mon[d.id].Base64.disconnect()
}
if($.shinobi.mon[d.id].Poseidon){
$.shinobi.mon[d.id].Poseidon.destroy()
$.shinobi.mon[d.id].Poseidon.stop()
}
if ($.shinobi.mon[d.id].h265Player) {
$.shinobi.mon[d.id].h265Player.stop()
@ -253,13 +253,19 @@ $(document).ready(function(){
var stream = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element');
if($.shinobi.mon[d.id].details.stream_flv_type==='ws'){
var createPoseidon = function(){
var onPoseidonError = function(){
// setTimeout(function(){
// $.ccio.cx({f:'monitor',ff:'watch_on',id:d.id},user)
// },2000)
}
$.shinobi.mon[d.id].Poseidon = new Poseidon({
video: stream[0],
auth_token:'<%=data.auth%>',
ke:d.ke,
uid:'<%=data.uid%>',
id:d.id,
url: '<%=data.url%>'
url: '<%=data.url%>',
onError : onPoseidonError
});
$.shinobi.mon[d.id].Poseidon.start();
$.shinobi.mon[d.id].Poseidon._socket.on('data',function(res){

View file

@ -1,6 +1,7 @@
<% var details=JSON.parse($user.details) %>
<% var details = JSON.parse($user.details) %>
<% include blocks/header %>
<script>var $user=<%- JSON.stringify($user) %>;</script>
<script>var $user = <%- JSON.stringify($user) %>;</script>
<script>var lang = <%- JSON.stringify(lang) %>;</script>
<link rel="stylesheet" href="<%-originalURL%>libs/css/pnotify.custom.min.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/vbox.css">
<link rel="stylesheet" href="<%-originalURL%>libs/css/daterangepicker.css">
@ -194,5 +195,6 @@
<script src="<%-originalURL%>libs/js/lodash.min.js"></script>
<script src="<%-originalURL%>libs/js/gridstack.min.js"></script>
<script src="<%-originalURL%>libs/js/gridstack.jQueryUI.min.js"></script>
<script src="<%-originalURL%>libs/js/basic.js"></script>
<script><% include ../libs/js/main.dash2.js %></script>
<% include blocks/help.ejs %>

View file

@ -22,6 +22,11 @@
<div class="col-lg-12">
<form id="login-form" method="post" style="display: block;">
<input type="hidden" name="machineID" id="machineID" value="">
<% var message,timeLeft;if(message){ %>
<div class="form-group text-center monospace">
<%= message %>
</div>
<% } %>
<div class="form-group">
<input type="text" name="mail" id="email" tabindex="1" class="monospace form-control" placeholder="Email" value="">
</div>

View file

@ -14,6 +14,7 @@
<link href="<%-originalURL%>libs/css/main.dash2.css" rel="stylesheet" />
<link rel="stylesheet" href="<%-originalURL%>libs/css/pnotify.custom.min.css">
<link href="<%-originalURL%>libs/css/now-ui-kit.css?v=1.1.0" rel="stylesheet" />
<link href="<%-originalURL%>libs/css/super-page.css" rel="stylesheet" />
<script src="<%-originalURL%>libs/js/jquery.min.js"></script>
<script src="<%-originalURL%>libs/js/jquery.serialize.js"></script>
<script src="<%-originalURL%>libs/js/pnotify.custom.min.js"></script>
@ -31,7 +32,7 @@
<nav id="main-nav" class="navbar navbar-expand-lg bg-primary fixed-top" color-on-scroll="400">
<div class="container">
<div class="navbar-translate">
<a tabindex="1" class="navbar-brand logout" href="/" data-placement="bottom" target="_blank">
<a tabindex="1" class="navbar-brand logout" style="outline:0" href="/" data-placement="bottom">
<%-lang.superAdminTitle%>
</a>
<button class="navbar-toggler navbar-toggler" type="button" data-toggle="collapse" data-target="#navigation" aria-controls="navigation-index" aria-expanded="false" aria-label="Toggle navigation">
@ -69,6 +70,9 @@
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#system" role="tab">Controls and Logs</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#changeSuperPreferences" role="tab">Preferences</a>
</li>
</ul>
<div class="card-body">
<!-- Tab panes -->
@ -96,15 +100,10 @@
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link add" href="javascript:$.conf.e.submit()">
<a class="nav-link submit">
<p><i class="fa fa-check"></i> <%-lang.Save%></p>
</a>
</li>
<li class="nav-item">
<a class="nav-link add" href="javascript:$.conf.e.submit()">
<p class="msg"></p>
</a>
</li>
</ul>
</div>
</div>
@ -117,7 +116,7 @@
</div>
</form>
</div>
<div class="tab-pane" id="system" role="tabpanel">
<div class="tab-pane text-left" id="system" role="tabpanel">
<nav class="navbar navbar-rounded navbar-expand-lg bg-primary">
<div class="container">
<div class="collapse navbar-collapse">
@ -157,6 +156,9 @@
<table class="table table-striped"></table>
</div>
</div>
<div class="tab-pane text-left" id="changeSuperPreferences" role="tabpanel">
<% include blocks/changeSuperPreferences.ejs %>
</div>
</div>
</div>
</div>
@ -209,26 +211,33 @@ switch($user.lang){
$.ccio.ws=io(location.origin);
$.ccio.cx=function(x){return $.ccio.ws.emit('super',x)}
$.ccio.ws.on('connect',function(d){
$.ccio.cx({f:'init',mail:$user.mail,pass:$user.pass});
$.ccio.cx({f:'init',mail:$user.mail,pass:$user.pass})
})
$.ccio.ws.on('f',function(d){
switch(d.f){
case'init_success':
$user.sessionKey = d.superSessionKey
break;
case'log':
$.ccio.tm(4,d.log,'#logs table')
break;
case'save_configuration':
d.msg = 'Saved, Restart Shinobi to apply changes.'
break;
case'save_preferences':
d.msg = 'Saved Preferences'
break;
case'edit_account':
d.msg='Account Edited';
$.each(d.form,function(n,v){
$.ccio.accounts[d.ke][n]=v;
});
$.ccio.accounts[d.ke][n]=v
})
$('[ke="'+d.ke+'"] .mail').text(d.form.mail)
break;
case'add_account':
d.msg='Account Created';
$.ccio.tm(0,d,'#accounts table')
$.aN.selected=$.ccio.accounts[d.ke];
$.aN.selected = $.ccio.accounts[d.ke]
break;
case'delete_account':
$('#accounts table tr[ke="'+d.ke+'"]').remove()
@ -328,10 +337,10 @@ $.logs={e:$('#logs')}
var stringedConfig = JSON.stringify(plainConfig)
%>
var config = <%- JSON.stringify(plainConfig) || [] %>
$.conf={e:$('#conf_json')};
$.conf={e:$('#config')};
$.conf.f = $.conf.e.find('form')
$.conf.configForHumans=$('#configForHumans')
$.conf.draw=$.conf.e.find('[name="json"]')
$.conf.msg=$.conf.e.find('.msg')
$.conf.valid=1;
$.conf.jsonToFields = function(){
// var tmp = ''
@ -358,7 +367,10 @@ $.conf.draw.keyup(function(){
}
new PNotify({text:msg,type:color})
})
$.conf.e.submit(function(e){
$.conf.e.find('.submit').click(function(e){
$.conf.f.submit()
})
$.conf.f.submit(function(e){
e.preventDefault()
if($.conf.valid===1){
$.confirm.e.modal('show');
@ -367,7 +379,12 @@ $.conf.e.submit(function(e){
e.html+='<pre>'+JSON.stringify($.parseJSON($.conf.draw.val()),null,3)+'</pre>'
$.confirm.body.html(e.html)
$.confirm.click({title:'Save',class:'btn-success'},function(){
$.ccio.cx({f:'system',ff:'configure',data:$.parseJSON($.conf.draw.val())})
$.post('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/system/configure',{
data: $.conf.draw.val()
},function(data){
// console.log(data)
})
// $.ccio.cx({f:'system',ff:'configure',data:$.parseJSON($.conf.draw.val())})
});
}else{
new PNotify({text:'Invalid JSON Syntax, Cannot Save.',type:'error'})
@ -418,7 +435,7 @@ $.conf.e.on('keyup','.config-row input',function(){
$.conf.draw.val(JSON.stringify(newConfig,null,3))
console.log(newConfig)
})
$.conf.e.ready(function(){
$.conf.f.ready(function(){
$.conf.jsonToFields()
})
//sys controls
@ -431,7 +448,10 @@ $.system.e.find('[system]').click(function(e){
e.html='Do you want to delete these logs? User logs will <b>not</b> be deleted.'
$.confirm.body.html(e.html)
$.confirm.click({title:'Delete',class:'btn-danger'},function(){
$.ccio.cx({f:'logs',ff:'delete',ke:'$'})
// $.ccio.cx({f:'logs',ff:'delete',ke:'$'})
$.get('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/logs/delete',function(data){
console.log(data)
})
$.logs.e.find('table').empty()
});
break;
@ -440,8 +460,11 @@ $.system.e.find('[system]').click(function(e){
$.confirm.title.html('Update Shinobi?')
$.confirm.body.html('Updating Shinobi means overwriting files. If you have modified any files yourself you should update Shinobi manually.')
$.confirm.click({title:'Update',class:'btn-danger'},function(){
$.ccio.cx({f:'system',ff:'update'})
});
// $.ccio.cx({f:'system',ff:'update'})
$.get('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/system/update',function(data){
console.log(data)
})
})
break;
}
})
@ -466,9 +489,31 @@ $.system.e.find('[restart]').click(function(e){
$.confirm.title.html('Restart?')
$.confirm.body.html(e.html)
$.confirm.click({title:'Restart',class:'btn-danger'},function(){
$.ccio.cx({f:'system',ff:'restart',target:e.target})
// $.ccio.cx({f:'system',ff:'restart',target:e.target})
$.get('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/system/restart/'+encodeURIComponent(e.target),function(data){
console.log(data)
})
});
})
$.changeSuperPreferences = {
window : $('#changeSuperPreferences')
}
$.changeSuperPreferences.form = $.changeSuperPreferences.window.find('form')
$.changeSuperPreferences.form.find('.submit').click(function(){
$.changeSuperPreferences.form.submit()
})
$.changeSuperPreferences.form.submit(function(e){
e.preventDefault()
var formValues = $(this).serializeObject()
// $.ccio.cx({f:'accounts',ff:'saveSuper',form:formValues})
$.post('<%=originalURL%><%=config.webPaths.superApiPrefix%>'+$user.sessionKey+'/accounts/saveSettings',{
data: JSON.stringify(formValues)
},function(data){
console.log(data)
})
return false
})
////
$(document).ready(function(){
$.each(<%-JSON.stringify(Logs)%>,function(n,v){
$.ccio.tm(4,v,'#logs table')
@ -478,7 +523,7 @@ $(document).ready(function(){
$('body')
.on('click','.logout',function(e){
localStorage.removeItem('ShinobiLogin_'+location.host);location.href='/';
})
})
</script>
<% include blocks/mainpermissions.ejs %>
</html>
</html>