diff --git a/LICENSE.md b/LICENSE.md index 9e331858..f6160f2d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -102,6 +102,7 @@ Courthouse Vancouver Robson Square Node.js - https://nodejs.org/en/ MariaDB - https://mariadb.org/ FFmpeg - https://www.ffmpeg.org/ + request - https://www.npmjs.com/package/request Express (npm) - https://expressjs.com/ https://www.npmjs.com/package/express EJS (npm) - http://ejs.co/ https://www.npmjs.com/package/ejs pam-diff (npm) (Motion Detector) - https://github.com/kevinGodell/pam-diff diff --git a/camera.js b/camera.js index 2f5be48c..8be86382 100644 --- a/camera.js +++ b/camera.js @@ -116,7 +116,6 @@ 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.strictDatabase===undefined){config.strictDatabase=false} if(config.pipeAddition===undefined){config.pipeAddition=7}else{config.pipeAddition=parseInt(config.pipeAddition)} //Web Paths if(config.webPaths===undefined){config.webPaths={}} @@ -220,42 +219,24 @@ if(databaseOptions.client === 'sqlite3' && databaseOptions.connection.filename = databaseOptions.connection.filename = __dirname+"/shinobi.sqlite" } s.databaseEngine = knex(databaseOptions) -s.sqlDate = function(value){ - var dateQueryFunction = '' - if(databaseOptions.client === 'sqlite3'){ - value = value.toLowerCase() - if (value.slice(-1) !== 's') { - value = value+'s' - } - dateQueryFunction = "datetime('now', '-"+value+"')" - }else{ - value = value.toUpperCase() - if (value.slice(-1) === 'S') { - value = value.slice(0, -1); - } - dateQueryFunction = "DATE_SUB(NOW(), INTERVAL "+value+")" - } - return dateQueryFunction -} 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 } } }) @@ -264,7 +245,11 @@ s.mergeQueryValues = function(query,values){ } return newQuery } -s.sqlQuery = function(query,values,onMoveOn,hideLog){ +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; @@ -272,24 +257,53 @@ s.sqlQuery = function(query,values,onMoveOn,hideLog){ } if(!onMoveOn){onMoveOn=function(){}} var mergedQuery = s.mergeQueryValues(query,values) - return s.databaseEngine.raw(query,values) - .asCallback(function(err,r){ - if(err&&config.databaseLogs){ - s.systemLog('s.sqlQuery QUERY',query) - s.systemLog('s.sqlQuery ERROR',err) + 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 && typeof onMoveOn === 'function'){ - switch(databaseOptions.client){ - case'sqlite3': - if(!r)r=[] - break; - default: - if(r)r=r[0] - break; + onMoveOn(err,r) + } + }) +} +//discord bot +if(config.discordBot === true){ + try{ + var Discord = require("discord.js") + s.sendDiscordAlert = function(data,files,groupKey){ + if(!data)data = {}; + var sendBody = Object.assign({ + color: 3447003, + title: 'Alert from Shinobi', + description: "", + fields: [], + timestamp: new Date(), + footer: { + icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png", + text: "Shinobi Systems" } - onMoveOn(err,r) - } - }) + },data) + s.group[groupKey].discordBot.channels.get(s.group[groupKey].init.discordbot_channel).send({ + embed: sendBody, + files: files + }) + } + }catch(err){ + console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.') + s.sendDiscordAlert = function(){} + } } //kill any ffmpeg running s.ffmpegKill=function(){ @@ -345,7 +359,7 @@ s.txWithSubPermissions = function(z,y,permissionChoices){ var valid=0 var checked=permissionChoices.length permissionChoices.forEach(function(b){ - if(user.details[b].indexOf(z.mid)!==-1){ + if(user.details[b] && user.details[b].indexOf(z.mid)!==-1){ ++valid } }) @@ -449,6 +463,21 @@ s.getFunctionParamNames = function(func) { result = []; return result; } +s.getDetectorStreams = function(monitor){ + var pathDir = s.dir.streams+monitor.ke+'/'+monitor.id+'/' + var streamDirItems = fs.readdirSync(pathDir) + var items = [] + streamDirItems.forEach(function(filename){ + if(filename.indexOf('detectorStream') > -1 && filename.indexOf('.m3u8') === -1){ + try{ + items.push(pathDir+filename) + }catch(err){ + console.log(err) + } + } + }) + return items +} s.createPamDiffRegionArray = function(regions,globalSensitivity,fullFrame){ var pamDiffCompliantArray = [], arrayForOtherStuff = [], @@ -494,7 +523,7 @@ s.getRequest = function(url,callback){ s.kill=function(x,e,p){ if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].spawn !== undefined){ if(s.group[e.ke].mon[e.id].spawn){ - s.group[e.ke].mon[e.mid].allowStdinWrite = false + s.group[e.ke].mon[e.id].allowStdinWrite = false s.txToDashcamUsers({ f : 'disable_stream', ke : e.ke, @@ -549,7 +578,7 @@ s.log=function(e,x){ // s.systemLog('s.log : ',{f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRP_'+e.ke) } //system log -s.systemLog=function(q,w,e){ +s.systemLog = function(q,w,e){ if(!w){w=''} if(!e){e=''} if(config.systemLog===true){ @@ -560,6 +589,17 @@ s.systemLog=function(q,w,e){ return console.log(s.timeObject().format(),q,w,e) } } +//system log +s.debugLog = function(q,w,e){ + if(!w){w = ''} + if(!e){e = ''} + if(config.debugLog === true){ + console.log(s.timeObject().format(),q,w,e) + if(config.debugLogVerbose === true){ + console.log(new Error()) + } + } +} //SSL options if(config.ssl&&config.ssl.key&&config.ssl.cert){ config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8') @@ -659,12 +699,7 @@ s.init=function(x,e,k,fn){ switch(x){ case 0://init camera if(!s.group[e.ke]){s.group[e.ke]={}}; - if(!s.group[e.ke].fileBin){s.group[e.ke].fileBin={}}; if(!s.group[e.ke].mon){s.group[e.ke].mon={}} - if(!s.group[e.ke].sizeChangeQueue){s.group[e.ke].sizeChangeQueue=[]} - if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]} - 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].mon[e.mid]){s.group[e.ke].mon[e.mid]={}} if(!s.group[e.ke].mon[e.mid].streamIn){s.group[e.ke].mon[e.mid].streamIn={}}; if(!s.group[e.ke].mon[e.mid].emitterChannel){s.group[e.ke].mon[e.mid].emitterChannel={}}; @@ -679,7 +714,6 @@ s.init=function(x,e,k,fn){ if(!s.group[e.ke].mon[e.mid].started){s.group[e.ke].mon[e.mid].started=0}; if(s.group[e.ke].mon[e.mid].delete){clearTimeout(s.group[e.ke].mon[e.mid].delete)} if(!s.group[e.ke].mon_conf){s.group[e.ke].mon_conf={}} - s.init('apps',e) break; case'group': if(!s.group[e.ke]){ @@ -688,6 +722,11 @@ s.init=function(x,e,k,fn){ 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].sizeChangeQueue){s.group[e.ke].sizeChangeQueue=[]} + if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]} + if(!s.group[e.ke].users){s.group[e.ke].users={}} + if(!s.group[e.ke].dashcamUsers){s.group[e.ke].dashcamUsers={}} 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; @@ -723,6 +762,18 @@ s.init=function(x,e,k,fn){ ar.webdav_pass ); } + //discordbot + if(!s.group[e.ke].discordBot && + config.discordBot === true && + ar.discordbot === '1' && + ar.discordbot_token !== '' + ){ + s.group[e.ke].discordBot = new Discord.Client() + s.group[e.ke].discordBot.on('ready', () => { + console.log(`${r.mail} : Discord Bot Logged in as ${s.group[e.ke].discordBot.user.tag}!`) + }) + s.group[e.ke].discordBot.login(ar.discordbot_token) + } Object.keys(ar).forEach(function(v){ s.group[e.ke].init[v]=ar[v] }) @@ -918,15 +969,12 @@ s.video=function(x,e,k){ time = e.time } time = new Date(time) - if(config.databaseType !== 'sqlite'){ - time = moment(time).format('YYYY-MM-DD HH:mm:ss') - } e.save=[e.id,e.ke,time]; - s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=? LIMIT 1',e.save,function(err,r){ + s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){ if(r&&r[0]){ r=r[0] var dir=s.video('getDir',r) - s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=? LIMIT 1',e.save,function(){ + s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(){ fs.stat(dir+filename,function(err,file){ if(err){ s.systemLog('File Delete Error : '+e.ke+' : '+' : '+e.mid,err) @@ -1093,7 +1141,7 @@ s.video=function(x,e,k){ if(!evs)return console.log(err) evs.forEach(function(ev){ ev.dir=s.video('getDir',ev)+s.formattedTime(ev.time)+'.'+ev.ext; - k.del.push('(mid=? AND time=?)'); + k.del.push('(mid=? AND `time`=?)'); k.ar.push(ev.mid),k.ar.push(ev.time); s.file('delete',ev.dir); s.init('diskUsedSet',e,-(ev.size/1000000)) @@ -1238,10 +1286,6 @@ s.video=function(x,e,k){ k.details.dir = e.details.dir } if(config.useUTC === true)k.details.isUTC = config.useUTC; - if(config.strictDatabase === true){ - e.startTime = s.formattedTime(e.startTime) - e.endTime = s.formattedTime(e.endTime) - } var save = [ e.mid, e.ke, @@ -2442,9 +2486,7 @@ s.camera=function(x,e,cn,tx){ e.details.fatal_max = parseFloat(e.details.fatal_max) } var errorFatal = function(errorMessage){ - if(config.debugSystem === true){ - console.log(errorMessage,(new Error()).stack) - } + s.debugLog(errorMessage) clearTimeout(s.group[e.ke].mon[e.id].err_fatal_timeout); ++errorFatalCount; if(s.group[e.ke].mon[e.id].started===1){ @@ -2518,7 +2560,7 @@ s.camera=function(x,e,cn,tx){ cutoff *= 100 } s.group[e.ke].mon[e.id].checker=setTimeout(function(){ - if(s.group[e.ke].mon[e.id].started === 1){ + if(s.group[e.ke].mon[e.id].started === 1 && s.group[e.ke].mon_conf[e.id].mode === 'record'){ launchMonitorProcesses(); s.init('monitorStatus',{id:e.id,ke:e.ke,status:lang.Restarting}); s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang['Restarting Process']}}); @@ -3193,8 +3235,71 @@ s.camera=function(x,e,cn,tx){ }).end(); } + var screenshotName = 'Motion_'+(d.mon.name.replace(/[^\w\s]/gi,''))+'_'+d.id+'_'+d.ke+'_'+s.formattedTime() + var screenshotBuffer = null + var detectorStreamBuffers = null + + //discord bot + if(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.sendDiscordAlert({ + author: { + name: s.group[d.ke].mon_conf[d.id].name, + icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png" + }, + title: lang.Event+' - '+screenshotName, + description: lang.EventText1+' '+s.timeObject(new Date).format(), + fields: [], + timestamp: new Date(), + footer: { + icon_url: "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png", + text: "Shinobi Systems" + } + },files,d.ke) + } + if(!detectorStreamBuffers){ + detectorStreamBuffers = s.getDetectorStreams(d) + } + detectorStreamBuffers.slice(detectorStreamBuffers.length - 2,detectorStreamBuffers.length).forEach(function(filepath,n){ + files.push({ + attachment: filepath, + name: 'Video Clip '+n+'.ts' + }) + }) + if(screenshotBuffer){ + sendAlert() + }else if(d.mon.details.snap === '1'){ + fs.readFile(s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg',function(err, frame){ + if(err){ + s.systemLog(lang.EventText2+' '+d.ke+' '+d.id,err) + }else{ + screenshotBuffer = frame + files.push({ + attachment: screenshotBuffer, + name: screenshotName+'.jpg' + }) + } + sendAlert() + }) + }else{ + sendAlert() + } + } //mailer - if(config.mail&&!s.group[d.ke].mon[d.id].detector_mail&&d.mon.details.detector_mail==='1'){ + if(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 @@ -3209,35 +3314,53 @@ s.camera=function(x,e,cn,tx){ clearTimeout(s.group[d.ke].mon[d.id].detector_mail); delete(s.group[d.ke].mon[d.id].detector_mail); },detector_mail_timeout); - d.frame_filename='Motion_'+(d.mon.name.replace(/[^\w\s]/gi, ''))+'_'+d.id+'_'+d.ke+'_'+s.formattedTime()+'.jpg'; - fs.readFile(s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg',function(err, frame){ + var files = [] + var sendMail = function(){ d.mailOptions = { from: '"ShinobiCCTV" ', // sender address to: r.mail, // list of receivers - subject: lang.Event+' - '+d.frame_filename, // Subject line + subject: lang.Event+' - '+screenshotName, // Subject line html: ''+lang.EventText1+' '+s.timeObject(new Date).format()+'.', - }; - if(err){ - s.systemLog(lang.EventText2+' '+d.ke+' '+d.id,err) - }else{ - d.mailOptions.attachments=[ - { - filename: d.frame_filename, - content: frame - } - ] - d.mailOptions.html=''+lang.EventText3+'' + attachments: files } - Object.keys(d.details).forEach(function(v,n){ + Object.keys(d.details).forEach(function(v,n){ d.mailOptions.html+='
'+v+' : '+d.details[v]+'
' }) nodemailer.sendMail(d.mailOptions, (error, info) => { if (error) { s.systemLog(lang.MailError,error) - return ; + return false; } - }); + }) + } + if(!detectorStreamBuffers){ + detectorStreamBuffers = s.getDetectorStreams(d) + } + detectorStreamBuffers.slice(detectorStreamBuffers.length - 2,detectorStreamBuffers.length).forEach(function(filepath,n){ + files.push({ + attachment: filepath, + name: 'Video Clip '+n+'.ts' + }) }) + if(screenshotBuffer){ + sendMail() + }else if(d.mon.details.snap === '1'){ + fs.readFile(s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg',function(err, frame){ + if(err){ + s.systemLog(lang.EventText2+' '+d.ke+' '+d.id,err) + }else{ + screenshotBuffer = frame + files.push({ + filename: screenshotName+'.jpg', + content: frame + }) + d.mailOptions.html=''+lang.EventText3+'' + } + sendMail() + }) + }else{ + sendMail() + } }); } if(d.mon.details.detector_command_enable==='1'&&!s.group[d.ke].mon[d.id].detector_command){ @@ -3839,57 +3962,64 @@ var tx; if(r&&r[0]){ r=r[0]; d.d=JSON.parse(r.details); - if(d.d.get_server_log==='1'){ - cn.join('GRPLOG_'+d.ke) - }else{ - cn.leave('GRPLOG_'+d.ke) - } - ///unchangeable from client side, so reset them in case they did. - d.form.details=JSON.parse(d.form.details) - //admin permissions - d.form.details.permissions=d.d.permissions - d.form.details.edit_size=d.d.edit_size - d.form.details.edit_days=d.d.edit_days - d.form.details.use_admin=d.d.use_admin - d.form.details.use_webdav=d.d.use_webdav - d.form.details.use_ldap=d.d.use_ldap - //check - if(d.d.edit_days=="0"){ - d.form.details.days=d.d.days; - } - if(d.d.edit_size=="0"){ - d.form.details.size=d.d.size; - } - if(d.d.sub){ - d.form.details.sub=d.d.sub; - if(d.d.monitors){d.form.details.monitors=d.d.monitors;} - if(d.d.allmonitors){d.form.details.allmonitors=d.d.allmonitors;} - if(d.d.video_delete){d.form.details.video_delete=d.d.video_delete;} - if(d.d.video_view){d.form.details.video_view=d.d.video_view;} - if(d.d.monitor_edit){d.form.details.monitor_edit=d.d.monitor_edit;} - if(d.d.size){d.form.details.size=d.d.size;} - if(d.d.days){d.form.details.days=d.d.days;} - delete(d.form.details.mon_groups) - } - var newSize = d.form.details.size - d.form.details=JSON.stringify(d.form.details) - /// - d.set=[],d.ar=[]; - if(d.form.pass&&d.form.pass!==''){d.form.pass=s.md5(d.form.pass);}else{delete(d.form.pass)}; - delete(d.form.password_again); - d.for=Object.keys(d.form); - d.for.forEach(function(v){ - d.set.push(v+'=?'),d.ar.push(d.form[v]); - }); - d.ar.push(d.ke),d.ar.push(d.uid); - s.sqlQuery('UPDATE Users SET '+d.set.join(',')+' WHERE ke=? AND uid=?',d.ar,function(err,r){ - if(!d.d.sub){ - s.group[d.ke].sizeLimit = parseFloat(newSize) - delete(s.group[d.ke].webdav) - s.init('apps',d) + if(!d.d.sub || d.d.user_change === "1"){ + if(d.d.get_server_log==='1'){ + cn.join('GRPLOG_'+d.ke) + }else{ + cn.leave('GRPLOG_'+d.ke) } - tx({f:'user_settings_change',uid:d.uid,ke:d.ke,form:d.form}); - }); + ///unchangeable from client side, so reset them in case they did. + d.form.details=JSON.parse(d.form.details) + //admin permissions + d.form.details.permissions=d.d.permissions + d.form.details.edit_size=d.d.edit_size + d.form.details.edit_days=d.d.edit_days + d.form.details.use_admin=d.d.use_admin + d.form.details.use_webdav=d.d.use_webdav + d.form.details.use_ldap=d.d.use_ldap + //check + if(d.d.edit_days=="0"){ + d.form.details.days=d.d.days; + } + if(d.d.edit_size=="0"){ + d.form.details.size=d.d.size; + } + if(d.d.sub){ + d.form.details.sub=d.d.sub; + if(d.d.monitors){d.form.details.monitors=d.d.monitors;} + if(d.d.allmonitors){d.form.details.allmonitors=d.d.allmonitors;} + if(d.d.monitor_create){d.form.details.monitor_create=d.d.monitor_create;} + if(d.d.video_delete){d.form.details.video_delete=d.d.video_delete;} + if(d.d.video_view){d.form.details.video_view=d.d.video_view;} + if(d.d.monitor_edit){d.form.details.monitor_edit=d.d.monitor_edit;} + if(d.d.size){d.form.details.size=d.d.size;} + if(d.d.days){d.form.details.days=d.d.days;} + delete(d.form.details.mon_groups) + } + var newSize = d.form.details.size + d.form.details=JSON.stringify(d.form.details) + /// + d.set=[],d.ar=[]; + if(d.form.pass&&d.form.pass!==''){d.form.pass=s.md5(d.form.pass);}else{delete(d.form.pass)}; + delete(d.form.password_again); + d.for=Object.keys(d.form); + d.for.forEach(function(v){ + d.set.push(v+'=?'),d.ar.push(d.form[v]); + }); + d.ar.push(d.ke),d.ar.push(d.uid); + s.sqlQuery('UPDATE Users SET '+d.set.join(',')+' WHERE ke=? AND uid=?',d.ar,function(err,r){ + if(!d.d.sub){ + s.group[d.ke].sizeLimit = parseFloat(newSize) + delete(s.group[d.ke].webdav) + if(s.group[d.ke].discordBot && s.group[d.ke].discordBot.destroy){ + s.group[d.ke].discordBot.destroy() + delete(s.group[d.ke].discordBot) + } + s.init('apps',d) + } + tx({f:'user_settings_change',uid:d.uid,ke:d.ke,form:d.form}); + }); + } } }) break; @@ -3901,15 +4031,15 @@ var tx; switch(d.fff){ case'videos&events': if(!d.eventLimit){ - d.eventLimit=500 + d.eventLimit = 500 }else{ d.eventLimit = parseInt(d.eventLimit); } if(!d.eventStartDate&&d.startDate){ - d.eventStartDate=d.startDate + d.eventStartDate = s.stringToSqlTime(d.startDate) } if(!d.eventEndDate&&d.endDate){ - d.eventEndDate=d.endDate + d.eventEndDate = s.stringToSqlTime(d.endDate) } var monitorQuery = '' var monitorValues = [] @@ -3932,15 +4062,13 @@ var tx; var eventQuery = 'SELECT * FROM Events WHERE ke=?'; var eventQueryValues = [cn.ke]; if(d.eventStartDate&&d.eventStartDate!==''){ - d.eventStartDate=d.eventStartDate.replace('T',' ') if(d.eventEndDate&&d.eventEndDate!==''){ - d.eventEndDate=d.eventEndDate.replace('T',' ') eventQuery+=' AND `time` >= ? AND `time` <= ?'; - eventQueryValues.push(decodeURIComponent(d.eventStartDate)) - eventQueryValues.push(decodeURIComponent(d.eventEndDate)) + eventQueryValues.push(d.eventStartDate) + eventQueryValues.push(d.eventEndDate) }else{ eventQuery+=' AND `time` >= ?'; - eventQueryValues.push(decodeURIComponent(d.eventStartDate)) + eventQueryValues.push(d.eventStartDate) } } if(monitorValues.length>0){ @@ -3969,10 +4097,10 @@ var tx; eventQuery.push() } if(!d.videoStartDate&&d.startDate){ - d.videoStartDate=d.startDate + d.videoStartDate = s.stringToSqlTime(d.startDate) } if(!d.videoEndDate&&d.endDate){ - d.videoEndDate=d.endDate + d.videoEndDate = s.stringToSqlTime(d.endDate) } var getVideos = function(callback){ var videoQuery='SELECT * FROM Videos WHERE ke=?'; @@ -3986,19 +4114,15 @@ var tx; } switch(true){ case(d.videoStartDate&&d.videoStartDate!==''&&d.videoEndDate&&d.videoEndDate!==''): - d.videoStartDate=d.videoStartDate.replace('T',' ') - d.videoEndDate=d.videoEndDate.replace('T',' ') videoQuery+=' AND `time` '+d.videoStartDateOperator+' ? AND `end` '+d.videoEndDateOperator+' ?'; videoQueryValues.push(d.videoStartDate) videoQueryValues.push(d.videoEndDate) break; case(d.videoStartDate&&d.videoStartDate!==''): - d.videoStartDate=d.videoStartDate.replace('T',' ') videoQuery+=' AND `time` '+d.videoStartDateOperator+' ?'; videoQueryValues.push(d.videoStartDate) break; case(d.videoEndDate&&d.videoEndDate!==''): - d.videoEndDate=d.videoEndDate.replace('T',' ') videoQuery+=' AND `end` '+d.videoEndDateOperator+' ?'; videoQueryValues.push(d.videoEndDate) break; @@ -4406,10 +4530,24 @@ var tx; d.value=d.value.concat([d.ke,d.$uid]) s.sqlQuery("UPDATE Users SET "+d.condition.join(',')+" WHERE ke=? AND uid=?",d.value) s.tx({f:'edit_sub_account',ke:d.ke,uid:d.$uid,mail:d.mail,form:d.form},'ADM_'+d.ke); + s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[d.ke,d.$uid],function(err,rows){ + if(rows && rows[0]){ + rows.forEach(function(row){ + delete(s.api[row.code]) + }) + } + }) break; case'delete': s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.$uid,d.ke,d.mail]) - s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.$uid,d.ke]) + s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[d.ke,d.$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=?',[d.$uid,d.ke]) + } + }) s.tx({f:'delete_sub_account',ke:d.ke,uid:d.$uid,mail:d.mail},'ADM_'+d.ke); break; } @@ -4569,7 +4707,7 @@ var tx; } s.log({ke:cn.ke,mid:'$USER'},{type:lang['Websocket Disconnected'],msg:{mail:s.group[cn.ke].users[cn.auth].mail,id:cn.uid,ip:cn.ip}}) delete(s.group[cn.ke].users[cn.auth]); - delete(s.group[cn.ke].dashcamUsers[cn.auth]); + if(s.group[cn.ke].dashcamUsers && s.group[cn.ke].dashcamUsers[cn.auth])delete(s.group[cn.ke].dashcamUsers[cn.auth]); } } if(cn.pluginEngine){ @@ -5637,7 +5775,11 @@ app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){ res.setHeader('Content-Type', 'application/json'); res.header("Access-Control-Allow-Origin",req.headers.origin); s.auth(req.params,function(user){ - if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_view.indexOf(req.params.id)===-1){ + var hasRestrictions = user.details.sub && user.details.allmonitors !== '1' + if( + user.permissions.watch_videos==="0" || + hasRestrictions && (!user.details.video_view || user.details.video_view.indexOf(req.params.id)===-1) + ){ res.end(s.s([])) return } @@ -5667,6 +5809,12 @@ app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){ } } if(req.query.start||req.query.end){ + if(req.query.start && req.query.start !== ''){ + req.query.start = s.stringToSqlTime(req.query.start) + } + if(req.query.end && req.query.end !== ''){ + req.query.end = s.stringToSqlTime(req.query.end) + } if(!req.query.startOperator||req.query.startOperator==''){ req.query.startOperator='>=' } @@ -5675,8 +5823,6 @@ app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){ } switch(true){ case(req.query.start&&req.query.start!==''&&req.query.end&&req.query.end!==''): - req.query.start=req.query.start.replace('T',' ') - req.query.end=req.query.end.replace('T',' ') req.sql+=' AND `time` '+req.query.startOperator+' ? AND `end` '+req.query.endOperator+' ?'; req.count_sql+=' AND `time` '+req.query.startOperator+' ? AND `end` '+req.query.endOperator+' ?'; req.ar.push(req.query.start) @@ -5685,14 +5831,12 @@ app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){ req.count_ar.push(req.query.end) break; case(req.query.start&&req.query.start!==''): - req.query.start=req.query.start.replace('T',' ') req.sql+=' AND `time` '+req.query.startOperator+' ?'; req.count_sql+=' AND `time` '+req.query.startOperator+' ?'; req.ar.push(req.query.start) req.count_ar.push(req.query.start) break; case(req.query.end&&req.query.end!==''): - req.query.end=req.query.end.replace('T',' ') req.sql+=' AND `end` '+req.query.endOperator+' ?'; req.count_sql+=' AND `end` '+req.query.endOperator+' ?'; req.ar.push(req.query.end) @@ -5712,17 +5856,17 @@ app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){ res.end(s.s({total:0,limit:req.query.limit,skip:0,videos:[]}, null, 3)); return } - s.sqlQuery(req.count_sql,req.count_ar,function(err,count){ - s.video('linkBuild',r,req.params.auth) - if(req.query.limit.indexOf(',')>-1){ - req.skip=parseInt(req.query.limit.split(',')[0]) - req.query.limit=parseInt(req.query.limit.split(',')[0]) - }else{ - req.skip=0 - req.query.limit=parseInt(req.query.limit) - } - res.end(s.s({isUTC:config.useUTC,total:count[0]['COUNT(*)'],limit:req.query.limit,skip:req.skip,videos:r}, null, 3)); - }) + s.sqlQuery(req.count_sql,req.count_ar,function(err,count){ + s.video('linkBuild',r,req.params.auth) + if(req.query.limit.indexOf(',')>-1){ + req.skip=parseInt(req.query.limit.split(',')[0]) + req.query.limit=parseInt(req.query.limit.split(',')[0]) + }else{ + req.skip=0 + req.query.limit=parseInt(req.query.limit) + } + res.end(s.s({isUTC:config.useUTC,total:count[0]['COUNT(*)'],limit:req.query.limit,skip:req.skip,videos:r}, null, 3)); + }) }) },res,req); }); @@ -5755,9 +5899,9 @@ app.get(['/:auth/events/:ke','/:auth/events/:ke/:id','/:auth/events/:ke/:id/:lim } } if(req.params.start&&req.params.start!==''){ - req.params.start=req.params.start.replace('T',' ') + req.params.start = s.stringToSqlTime(req.params.start) if(req.params.end&&req.params.end!==''){ - req.params.end=req.params.end.replace('T',' ') + req.params.end = s.stringToSqlTime(req.params.end) req.sql+=' AND `time` >= ? AND `time` <= ?'; req.ar.push(decodeURIComponent(req.params.start)) req.ar.push(decodeURIComponent(req.params.end)) @@ -5788,7 +5932,7 @@ app.get(['/:auth/logs/:ke','/:auth/logs/:ke/:id'], function (req,res){ res.setHeader('Content-Type', 'application/json'); res.header("Access-Control-Allow-Origin",req.headers.origin); s.auth(req.params,function(user){ - if(user.permissions.get_logs==="0"){ + if(user.permissions.get_logs==="0" || user.details.sub && user.details.view_logs !== '1'){ res.end(s.s([])) return } @@ -5818,13 +5962,13 @@ app.get(['/:auth/logs/:ke','/:auth/logs/:ke/:id'], function (req,res){ req.query.endOperator='<=' } if(req.query.start && req.query.start !== '' && req.query.end && req.query.end !== ''){ - req.query.start=req.query.start.replace('T',' ') - req.query.end=req.query.end.replace('T',' ') + 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=req.query.start.replace('T',' ') + req.query.start = s.stringToSqlTime(req.query.start) req.sql+=' AND `time` '+req.query.startOperator+' ?'; req.ar.push(req.query.start) } @@ -5886,7 +6030,8 @@ app.all(['/:auth/configureMonitor/:ke/:id','/:auth/configureMonitor/:ke/:id/:f'] res.setHeader('Content-Type', 'application/json'); res.header("Access-Control-Allow-Origin",req.headers.origin); s.auth(req.params,function(user){ - if(req.params.f!=='delete'){ + var hasRestrictions = user.details.sub && user.details.allmonitors !== '1' + if(req.params.f !== 'delete'){ if(!req.body.data&&!req.query.data){ req.ret.msg='No Monitor Data found.' res.end(s.s(req.ret, null, 3)) @@ -5905,7 +6050,10 @@ app.all(['/:auth/configureMonitor/:ke/:id','/:auth/configureMonitor/:ke/:id/:f'] } return } - if(!user.details.sub||user.details.allmonitors==='1'||user.details.monitor_edit.indexOf(req.monitor.mid)>-1){ + if(!user.details.sub || + user.details.allmonitors === '1' || + hasRestrictions && user.details.monitor_edit.indexOf(req.monitor.mid) >- 1 || + hasRestrictions && user.details.monitor_create === '1'){ if(req.monitor&&req.monitor.mid&&req.monitor.name){ req.set=[],req.ar=[]; req.monitor.mid=req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,''); @@ -6148,6 +6296,94 @@ app.get('/:auth/fileBin/:ke/:id/:year/:month/:day/:file', function (req,res){ } s.auth(req.params,req.fn,res,req); }); +//zip videos and get link from fileBin +app.get('/:auth/zipVideos/:ke', function (req,res){ + res.header("Access-Control-Allow-Origin",req.headers.origin); + var failed = function(resp){ + res.setHeader('Content-Type', 'application/json'); + res.end(s.s(resp)) + } + if(req.query.videos && req.query.videos !== ''){ + s.auth(req.params,function(user){ + var videosSelected = JSON.parse(req.query.videos) + var where = [] + var values = [] + videosSelected.forEach(function(video){ + where.push("(ke=? AND mid=? AND `time`=?)") + if(!video.ke)video.ke = req.params.ke + values.push(video.ke) + values.push(video.mid) + var time = s.nameToTime(video.filename) + if(req.query.isUTC === 'true'){ + time = s.utcToLocal(time) + } + time = new Date(time) + values.push(time) + }) + s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){ + var resp = {ok:false} + if(r && r[0]){ + resp.ok = true + var zipDownload = null + var tempFiles = [] + var fileId = s.gid() + var fileBinDir = s.dir.fileBin+req.params.ke+'/' + var tempScript = s.dir.streams+req.params.ke+'/'+fileId+'.sh' + var zippedFilename = s.formattedTime()+'-'+fileId+'-Shinobi_Recordings.zip' + var zippedFile = fileBinDir+zippedFilename + var script = 'cd '+fileBinDir+' && zip -9 -r '+zippedFile + res.on('close', () => { + if(zipDownload && zipDownload.destroy){ + zipDownload.destroy() + } + fs.unlink(zippedFile); + }) + if(!fs.existsSync(fileBinDir)){ + fs.mkdirSync(fileBinDir); + } + r.forEach(function(video){ + timeFormatted = s.formattedTime(video.time) + video.filename = timeFormatted+'.'+video.ext + var dir = s.video('getDir',video)+video.filename + var tempVideoFile = timeFormatted+' - '+video.mid+'.'+video.ext + fs.writeFileSync(fileBinDir+tempVideoFile, fs.readFileSync(dir)) + tempFiles.push(fileBinDir+tempVideoFile) + script += ' "'+tempVideoFile+'"' + }) + fs.writeFileSync(tempScript,script,'utf8') + var zipCreate = spawn('sh',(tempScript).split(' '),{detached: true}) + zipCreate.stderr.on('data',function(data){ + s.log({ke:req.params.ke,mid:'$USER'},{title:'Zip Create Error',msg:data.toString()}) + }) + zipCreate.on('exit',function(data){ + fs.unlinkSync(tempScript) + tempFiles.forEach(function(file){ + fs.unlink(file,function(){}) + }) + res.setHeader('Content-Disposition', 'attachment; filename="'+zippedFilename+'"') + var zipDownload = fs.createReadStream(zippedFile) + zipDownload.pipe(res) + zipDownload.on('error', function (error) { + s.log({ke:req.params.ke,mid:'$USER'},{title:'Zip Download Error',msg:error.toString()}) + if(zipDownload && zipDownload.destroy){ + zipDownload.destroy() + } + }); + zipDownload.on('close', function () { + res.end() + zipDownload.destroy(); + fs.unlinkSync(zippedFile); + }); + }) + }else{ + failed({ok:false,msg:'No Videos Found'}) + } + }) + },res,req); + }else{ + failed({ok:false,msg:'"videos" query variable is missing from request.'}) + } +}); // Get video file app.get('/:auth/videos/:ke/:id/:file', function (req,res){ s.auth(req.params,function(user){ @@ -6160,7 +6396,7 @@ app.get('/:auth/videos/:ke/:id/:file', function (req,res){ time = s.utcToLocal(time) } time = new Date(time) - s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND time=?',[req.params.ke,req.params.id,time],function(err,r){ + s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND `time`=?',[req.params.ke,req.params.id,time],function(err,r){ if(r&&r[0]){ req.dir=s.video('getDir',r[0])+req.params.file if (fs.existsSync(req.dir)){ @@ -6256,7 +6492,7 @@ app.get(['/:auth/videos/:ke/:id/:file/:mode','/:auth/videos/:ke/:id/:file/:mode/ time = s.utcToLocal(time) } time = new Date(time) - req.sql='SELECT * FROM Videos WHERE ke=? AND mid=? AND time=?'; + req.sql='SELECT * FROM Videos WHERE ke=? AND mid=? AND `time`=?'; req.ar=[req.params.ke,req.params.id,time]; s.sqlQuery(req.sql,req.ar,function(err,r){ if(r&&r[0]){ @@ -6273,7 +6509,7 @@ app.get(['/:auth/videos/:ke/:id/:file/:mode','/:auth/videos/:ke/:id/:file/:mode/ req.ret.msg='Not a valid value.'; }else{ req.ret.ok=true; - s.sqlQuery('UPDATE Videos SET status=? WHERE ke=? AND mid=? AND time=?',[req.params.f,req.params.ke,req.params.id,time]) + s.sqlQuery('UPDATE Videos SET status=? WHERE ke=? AND mid=? AND `time`=?',[req.params.f,req.params.ke,req.params.id,time]) s.tx(r,'GRP_'+r.ke); } break; @@ -6890,6 +7126,7 @@ if(config.childNodes.mode === 'child'){ } s.systemLog(v.mail+' : '+lang.startUpText0+' : '+rr.length,v.size) s.init('group',v) + s.init('apps',v) s.systemLog(v.mail+' : '+lang.startUpText1,countFinished+'/'+count) if(countFinished===count){ s.systemLog(lang.startUpText4) diff --git a/languages/en_CA.json b/languages/en_CA.json index fac04d2f..1fd09bcb 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -104,6 +104,8 @@ "Can View Streams": "Can View Streams", "Can View Videos": "Can View Videos", "Can View Monitor": "Can View Monitor", + "Can Change User Settings": "Can Change User Settings", + "Can Create and Delete Monitors": "Can Create and Delete Monitors", "Can Edit Monitor": "Can Edit Monitor", "Can Delete Videos": "Can Delete Videos", "Delete Video": "Delete Video", @@ -178,6 +180,7 @@ "Monitor Groups": "Monitor Groups", "Group Name": "Group Name", "WebDAV": "WebDAV", + "Discord Bot": "Discord Bot", "URL": "URL", "Autosave": "Autosave", "Save Directory": "Save Directory", @@ -186,6 +189,7 @@ "Monitors per row": "Monitors per row for Montage", "Browser Console Log": "Browser Console Log", "Log Stream": "Log Stream", + "Privileges": "Privileges", "All Monitors and Privileges": "All Monitors and Privileges", "Permissions": "Permissions", "Time-lapse Tool": "Time-lapse Tool", @@ -211,9 +215,13 @@ "Set to Watch Only": "Set to Watch Only", "Save as": "Save as", "Add New": "Add New", + "Zip and Download": "Zip and Download", + "Export Selected Videos": "Export Selected Videos", "Delete Selected Videos": "Delete Selected Videos", "DeleteSelectedVideosMsg": "Do you want to delete these videos? You cannot recover them.", + "ExportSelectedVideosMsg": "Do you want to export these videos? It may take some time to zip and download.", "clientStreamFailedattemptingReconnect": "Client side ctream check failed, attempting reconnect.", + "Export Video": "Export Video", "Delete Filter": "Delete Filter", "confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.", "Fix Video": "Fix Video", @@ -249,6 +257,7 @@ "Unable to Launch": "Unable to Launch", "UnabletoLaunchText": "Please save new monitor first. Then attempt to launch the region editor.", "NoVideosFoundForDateRange": "No Videos found in this date range. Try setting the start date further back.", + "NoLogsFoundForDateRange": "No Logs found in this date range. Try widening the date range.", "monitorEditFailedMaxReached": "Your account has reached the maximum number of cameras that can be created. Speak to an administrator if you would like this changed.", "in": "in", "ago": "ago", @@ -369,7 +378,9 @@ "Allow Next Trigger": "Allow Next Trigger in Milliseconds", "Save Events to SQL": "Save Events to SQL", "Email on Trigger": "Email on Trigger Emails go to the main account holder's login address.", + "Discord Alert on Trigger": "Discord Alert on Trigger", "Allow Next Email": "Allow Next Email in Minutes", + "Allow Next Discord Alert": "Allow Next Discord Alert in Minutes", "How to Record": "How to Record", "Trigger Record": "Trigger Record", "Recording Timeout": "Recording Timeout in Minutes", @@ -432,6 +443,8 @@ "libx264": "libx264", "libx265": "libx265", "copy": "copy", + "Audio": "Audio", + "Mute Audio": "Mute Audio", "No Audio": "No Audio", "aac": "aac", "ac3": "ac3", @@ -576,6 +589,8 @@ "Preview":"Preview", "Websocket Connected":"Websocket Connected", "Websocket Disconnected":"Websocket Disconnected", + "Token":"Token", + "Channel ID":"Channel ID", "New Authentication Token":"New Authentication Token", "All Logs":"All Logs", "For Group":"For Group", @@ -588,8 +603,10 @@ "in Days":"in Days", "Can edit how long to keep Logs":"Can edit how long to keep Logs", "Can use Admin Panel":"Can use Admin Panel", + "Can use Discord Bot":"Can use Discord Bot", "Can use WebDAV":"Can use WebDAV", "Can use LDAP":"Can use LDAP", + "Can View Logs":"Can View Logs", "Can edit how long to keep Events":"Can edit how long to keep Events", "Leave blank for unlimited":"Leave blank for unlimited", "Limited":"Limited", diff --git a/web/libs/js/main.dash2.js b/web/libs/js/main.dash2.js index 926926eb..06f6c934 100644 --- a/web/libs/js/main.dash2.js +++ b/web/libs/js/main.dash2.js @@ -12,6 +12,20 @@ $.ccio={ fr:$('#files_recent'), mon:{} }; +$.ccio.permissionCheck = function(toCheck,monitorId){ + var details = $user.details + if(details.sub && details.allmonitors === '0'){ + var chosenValue = details[toCheck] + if(details[toCheck] instanceof Array && chosenValue.indexOf(monitorId) > -1){ + return true + }else if(chosenValue === '1'){ + return true + } + }else{ + return true + } + return false +} $.ccio.downloadJSON = function(jsonToDownload,filename,errorResponse){ var arr = jsonToDownload; if(arr.length===0 && errorResponse){ @@ -899,7 +913,46 @@ switch($user.details.lang){ break; case 1://monitor icon d.src=placeholder.getData(placeholder.plcimg({bgcolor:'#b57d00',text:'...'})); - tmp+='
'+d.name+'
'+d.mid+'
<%-cleanLang(lang['Save as'])%> : '+d.ext+'
Status : '+d.status+'
'; + tmp+='
'+d.name+'
'+d.mid+'
<%-cleanLang(lang['Save as'])%> : '+d.ext+'
Status : '+d.status+'
' + tmp+='
' + var buttons = { + "Pop": { + "label": "Pop", + "attr": "monitor=\"pop\"", + "class": "default", + "icon": "external-link" + }, + "Power Viewer": { + "label": "Power Viewer", + "attr": "monitor=\"powerview\"", + "class": "default", + "icon": "map-marker" + }, + "Videos List": { + "label": "Videos List", + "attr": "monitor=\"videos_table\"", + "class": "default", + "icon": "film" + }, + "Monitor Settings": { + "label": "Monitor Settings", + "attr": "monitor=\"edit\"", + "class": "default", + "icon": "wrench" + } + } + if(!$.ccio.permissionCheck('video_view',d.mid)){ + delete(buttons["Videos List"]) + delete(buttons["Power Viewer"]) + } + if(!$.ccio.permissionCheck('monitor_edit',d.mid)){ + delete(buttons["Monitor Settings"]) + } + $.each(buttons,function(n,v){ + tmp+='' + }) + tmp+='
\ +
'; delete(d.src); break; case 2://monitor stream @@ -922,23 +975,90 @@ switch($user.details.lang){ tmp+='
'+d.name+', <%-cleanLang(lang['Recording FPS'])%> : '+d.fps+'
'; tmp+=''; tmp+='
'//start of btn list - $.each([ - {label:"<%-cleanLang(lang.Snapshot)%>",attr:'monitor="snapshot"',class:'primary',icon:'camera'}, - {label:"<%-cleanLang(lang['Show Logs'])%>",attr:'monitor="show_data"',class:'warning',icon:'exclamation-triangle'}, -// {label:"<%-cleanLang(lang['Show Logs'])%>",attr:'class_toggle="show_data" data-target="'+dataTarget+'"',class:'warning',icon:'exclamation-triangle'}, - {label:"<%-cleanLang(lang.Control)%>",attr:'monitor="control_toggle"',class:'default arrows',icon:'arrows'}, - {label:"<%-cleanLang(lang['Status Indicator'])%>",attr:'monitor="watch_on"',class:'success signal',icon:'plug'}, - {label:"<%-cleanLang(lang['Detector'])%>",attr:'monitor="motion"',class:'warning',icon:'grav'}, - {label:"<%-cleanLang(lang.Pop)%>",attr:'monitor="pop"',class:'default',icon:'external-link'}, -// {label:"<%-cleanLang(lang.Magnify)%>",attr:'monitor="magnify"',class:'default',icon:'search-plus'}, - {label:"<%-cleanLang(lang.Calendar)%>",attr:'monitor="calendar"',class:'default',icon:'calendar'}, - {label:"<%-cleanLang(lang['Power Viewer'])%>",attr:'monitor="powerview"',class:'default',icon:'map-marker'}, - {label:"<%-cleanLang(lang['Time-lapse'])%>",attr:'monitor="timelapse"',class:'default',icon:'angle-double-right'}, - {label:"<%-cleanLang(lang['Videos List'])%>",attr:'monitor="videos_table"',class:'default',icon:'film'}, - {label:"<%-cleanLang(lang['Monitor Settings'])%>",attr:'monitor="edit"',class:'default permission_monitor_edit',icon:'wrench'}, - {label:"<%-cleanLang(lang.Fullscreen)%>",attr:'monitor="fullscreen"',class:'default',icon:'arrows-alt'}, - {label:"<%-cleanLang(lang.Close)%>",attr:'monitor="watch_off"',class:'danger',icon:'times'}, - ],function(n,v){ + var buttons = { + "Snapshot": { + "label": "Snapshot", + "attr": "monitor=\"snapshot\"", + "class": "primary", + "icon": "camera" + }, + "Show Logs": { + "label": "Show Logs", + "attr": "monitor=\"show_data\"", + "class": "warning", + "icon": "exclamation-triangle" + }, + "Control": { + "label": "Control", + "attr": "monitor=\"control_toggle\"", + "class": "default arrows", + "icon": "arrows" + }, + "Status Indicator": { + "label": "Status Indicator", + "attr": "monitor=\"watch_on\"", + "class": "success signal", + "icon": "plug" + }, + "Pop": { + "label": "Pop", + "attr": "monitor=\"pop\"", + "class": "default", + "icon": "external-link" + }, + "Calendar": { + "label": "Calendar", + "attr": "monitor=\"calendar\"", + "class": "default ", + "icon": "calendar" + }, + "Power Viewer": { + "label": "Power Viewer", + "attr": "monitor=\"powerview\"", + "class": "default", + "icon": "map-marker" + }, + "Time-lapse": { + "label": "Time-lapse", + "attr": "monitor=\"timelapse\"", + "class": "default", + "icon": "angle-double-right" + }, + "Videos List": { + "label": "Videos List", + "attr": "monitor=\"videos_table\"", + "class": "default", + "icon": "film" + }, + "Monitor Settings": { + "label": "Monitor Settings", + "attr": "monitor=\"edit\"", + "class": "default", + "icon": "wrench" + }, + "Fullscreen": { + "label": "Fullscreen", + "attr": "monitor=\"fullscreen\"", + "class": "default", + "icon": "arrows-alt" + }, + "Close": { + "label": "Close", + "attr": "monitor=\"watch_off\"", + "class": "danger", + "icon": "times" + } + } + if(!$.ccio.permissionCheck('video_view',d.mid)){ + delete(buttons["Videos List"]) + delete(buttons["Time-lapse"]) + delete(buttons["Power Viewer"]) + delete(buttons["Calendar"]) + } + if(!$.ccio.permissionCheck('monitor_edit',d.mid)){ + delete(buttons["Monitor Settings"]) + } + $.each(buttons,function(n,v){ tmp+='' }) tmp+='
';//end of btn list @@ -1015,6 +1135,11 @@ switch($user.details.lang){ } } k.e.append(tmp).find('.stream-element').resize(); + if($.ccio.op().switches.monitorMuteAudio === 1){ + k.e.find('video').each(function(n,el){ + el.muted = "muted" + }) + } break; case'user-row': d.e=$('.user-row[uid="'+d.uid+'"][ke="'+d.ke+'"]') @@ -1802,6 +1927,7 @@ $.ccio.globalWebsocket=function(d,user){ delete($.timelapse.currentVideosArray.videos[$.timelapse.currentVideos[d.filename].position]) $.timelapse.drawTimeline(false) } + if($.vidview.loadedVideos && $.vidview.loadedVideos[d.filename])delete($.vidview.loadedVideos[d.filename]) break; case'video_build_success': if(!d.mid){d.mid=d.id;};d.status=1; @@ -2519,6 +2645,14 @@ $.ccio.cx=function(x,user){ $(document).ready(function(e){ console.log("%cWarning!", "font: 2em monospace; color: red;"); console.log('%cLeaving the developer console open is fine if you turn off "Network Recording". This is because it will keep a log of all files, including frames and videos segments.', "font: 1.2em monospace; "); +if(!$.ccio.permissionCheck('monitor_create')){ + $('#add_monitor_button_main').remove() +} +$.each(['user_change','monitor_create','view_logs'],function(n,permission){ + if(!$.ccio.permissionCheck(permission)){ + $('.permission_'+permission).remove() + } +}) //global form functions $.ccio.form={}; $.ccio.form.details=function(e){ @@ -3084,7 +3218,7 @@ $.multimon.e.on('shown.bs.modal',function() { tmp+='
' tmp+=''+v.name+'
'+v.mid+''+v.status+''+streamURL+'' //buttons - tmp+=' ' + tmp+=' ' tmp+='' }) $.multimon.table.html(tmp) @@ -4068,27 +4202,78 @@ $.vidview.f.submit(function(e){ $('#videos_viewer_limit,#videos_viewer_daterange').change(function(){ $.vidview.f.submit() }) -$.vidview.e.find('.delete_selected').click(function(e){ - e.s={} +$.vidview.getSelected = function(getArray){ + var arr = {} + if(getArray){ + arr = [] + } $.vidview.f.find('[data-ke] input:checked').each(function(n,v){ v=$(v).parents('tr') - e.s[v.attr('data-file')]={mid:v.attr('data-mid'),auth:v.attr('data-auth')} + if(getArray){ + arr.push({filename:v.attr('data-file'),mid:v.attr('data-mid'),auth:v.attr('data-auth')}) + }else{ + arr[v.attr('data-file')]={mid:v.attr('data-mid'),auth:v.attr('data-auth')} + } }) + return arr +} +$.vidview.e.find('.delete_selected').click(function(){ + e = {} + e.s = $.vidview.getSelected() + if(Object.keys(e.s).length === 0){ + $.ccio.init('note',{ + title:'No Videos Selected', + text:'You must choose at least one video.', + type:'error' + },$user); + return + } $.confirm.e.modal('show'); $.confirm.title.text('<%-cleanLang(lang['Delete Selected Videos'])%>') e.html='<%-cleanLang(lang.DeleteSelectedVideosMsg)%>
' + var deleteLinks = [] $.each(e.s,function(n,v){ e.html+=n+'
'; + if($.vidview.loadedVideos[n])deleteLinks.push($.vidview.loadedVideos[n].links.deleteVideo) }) $.confirm.body.html(e.html) $.confirm.click({title:'Delete Video',class:'btn-danger'},function(){ - $.each(e.s,function(n,v){ - $.getJSON($.ccio.init('location',$.users[v.auth])+v.auth+'/videos/'+$user.ke+'/'+v.mid+'/'+n+'/delete',function(d){ + $.each(deleteLinks,function(n,link){ + $.getJSON(link,function(d){ $.ccio.log(d) }) }) }); }) +$.vidview.e.find('.export_selected').click(function(){ + e = {} + var videos = $.vidview.getSelected(true) + if(videos.length === 0){ + $.ccio.init('note',{ + title:'No Videos Selected', + text:'You must choose at least one video.', + type:'error' + },$user); + return + } + $.confirm.e.modal('show'); + $.confirm.title.text('<%-cleanLang(lang['Export Selected Videos'])%>') + var html = '<%-cleanLang(lang.ExportSelectedVideosMsg)%>
' + $.each(videos,function(n,v){ + html+=v.filename+'
'; + }) + $.confirm.body.html(html) + $.confirm.click({title:'Export Video',class:'btn-danger'},function(){ + var queryVariables = [] + queryVariables.push('videos='+JSON.stringify(videos)) + if(<%-config.useUTC%> === true){ + queryVariables.push('isUTC=true') + } + console.log(queryVariables) + var downloadZip = $.ccio.init('location',$user)+$user.auth_token+'/zipVideos/'+$user.ke+'?'+queryVariables.join('&') + $('#temp').html('').find('iframe').attr('src',downloadZip); + }); +}) $.vidview.pages.on('click','[page]',function(e){ e.limit=$.vidview.limit.val(); e.page=$(this).attr('page'); @@ -4948,6 +5133,15 @@ $('body') $('.monitor_item').attr('data-gs-auto-position','no') } break; + case'monitorMuteAudio': + $('.monitor_item video').each(function(n,el){ + if(e.o[e.switch] === 1){ + el.muted = true + }else{ + el.muted = false + } + }) + break; } switch(e.e.attr('type')){ case'text': @@ -5161,6 +5355,7 @@ $('body') d.fn() $.vidview.pages.find('[page="'+$.vidview.current_page+'"]').addClass('active') e.v=$.vidview.e; + $.vidview.loadedVideos = {} e.b=e.v.modal('show').find('.modal-body .contents'); e.t=e.v.find('.modal-title i'); switch(e.a){ @@ -5169,7 +5364,8 @@ $('body') e.ar=[]; if(d.videos[0]){ $.each(d.videos,function(n,v){ - if(v.status!==0){ + if(v.status !== 0){ + $.vidview.loadedVideos[v.filename] = Object.assign(v,{}) var n=$.ccio.mon[v.ke+v.mid+user.auth_token]; if(n){v.title=n.name+' - '+(parseInt(v.size)/1000000).toFixed(2)+'mb';} v.start=v.time; @@ -5221,13 +5417,14 @@ $('body') e.tmp+=''; $.each(d.videos,function(n,v){ if(v.status!==0){ + $.vidview.loadedVideos[v.filename] = Object.assign(v,{}) var href = $.ccio.init('videoUrlBuild',v) v.mon=$.ccio.mon[v.ke+v.mid+user.auth_token]; v.start=v.time; // v.filename=$.ccio.init('tf',v.time)+'.'+v.ext; e.tmp+=''; e.tmp+='
'; - e.tmp+=''; + e.tmp+=''; e.tmp+=''+$.ccio.timeObject(v.end).format('h:mm:ss A, MMMM Do YYYY')+''; e.tmp+=''+$.ccio.timeObject(v.time).format('h:mm:ss A, MMMM Do YYYY')+''; e.tmp+=''+v.mon.name+''; diff --git a/web/pages/admin.ejs b/web/pages/admin.ejs index af63fa70..9c6c53bc 100644 --- a/web/pages/admin.ejs +++ b/web/pages/admin.ejs @@ -181,7 +181,9 @@ $.sU.e.on('click','.permission',function(e){ $.each($.ccio.subs[$.pR.user],function(n,v){ $.pR.e.find('[name="'+n+'"]').val(v) }) - $.pR.e.find('[detail="allmonitors"]').val(e.d.allmonitors).change() + $.pR.e.find('[detail]').each(function(n,v){ + $(v).val(e.d[$(v).attr('detail')]) + }).first().change() $.each(['monitors','monitor_edit','video_delete','video_view'],function(m,b){ if(e.d[b]){ $.each(e.d[b],function(n,v){ @@ -193,37 +195,43 @@ $.sU.e.on('click','.permission',function(e){ //permission window $.pR={e:$('#permissions'),l:$('#permissionsLabel small')};$.pR.f=$.pR.e.find('form') +$.pR.e.on('change','[detail]',function(e){ + e.f=$(this).parents('form'); + var details = $.pR.e.find('[name="details"]') + e.ar = JSON.parse(details.val()) + e.f.find('[detail]').each(function(n,v){ + v = $(v);e.ar[v.attr('detail')] = v.val() + }) + details.val(JSON.stringify(e.ar)) +}) $.pR.e.on('change','[detail="allmonitors"]',function(e){ e.e=$(this), e.mon=$('.permission-view') - e.details=$.pR.e.find('[name="details"]') - e.json=JSON.parse(e.details.val()) - if(e.e.val()=='1'){ + if(e.e.val() === '1'){ e.mon.hide(); - e.json.allmonitors='1'; }else{ e.mon.show() - e.json.allmonitors='0'; $.pR.e.find('[monitor]').first().change() } - e.details.val(JSON.stringify(e.json)) }) $.pR.e.on('click','[check]',function(e){ $(this).parents('.form-group-group').find('select').val($(this).attr('check')).first().change() }) $.pR.e.on('change','[monitor]',function(e){ - e.monitors=[]; - e.key=$(this).attr('monitor'); e.details=$.pR.e.find('[name="details"]') try{e.detail=JSON.parse(e.details.val())}catch(err){e.detail={}} if(!e.detail){e.detail={}} - $.pR.e.find('[monitor="'+e.key+'"]').each(function(n,v){ - v=$(v) - if(v.val()=='1'){ - e.monitors.push(v.attr('mid')) - } - }); - e.detail[e.key]=e.monitors; + $.pR.e.find('[monitor]').each(function(n,kel){ + var monitors = []; + var key = $(kel).attr('monitor') + $.pR.e.find('[monitor="'+key+'"]').each(function(n,v){ + var el = $(v) + if(el.val() === '1'){ + monitors.push(el.attr('mid')) + } + }); + e.detail[key] = monitors + }) e.details.val(JSON.stringify(e.detail)) }); $.pR.f.submit(function(e){ diff --git a/web/pages/blocks/header.ejs b/web/pages/blocks/header.ejs index 4e58fa8d..d49d2a53 100644 --- a/web/pages/blocks/header.ejs +++ b/web/pages/blocks/header.ejs @@ -15,7 +15,7 @@ -<% cleanLang=function(string){ +<% cleanLang = function(string){ if(!string){string=''} return string.replace(/'/g,"\\'") -} %> \ No newline at end of file +}%> \ No newline at end of file diff --git a/web/pages/blocks/mainpermissions.ejs b/web/pages/blocks/mainpermissions.ejs index 8ef0115d..db5230a2 100644 --- a/web/pages/blocks/mainpermissions.ejs +++ b/web/pages/blocks/mainpermissions.ejs @@ -119,6 +119,14 @@ +
+ +
-
-
- -
-
- -
+
+ +
+
+ +
+
+ +
+
+
<% } %> + <% if(details.use_discordbot!=='0'){ %> +
+

<%-lang['Discord Bot']%>

+
+ +
+
+
+ +
+
+ +
+
+
+ <% } %> <% if(details.use_ldap!=='0'){ %>

<%-lang.LDAP%>

diff --git a/web/pages/blocks/subpermissions.ejs b/web/pages/blocks/subpermissions.ejs index 4801a321..8faece0e 100644 --- a/web/pages/blocks/subpermissions.ejs +++ b/web/pages/blocks/subpermissions.ejs @@ -10,13 +10,40 @@