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:
parent
04011678fb
commit
b7d08eb500
67 changed files with 11651 additions and 8452 deletions
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"port": 8080,
|
||||
"passwordType": "sha256",
|
||||
"addStorage": [
|
||||
{"name":"second","path":"__DIR__/videos2"}
|
||||
],
|
||||
|
|
181
cron.js
181
cron.js
|
@ -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')
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
191
libs/auth.js
Normal 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&¶ms.username!==''&¶ms.password&¶ms.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
227
libs/basic.js
Normal 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
217
libs/childNode.js
Normal 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
555
libs/cloudUploaders.js
Normal 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
55
libs/config.js
Normal 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
204
libs/detector.js
Normal 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
384
libs/events.js
Normal 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'&¤tConfig.detector_trigger==='1'&¤tConfig.detector_record_method==='sip'){
|
||||
s.createEventBasedRecording(d)
|
||||
}else if(filter.record && d.mon.mode!=='stop'&¤tConfig.detector_trigger=='1'&¤tConfig.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&¤tConfig.detector_trigger_record_fps!==''&¤tConfig.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
58
libs/extenders.js
Normal 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
910
libs/ffmpeg.js
Normal 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
49
libs/folders.js
Normal 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
76
libs/health.js
Normal 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
61
libs/language.js
Normal 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
1321
libs/monitor.js
Normal file
File diff suppressed because it is too large
Load diff
318
libs/notification.js
Normal file
318
libs/notification.js
Normal 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
105
libs/plugins.js
Normal 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
33
libs/process.js
Normal 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
1433
libs/socketio.js
Normal file
File diff suppressed because it is too large
Load diff
88
libs/sql.js
Normal file
88
libs/sql.js
Normal 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
165
libs/startup.js
Normal 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
235
libs/user.js
Normal 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
329
libs/videos.js
Normal 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
35
libs/webServer.js
Normal 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
428
libs/webServerAdminPaths.js
Normal 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
1843
libs/webServerPaths.js
Normal file
File diff suppressed because it is too large
Load diff
358
libs/webServerStreamPaths.js
Normal file
358
libs/webServerStreamPaths.js
Normal 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
411
libs/webServerSuperPaths.js
Normal 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)
|
||||
})
|
||||
}
|
|
@ -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
77
test.js
Normal 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
130
test/run.js
Normal 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
7
test/testAdminUser.json
Normal 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\"}"
|
||||
}
|
11
test/testAdminUserDelete.json
Normal file
11
test/testAdminUserDelete.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"account": {
|
||||
"mail": "[LOGIN ADDRESS]",
|
||||
"ke": "[GROUP KEY]",
|
||||
"uid": "[USER ID]"
|
||||
},
|
||||
"deleteSubAccounts": "1",
|
||||
"deleteMonitors": "1",
|
||||
"deleteVideos": "1",
|
||||
"deleteEvents": "1"
|
||||
}
|
31
test/testAdminUserEdit.json
Normal file
31
test/testAdminUserEdit.json
Normal 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]"
|
||||
}
|
||||
}
|
26
test/testAdminUserRegister.json
Normal file
26
test/testAdminUserRegister.json
Normal 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
15
test/testApiAdd.json
Normal 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
5
test/testApiDelete.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": {
|
||||
"code": "[API KEY]"
|
||||
}
|
||||
}
|
1
test/testMonitor-WatchOnly.json
Normal file
1
test/testMonitor-WatchOnly.json
Normal file
File diff suppressed because one or more lines are too long
4
test/testSubUserDelete.json
Normal file
4
test/testSubUserDelete.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"uid" : "[SUB-ACCOUNT USER ID]",
|
||||
"mail" : "[SUB-ACCOUNT LOGIN ADDRESS]"
|
||||
}
|
10
test/testSubUserEdit.json
Normal file
10
test/testSubUserEdit.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"uid": "[SUB-ACCOUNT USER ID]",
|
||||
"mail": "[SUB-ACCOUNT LOGIN ADDRESS]",
|
||||
"data": {
|
||||
"details": {
|
||||
"sub": "1",
|
||||
"allmonitors": "1"
|
||||
}
|
||||
}
|
||||
}
|
7
test/testSubUserRegister.json
Normal file
7
test/testSubUserRegister.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"data": {
|
||||
"mail": "[SUB-ACCOUNT LOGIN ADDRESS]",
|
||||
"pass": "[SUB-ACCOUNT PASSWORD]",
|
||||
"password_again": "[SUB-ACCOUNT PASSWORD]"
|
||||
}
|
||||
}
|
24
web/libs/css/admin-page.css
Normal file
24
web/libs/css/admin-page.css
Normal file
File diff suppressed because one or more lines are too long
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
6
web/libs/css/super-page.css
Normal file
6
web/libs/css/super-page.css
Normal 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
36
web/libs/js/basic.js
Normal 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
11
web/libs/js/extra.js
Normal 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
|
@ -4192,4 +4192,4 @@
|
|||
|
||||
return _moment;
|
||||
|
||||
}));
|
||||
}));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -92,4 +92,4 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
39
web/pages/blocks/changeSuperPreferences.ejs
Normal file
39
web/pages/blocks/changeSuperPreferences.ejs
Normal 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>
|
|
@ -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"> <i class="fa fa-plus"></i> </a>
|
||||
<a class="btn btn-danger btn-xs remove"> <i class="fa fa-trash-o"></i> </a>
|
||||
<a class="btn btn-success btn-xs add"> <i class="fa fa-plus"></i> </a>
|
||||
<a class="btn btn-danger btn-xs remove"> <i class="fa fa-trash-o"></i> </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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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,"\\'")
|
||||
}%>
|
||||
}%>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"> <i class="fa fa-plus"></i> </a>
|
||||
<a class="btn btn-danger btn-xs erase"> <i class="fa fa-trash-o"></i> </a>
|
||||
<a class="btn btn-success btn-xs add"> <i class="fa fa-plus"></i> </a>
|
||||
<a class="btn btn-danger btn-xs erase"> <i class="fa fa-trash-o"></i> </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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> <%-lang['Delete']%></a>
|
||||
<a class="btn btn-default export_selected"><i class="fa fa-folder-o"></i> <%-lang['Zip and Download']%></a>
|
||||
<a class="btn btn-danger delete_selected"><i class="fa fa-trash-o"></i> <%-lang['Delete']%></a>
|
||||
<a class="btn btn-default export_selected"><i class="fa fa-folder-o"></i> <%-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>
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue