diff --git a/camera.js b/camera.js index 809a995..f5939a0 100644 --- a/camera.js +++ b/camera.js @@ -34,13 +34,13 @@ loadLib('ffmpeg')(s,config,function(){ //express web server with ejs var app = loadLib('webServer')(s,config,lang,io) //web server routes : page handling.. - loadLib('webServerPaths')(s,config,lang,app) + loadLib('webServerPaths')(s,config,lang,app,io) //web server routes for streams : streams.. - loadLib('webServerStreamPaths')(s,config,lang,app) + loadLib('webServerStreamPaths')(s,config,lang,app,io) //web server admin routes : create sub accounts, share monitors, share videos - loadLib('webServerAdminPaths')(s,config,lang,app) + loadLib('webServerAdminPaths')(s,config,lang,app,io) //web server superuser routes : create admin accounts and manage system functions - loadLib('webServerSuperPaths')(s,config,lang,app) + loadLib('webServerSuperPaths')(s,config,lang,app,io) //websocket connection handlers : login and streams.. loadLib('socketio')(s,config,lang,io) //user and group functions diff --git a/cron.js b/cron.js index 234ee9a..6856322 100644 --- a/cron.js +++ b/cron.js @@ -46,22 +46,35 @@ if(databaseOptions.client === 'sqlite3' && databaseOptions.connection.filename = databaseOptions.connection.filename = __dirname+"/shinobi.sqlite" } s.databaseEngine = knex(databaseOptions) +s.dateSubtract = function(date, interval, units){ + var ret = date + var checkRollover = function() { if(ret.getDate() != date.getDate()) ret.setDate(0);}; + switch(interval.toLowerCase()) { + case 'year' : ret.setFullYear(ret.getFullYear() - units); checkRollover(); break; + case 'quarter': ret.setMonth(ret.getMonth() - 3*units); checkRollover(); break; + case 'month' : ret.setMonth(ret.getMonth() - units); checkRollover(); break; + case 'week' : ret.setDate(ret.getDate() - 7*units); break; + case 'day' : ret.setDate(ret.getDate() - units); break; + case 'hour' : ret.setTime(ret.getTime() - units*3600000); break; + case 'minute' : ret.setTime(ret.getTime() - units*60000); break; + case 'second' :default: ret.setTime(ret.getTime() - units*1000); break; + } + return (new Date(ret)) +} 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+")" + var value = value.toLowerCase() + var splitValue = value.split(' ') + var amount = parseFloat(splitValue[0]) + var today = new Date() + var query + if(value.indexOf('min') > -1){ + query = s.dateSubtract(today,'minute',amount) + }else if(value.indexOf('day') > -1){ + query = s.dateSubtract(today,'day',amount) + }else if(value.indexOf('hour') > -1){ + query = s.dateSubtract(today,'hour',amount) } - return dateQueryFunction + return query } s.mergeQueryValues = function(query,values){ if(!values){values=[]} @@ -187,12 +200,11 @@ s.checkFilterRules = function(v,callback){ v.d.filters={}; } //delete old videos with filter - if(config.cron.deleteOld===true){ + if(config.cron.deleteOld === true){ var where = [{ "p1":"end", - "p2":"<", - "p3":s.sqlDate(v.d.days+" DAYS"), - "p3_type":"function", + "p2":"<=", + "p3":s.sqlDate(v.d.days+" DAY") }] //exclude monitors with their own max days v.monitorsWithMaxKeepDays.forEach(function(mid){ @@ -298,7 +310,7 @@ s.deleteRowsWithNoVideo = function(v,callback){ ){ s.alreadyDeletedRowsWithNoVideosOnStart[v.ke]=true; es={}; - s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=0 AND details NOT LIKE \'%"archived":"1"%\' AND time < '+s.sqlDate('10 MINUTE'),[v.ke],function(err,evs){ + s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=0 AND details NOT LIKE \'%"archived":"1"%\' AND time < ?',[v.ke,s.sqlDate('10 MINUTE')],function(err,evs){ if(evs&&evs[0]){ es.del=[];es.ar=[v.ke]; evs.forEach(function(ev){ @@ -341,7 +353,7 @@ s.deleteRowsWithNoVideo = 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){ + s.sqlQuery("DELETE FROM Logs WHERE ke=? AND `time` < ?",[v.ke,s.sqlDate(v.d.log_days+' DAY')],function(err,rrr){ callback() if(err)return console.error(err); if(rrr.affectedRows && rrr.affectedRows.length>0 || config.debugLog === true){ @@ -356,7 +368,7 @@ s.deleteOldLogs = 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){ + s.sqlQuery("DELETE FROM Events WHERE ke=? AND `time` < ?",[v.ke,s.sqlDate(v.d.event_days+' DAY')],function(err,rrr){ callback() if(err)return console.error(err); if(rrr.affectedRows && rrr.affectedRows.length>0 || config.debugLog === true){ @@ -371,8 +383,8 @@ s.deleteOldEvents = 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 `time` < "+s.sqlDate('? DAYS'); - s.sqlQuery("SELECT *"+fileBinQuery,[v.ke,v.d.fileBin_days],function(err,files){ + var fileBinQuery = " FROM Files WHERE ke=? AND `time` < ?"; + s.sqlQuery("SELECT *"+fileBinQuery,[v.ke,s.sqlDate(v.d.fileBin_days+' DAY')],function(err,files){ if(files&&files[0]){ //delete the files files.forEach(function(file){ @@ -451,8 +463,7 @@ s.processUser = function(number,rows){ },{ "p1":"end", "p2":"<", - "p3":s.sqlDate(b.details.max_keep_days+" DAYS"), - "p3_type":"function", + "p3":s.sqlDate(b.details.max_keep_days+" DAY") }] }; } diff --git a/languages/en_CA.json b/languages/en_CA.json index 24a4f60..e72d952 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -206,6 +206,9 @@ "Save Links to Database": "Save Links to Database", "Bucket": "Bucket", "Region": "Region", + "Use Global Amazon S3 Video Storage": "Use Global Amazon S3 Video Storage", + "Use Global Backblaze B2 Video Storage": "Use Global Backblaze B2 Video Storage", + "Use Global WebDAV Video Storage": "Use Global WebDAV Video Storage", "Amazon S3 Upload Error": "Amazon S3 Upload Error", "accountId": "Account ID", "applicationKey": "Application Key", @@ -574,6 +577,8 @@ "Monitor is now Disabled": "Monitor is now Disabled", "Monitor is now Watching": "Monitor is now Watching", "Monitor is now Recording": "Monitor is now Recording", + "Space Used": "Space Used", + "Processor": "Processor", "coProcessor": "coProcessor", "coProcessor Stopped": "coProcessor Stopped", "coProcessor Started": "coProcessor Started", @@ -606,6 +611,7 @@ "Control Error": "Control Error", "Database row does not exist": "Database row does not exist", "File Delete Error": "File Delete Error", + "List of Videos Delete Error": "List of Videos 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.", @@ -643,7 +649,14 @@ "Thumbnail": "Thumbnail", "Host Type": "Host Type", "Edit": "Edit", + "No Monitor ID Present in Form": "No Monitor ID Present in Form", + "State Configuration has no monitors associated": "State Configuration has no monitors associated", + "State Configuration Not Found": "State Configuration Not Found", + "Inserted State Configuration": "Inserted State Configuration", + "Edited State Configuration": "Edited State Configuration", + "Deleted State Configuration": "Deleted State Configuration", "Dashboard Language": "Dashboard Language", + "Form Data Not Found": "Form Data Not Found", "File Not Found": "File Not Found", "File Not Found in Filesystem": "File Not Found in Filesystem", "File Not Found in Database": "File Not Found in Database", diff --git a/libs/cloudUploaders.js b/libs/cloudUploaders.js index b0188cd..cbc4128 100644 --- a/libs/cloudUploaders.js +++ b/libs/cloudUploaders.js @@ -6,6 +6,7 @@ module.exports = function(s,config,lang){ // WebDAV var beforeAccountSaveForWebDav = function(d){ //d = save event + d.form.details.webdav_use_global=d.d.webdav_use_global d.form.details.use_webdav=d.d.use_webdav } var cloudDiskUseStartupForWebDav = function(group,userDetails){ @@ -20,6 +21,15 @@ module.exports = function(s,config,lang){ var loadWebDavForUser = function(e){ // e = user var ar = JSON.parse(e.details); + if(ar.webdav_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WebDAV){ + // { + // webdav_user: "", + // webdav_pass: "", + // webdav_url: "", + // webdav_dir: "", + // } + ar = Object.assign(ar,config.cloudUploaders.WebDAV) + } //owncloud/webdav if(!s.group[e.ke].webdav && ar.webdav_user&& @@ -150,6 +160,7 @@ module.exports = function(s,config,lang){ //Amazon S3 var beforeAccountSaveForAmazonS3 = function(d){ //d = save event + d.form.details.aws_use_global=d.d.aws_use_global d.form.details.use_aws_s3=d.d.use_aws_s3 } var cloudDiskUseStartupForAmazonS3 = function(group,userDetails){ @@ -163,7 +174,17 @@ module.exports = function(s,config,lang){ } var loadAmazonS3ForUser = function(e){ // e = user - var ar = JSON.parse(e.details); + var ar = JSON.parse(e.details) + if(ar.aws_use_global === '1' && config.cloudUploaders && config.cloudUploaders.AmazonS3){ + // { + // aws_accessKeyId: "", + // aws_secretAccessKey: "", + // aws_region: "", + // aws_s3_bucket: "", + // aws_s3_dir: "", + // } + ar = Object.assign(ar,config.cloudUploaders.AmazonS3) + } //Amazon S3 if(!s.group[e.ke].aws && !s.group[e.ke].aws_s3 && @@ -262,7 +283,8 @@ module.exports = function(s,config,lang){ //Backblaze B2 var beforeAccountSaveForBackblazeB2 = function(d){ //d = save event - d.form.details.use_aws_s3=d.d.use_bb_b2 + d.form.details.b2_use_global=d.d.b2_use_global + d.form.details.use_bb_b2=d.d.use_bb_b2 } var cloudDiskUseStartupForBackblazeB2 = function(group,userDetails){ group.cloudDiskUse['b2'].name = 'Backblaze B2' @@ -276,6 +298,15 @@ module.exports = function(s,config,lang){ var loadBackblazeB2ForUser = function(e){ var ar = JSON.parse(e.details); try{ + if(ar.b2_use_global === '1' && config.cloudUploaders && config.cloudUploaders.BackblazeB2){ + // { + // bb_b2_accountId: "", + // bb_b2_applicationKey: "", + // bb_b2_bucket: "", + // bb_b2_dir: "", + // } + ar = Object.assign(ar,config.cloudUploaders.BackblazeB2) + } if(!s.group[e.ke].bb_b2 && ar.bb_b2_accountId && ar.bb_b2_accountId !=='' && diff --git a/libs/detector.js b/libs/detector.js index 7cfbc05..2ea4034 100644 --- a/libs/detector.js +++ b/libs/detector.js @@ -1,5 +1,6 @@ var P2P = require('pipe2pam'); -var PamDiff = require('pam-diff'); +// pamDiff is based on https://www.npmjs.com/package/pam-diff +var PamDiff = require('./detectorPamDiff.js'); module.exports = function(s,config){ s.createPamDiffEngine = function(e){ var width, @@ -64,12 +65,13 @@ module.exports = function(s,config){ plug:'built-in', name:trigger.name, reason:'motion', - confidence:trigger.percent, + confidence:trigger.percent }, plates:[], - imgHeight:height, - imgWidth:width + imgHeight:e.details.detector_scale_y, + imgWidth:e.details.detector_scale_x } + if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix] var region = Object.values(regionJson).find(x => x.name == detectorObject.name) s.checkMaximumSensitivity(e, region, detectorObject, function() { s.checkTriggerThreshold(e, region, detectorObject, function() { diff --git a/libs/detectorPamDiff.js b/libs/detectorPamDiff.js new file mode 100644 index 0000000..a24ef3b --- /dev/null +++ b/libs/detectorPamDiff.js @@ -0,0 +1,622 @@ +// +// Shinobi - fork of pam-diff +// Copyright (C) 2018 Kevin Godell +// Author : Kevin Godell, https://github.com/kevinGodell +// npmjs : https://www.npmjs.com/package/pam-diff +// Github : https://github.com/kevinGodell/pam-diff +// +'use strict'; + +const { Transform } = require('stream'); + +const PP = require('polygon-points'); +/** + * + * @param chunk + * @private + */ + var _getMatrixFromPoints = function(thisRegion) { + var coordinates = [ + thisRegion.topLeft, + {"x" : thisRegion.bottomRight.x, "y" : thisRegion.topLeft.y}, + thisRegion.bottomRight + ] + var width = Math.sqrt( Math.pow(coordinates[1].x - coordinates[0].x, 2) + Math.pow(coordinates[1].y - coordinates[0].y, 2)); + var height = Math.sqrt( Math.pow(coordinates[2].x - coordinates[1].x, 2) + Math.pow(coordinates[2].y - coordinates[1].y, 2)) + return { + x: coordinates[0].x, + y: coordinates[0].y, + width: width, + height: height, + tag: thisRegion.name + } + } +class PamDiff extends Transform { + + /** + * + * @param [options] {Object} + * @param [callback] {Function} + */ + constructor(options, callback) { + super(options); + Transform.call(this, {objectMode: true}); + this.difference = PamDiff._parseOptions('difference', options);//global option, can be overridden per region + this.percent = PamDiff._parseOptions('percent', options);//global option, can be overridden per region + this.regions = PamDiff._parseOptions('regions', options);//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared. + this.callback = callback;//callback function to be called when pixel difference is detected + this._parseChunk = this._parseFirstChunk;//first parsing will be reading settings and configuring internal pixel reading + } + + /** + * + * @param option {String} + * @param options {Object} + * @return {*} + * @private + */ + static _parseOptions(option, options) { + if (options && options.hasOwnProperty(option)) { + return options[option]; + } + return null; + } + + /** + * + * @param number {Number} + * @param def {Number} + * @param low {Number} + * @param high {Number} + * @return {Number} + * @private + */ + static _validateNumber(number, def, low, high) { + if (isNaN(number)) { + return def; + } else if (number < low) { + return low; + } else if (number > high) { + return high; + } else { + return number; + } + } + + /** + * + * @deprecated + * @param string {String} + */ + setGrayscale(string) { + console.warn('grayscale option has been removed, "average" has proven to most accurate and is the default'); + } + + /** + * + * @param number {Number} + */ + set difference(number) { + this._difference = PamDiff._validateNumber(parseInt(number), 5, 1, 255); + } + + /** + * + * @return {Number} + */ + get difference() { + return this._difference; + } + + /** + * + * @param number {Number} + * @return {PamDiff} + */ + setDifference(number) { + this.difference = number; + return this; + } + + /** + * + * @param number {Number} + */ + set percent(number) { + this._percent = PamDiff._validateNumber(parseInt(number), 5, 1, 100); + } + + /** + * + * @return {Number} + */ + get percent() { + return this._percent; + } + + /** + * + * @param number {Number} + * @return {PamDiff} + */ + setPercent(number) { + this.percent = number; + return this; + } + + /** + * + * @param array {Array} + */ + set regions(array) { + if (!array) { + if (this._regions) { + delete this._regions; + delete this._regionsLength; + delete this._minDiff; + } + this._diffs = 0; + } else if (!Array.isArray(array) || array.length < 1) { + throw new Error(`Regions must be an array of at least 1 region object {name: 'region1', difference: 10, percent: 10, polygon: [[0, 0], [0, 50], [50, 50], [50, 0]]}`); + } else { + this._regions = []; + this._minDiff = 255; + for (const region of array) { + if (!region.hasOwnProperty('name') || !region.hasOwnProperty('polygon')) { + throw new Error('Region must include a name and a polygon property'); + } + const polygonPoints = new PP(region.polygon); + const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255); + const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100); + this._minDiff = Math.min(this._minDiff, difference); + this._regions.push( + { + name: region.name, + polygon: polygonPoints, + difference: difference, + percent: percent, + diffs: 0 + } + ); + } + this._regionsLength = this._regions.length; + this._createPointsInPolygons(this._regions, this._width, this._height); + } + } + + /** + * + * @return {Array} + */ + get regions() { + return this._regions; + } + + /** + * + * @param array {Array} + * @return {PamDiff} + */ + setRegions(array) { + this.regions = array; + return this; + } + + /** + * + * @param func {Function} + */ + set callback(func) { + if (!func) { + delete this._callback; + } else if (typeof func === 'function' && func.length === 1) { + this._callback = func; + } else { + throw new Error('Callback must be a function that accepts 1 argument.'); + } + } + + /** + * + * @return {Function} + */ + get callback() { + return this._callback; + } + + /** + * + * @param func {Function} + * @return {PamDiff} + */ + setCallback(func) { + this.callback = func; + return this; + } + + /** + * + * @return {PamDiff} + */ + resetCache() { + //delete this._oldPix; + //delete this._newPix; + //delete this._width; + //delete this._length; + this._parseChunk = this._parseFirstChunk; + return this; + } + + /** + * + * @param regions {Array} + * @param width {Number} + * @param height {Number} + * @private + */ + _createPointsInPolygons(regions, width, height) { + if (regions && width && height) { + this._pointsInPolygons = []; + for (const region of regions) { + const bitset = region.polygon.getBitset(this._width, this._height); + region.pointsLength = bitset.count; + this._pointsInPolygons.push(bitset.buffer); + } + } + } + + /** + * + * @param chunk + * @private + */ + _blackAndWhitePixelDiff(chunk) { + this._newPix = chunk.pixels; + for (let y = 0, i = 0; y < this._height; y++) { + for (let x = 0; x < this._width; x++, i++) { + const diff = this._oldPix[i] !== this._newPix[i]; + if (this._regions && diff === true) { + for (let j = 0; j < this._regionsLength; j++) { + if (this._pointsInPolygons[j][i]) { + this._regions[j].diffs++; + } + } + } else if (diff === true) { + this._diffs++; + } + } + } + if (this._regions) { + const regionDiffArray = []; + for (let i = 0; i < this._regionsLength; i++) { + const percent = Math.floor(100 * this._regions[i].diffs / this._regions[i].pointsLength); + if (percent >= this._regions[i].percent) { + regionDiffArray.push({name: this._regions[i].name, percent: percent}); + } + this._regions[i].diffs = 0; + } + if (regionDiffArray.length > 0) { + const data = {trigger: regionDiffArray, pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + } else { + const percent = Math.floor(100 * this._diffs / this._length); + if (percent >= this._percent) { + const data = {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + this._diffs = 0; + } + this._oldPix = this._newPix; + } + + /** + * + * @param chunk + * @private + */ + _grayScalePixelDiff(chunk) { + this._newPix = chunk.pixels; + for (let j = 0; j < this._regionsLength; j++) { + this._regions[j].topLeft = { + x: this._width, + y: this._height + } + this._regions[j].bottomRight = { + x: 0, + y: 0 + } + } + this.topLeft = { + x: this._width, + y: this._height + } + this.bottomRight = { + x: 0, + y: 0 + } + for (let y = 0, i = 0; y < this._height; y++) { + for (let x = 0; x < this._width; x++, i++) { + if (this._oldPix[i] !== this._newPix[i]) { + const diff = Math.abs(this._oldPix[i] - this._newPix[i]); + if (this._regions && diff >= this._minDiff) { + for (let j = 0; j < this._regionsLength; j++) { + if (this._pointsInPolygons[j][i] && diff >= this._regions[j].difference) { + var theRegion = this._regions[j] + theRegion.diffs++; + if(theRegion.topLeft.x > x && theRegion.topLeft.y > y){ + theRegion.topLeft.x = x + theRegion.topLeft.y = y + } + if(theRegion.bottomRight.x < x && theRegion.bottomRight.y < y){ + theRegion.bottomRight.x = x + theRegion.bottomRight.y = y + } + } + } + } else if (diff >= this._difference) { + this._diffs++; + if(this.topLeft.x > x && this.topLeft.y > y){ + this.topLeft.x = x + this.topLeft.y = y + } + if(this.bottomRight.x < x && this.bottomRight.y < y){ + this.bottomRight.x = x + this.bottomRight.y = y + } + } + } + } + } + if (this._regions) { + const regionDiffArray = []; + for (let i = 0; i < this._regionsLength; i++) { + var thisRegion = this._regions[i] + const percent = Math.floor(100 * thisRegion.diffs / thisRegion.pointsLength); + if (percent >= thisRegion.percent) { + // create matrix from points >> + thisRegion._matrix = _getMatrixFromPoints(thisRegion) + // create matrix from points />> + regionDiffArray.push({name: thisRegion.name, percent: percent, matrix: thisRegion._matrix}); + } + thisRegion.diffs = 0; + } + if (regionDiffArray.length > 0) { + this._matrix = _getMatrixFromPoints(this) + const data = {trigger: regionDiffArray, pam: chunk.pam, matrix: this._matrix}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + } else { + const percent = Math.floor(100 * this._diffs / this._length); + if (percent >= this._percent) { + this._matrix = _getMatrixFromPoints(this) + const data = {trigger: [{name: 'percent', percent: percent, matrix: this._matrix}], pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + this._diffs = 0; + } + this._oldPix = this._newPix; + } + + /** + * + * @param chunk + * @private + */ + _rgbPixelDiff(chunk) { + this._newPix = chunk.pixels; + for (let y = 0, i = 0, p = 0; y < this._height; y++) { + for (let x = 0; x < this._width; x++, i += 3, p++) { + if (this._oldPix[i] !== this._newPix[i] || this._oldPix[i + 1] !== this._newPix[i + 1] || this._oldPix[i + 2] !== this._newPix[i + 2]) { + const diff = Math.abs(this._oldPix[i] + this._oldPix[i + 1] + this._oldPix[i + 2] - this._newPix[i] - this._newPix[i + 1] - this._newPix[i + 2])/3; + if (this._regions && diff >= this._minDiff) { + for (let j = 0; j < this._regionsLength; j++) { + if (this._pointsInPolygons[j][p] && diff >= this._regions[j].difference) { + this._regions[j].diffs++; + } + } + } else { + if (diff >= this._difference) { + this._diffs++; + } + } + } + } + } + if (this._regions) { + const regionDiffArray = []; + for (let i = 0; i < this._regionsLength; i++) { + const percent = Math.floor(100 * this._regions[i].diffs / this._regions[i].pointsLength); + if (percent >= this._regions[i].percent) { + regionDiffArray.push({name: this._regions[i].name, percent: percent}); + } + this._regions[i].diffs = 0; + } + if (regionDiffArray.length > 0) { + const data = {trigger: regionDiffArray, pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + } else { + const percent = Math.floor(100 * this._diffs / this._length); + if (percent >= this._percent) { + const data = {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + this._diffs = 0; + } + this._oldPix = this._newPix; + } + + /** + * + * @param chunk + * @private + */ + _rgbAlphaPixelDiff(chunk) { + this._newPix = chunk.pixels; + for (let y = 0, i = 0, p = 0; y < this._height; y++) { + for (let x = 0; x < this._width; x++, i += 4, p++) { + if (this._oldPix[i] !== this._newPix[i] || this._oldPix[i + 1] !== this._newPix[i + 1] || this._oldPix[i + 2] !== this._newPix[i + 2]) { + const diff = Math.abs(this._oldPix[i] + this._oldPix[i + 1] + this._oldPix[i + 2] - this._newPix[i] - this._newPix[i + 1] - this._newPix[i + 2])/3; + if (this._regions && diff >= this._minDiff) { + for (let j = 0; j < this._regionsLength; j++) { + if (this._pointsInPolygons[j][p] && diff >= this._regions[j].difference) { + this._regions[j].diffs++; + } + } + } else { + if (diff >= this._difference) { + this._diffs++; + } + } + } + } + } + if (this._regions) { + const regionDiffArray = []; + for (let i = 0; i < this._regionsLength; i++) { + const percent = Math.floor(100 * this._regions[i].diffs / this._regions[i].pointsLength); + if (percent >= this._regions[i].percent) { + regionDiffArray.push({name: this._regions[i].name, percent: percent}); + } + this._regions[i].diffs = 0; + } + if (regionDiffArray.length > 0) { + const data = {trigger: regionDiffArray, pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + } else { + const percent = Math.floor(100 * this._diffs / this._length); + if (percent >= this._percent) { + const data = {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam}; + if (this._callback) { + this._callback(data); + } + if (this._readableState.pipesCount > 0) { + this.push(data); + } + if (this.listenerCount('diff') > 0) { + this.emit('diff', data); + } + } + this._diffs = 0; + } + this._oldPix = this._newPix; + } + + /** + * + * @param chunk + * @private + */ + _parseFirstChunk(chunk) { + this._width = parseInt(chunk.width); + this._height = parseInt(chunk.height); + this._oldPix = chunk.pixels; + this._length = this._width * this._height; + this._createPointsInPolygons(this._regions, this._width, this._height); + switch (chunk.tupltype) { + case 'blackandwhite' : + this._parseChunk = this._blackAndWhitePixelDiff; + break; + case 'grayscale' : + this._parseChunk = this._grayScalePixelDiff; + break; + case 'rgb' : + this._parseChunk = this._rgbPixelDiff; + //this._increment = 3;//future use + break; + case 'rgb_alpha' : + this._parseChunk = this._rgbAlphaPixelDiff; + //this._increment = 4;//future use + break; + default : + throw Error(`Unsupported tupltype: ${chunk.tupltype}. Supported tupltypes include grayscale(gray), blackandwhite(monob), rgb(rgb24), and rgb_alpha(rgba).`); + } + } + + /** + * + * @param chunk + * @param encoding + * @param callback + * @private + */ + _transform(chunk, encoding, callback) { + this._parseChunk(chunk); + callback(); + } + + /** + * + * @param callback + * @private + */ + _flush(callback) { + this.resetCache(); + callback(); + } +} + +/** + * + * @type {PamDiff} + */ +module.exports = PamDiff; +//todo get bounding box of all regions combined to exclude some pixels before checking if they exist inside specific regions diff --git a/libs/events.js b/libs/events.js index b2dd494..9682fa7 100644 --- a/libs/events.js +++ b/libs/events.js @@ -12,9 +12,7 @@ module.exports = function(s,config,lang){ }) break; case'delete': - d.videos.forEach(function(v,n){ - s.deleteVideo(v) - }) + s.deleteListOfVideos(d.videos) break; case'execute': exec(d.execute,{detached: true}) @@ -373,6 +371,7 @@ module.exports = function(s,config,lang){ 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) + clearTimeout(s.group[d.ke].mon[d.id].eventBasedRecording.timeout) delete(s.group[d.ke].mon[d.id].eventBasedRecording.timeout) clearTimeout(s.group[d.ke].mon[d.id].recordingChecker) }) diff --git a/libs/ffmpeg.js b/libs/ffmpeg.js index 0c338fe..409156e 100644 --- a/libs/ffmpeg.js +++ b/libs/ffmpeg.js @@ -526,12 +526,9 @@ module.exports = function(s,config,onFinish){ //add input feed map x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.stream) } - console.log('x.cust_stream',x.cust_stream) - if(x.stream_fps && (e.details.stream_vcodec !== 'copy' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64')){ x.cust_stream += x.stream_fps } - console.log('x.cust_stream',x.cust_stream) 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' diff --git a/libs/folders.js b/libs/folders.js index d73b10b..b2341d5 100644 --- a/libs/folders.js +++ b/libs/folders.js @@ -1,31 +1,31 @@ var fs = require('fs'); module.exports = function(s,config){ //directories - s.group={}; + 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' + if(s.isWin === false){ + config.streamDir = '/dev/shm' }else{ - config.streamDir=config.windowsTempDir + config.streamDir = config.windowsTempDir } if(!fs.existsSync(config.streamDir)){ - config.streamDir=s.mainDirectory+'/streams/' + config.streamDir = s.mainDirectory+'/streams/' }else{ - config.streamDir+='/streams/' + 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+'/' + 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)){ @@ -41,7 +41,7 @@ module.exports = function(s,config){ } //additional storage areas s.dir.addStorage.forEach(function(v,n){ - v.path=s.checkCorrectPathEnding(v.path) + v.path = s.checkCorrectPathEnding(v.path) if(!fs.existsSync(v.path)){ fs.mkdirSync(v.path); } diff --git a/libs/monitor.js b/libs/monitor.js index 7cceed9..1c0a425 100644 --- a/libs/monitor.js +++ b/libs/monitor.js @@ -73,6 +73,16 @@ module.exports = function(s,config,lang){ if(noPath !== true)url += e.path return url } + s.cleanMonitorObjectForDatabase = function(dirtyMonitor){ + var cleanMonitor = {} + var acceptedFields = ['mid','ke','name','shto','shfr','details','type','ext','protocol','host','path','port','fps','mode','width','height'] + Object.keys(dirtyMonitor).forEach(function(key){ + if(acceptedFields.indexOf(key) > -1){ + cleanMonitor[key] = dirtyMonitor[key] + } + }) + return cleanMonitor + } s.cleanMonitorObject = function(e){ x={keys:Object.keys(e),ar:{}}; x.keys.forEach(function(v){ @@ -89,18 +99,28 @@ module.exports = function(s,config,lang){ } var url var runExtraction = function(){ - var snapBuffer = [] - var snapProcess = spawn(config.ffmpegDir,('-loglevel quiet -re -i '+url+options+' -frames:v 1 -f mjpeg pipe:1').split(' '),{detached: true}) - snapProcess.stdout.on('data',function(data){ - snapBuffer.push(data) - }); - snapProcess.stderr.on('data',function(data){ - console.log(data.toString()) - }); - snapProcess.on('close',function(data){ - snapBuffer = Buffer.concat(snapBuffer) - callback(snapBuffer,false) - }) + try{ + var snapBuffer = [] + var snapProcess = spawn(config.ffmpegDir,('-loglevel quiet -re -i '+url+options+' -frames:v 1 -f image2pipe pipe:1').split(' '),{detached: true}) + snapProcess.stdout.on('data',function(data){ + snapBuffer.push(data) + }) + snapProcess.stderr.on('data',function(data){ + console.log(data.toString()) + }) + snapProcess.on('exit',function(data){ + clearTimeout(snapProcessTimeout) + snapBuffer = Buffer.concat(snapBuffer) + callback(snapBuffer,false) + }) + var snapProcessTimeout = setTimeout(function(){ + snapProcess.stdin.setEncoding('utf8') + snapProcess.stdin.write('q') + delete(snapProcessTimeout) + },5000) + }catch(err){ + callback(fs.readFileSync(config.defaultMjpeg,'binary'),false) + } } var checkExists = function(localStream,callback){ fs.stat(localStream,function(err){ @@ -230,6 +250,7 @@ module.exports = function(s,config,lang){ clearTimeout(s.group[e.ke].mon[e.id].watchdog_stop); delete(s.group[e.ke].mon[e.id].watchdog_stop); delete(s.group[e.ke].mon[e.id].lastJpegDetectorFrame); + delete(s.group[e.ke].mon[e.id].detectorFrameSaveBuffer); clearTimeout(s.group[e.ke].mon[e.id].recordingSnapper); clearInterval(s.group[e.ke].mon[e.id].getMonitorCpuUsage); if(s.group[e.ke].mon[e.id].onChildNodeExit){ @@ -377,7 +398,7 @@ module.exports = function(s,config,lang){ } } //create onvif connection - if(!s.group[e.ke].mon[e.id].onvifConnection){ + if(!s.group[e.ke].mon[e.id].onvifConnection || !s.group[e.ke].mon[e.id].onvifConnection.current_profile || !s.group[e.ke].mon[e.id].onvifConnection.current_profile.token){ s.group[e.ke].mon[e.id].onvifConnection = new onvif.OnvifDevice({ xaddr : 'http://' + controlURLOptions.host + ':' + controlURLOptions.port + '/onvif/device_service', user : controlURLOptions.username, @@ -494,8 +515,8 @@ module.exports = function(s,config,lang){ } s.cameraSendSnapshot = function(e){ s.checkDetails(e) - if(config.doSnapshot===true){ - if(e.mon.mode!=='stop'){ + if(config.doSnapshot === true){ + if(e.mon.mode !== 'stop'){ var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/' fs.stat(pathDir+'icon.jpg',function(err){ if(!err){ @@ -505,36 +526,22 @@ module.exports = function(s,config,lang){ }) }else{ e.url = s.buildMonitorUrl(e.mon) - switch(e.mon.type){ - case'mjpeg':case'h264':case'local': - if(e.mon.type==='local'){e.url=e.mon.path;} - s.getRawSnapshotFromMonitor(e.mon,'-s 200x200',function(data,isStaticFile){ - if((data[data.length-2] === 0xFF && data[data.length-1] === 0xD9)){ - if(!isStaticFile){ - fs.writeFile(s.dir.streams+e.ke+'/'+e.mid+'/icon.jpg',data,function(){}) - } - s.tx({ - f:'monitor_snapshot', - snapshot:data.toString('base64'), - snapshot_format:'b64', - mid:e.mid, - ke:e.ke - },'GRP_'+e.ke) - }else{ - s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) - } - }) - break; - case'jpeg': - request({url:e.url,method:'GET',encoding:null},function(err,data){ - if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return}; - s.tx({f:'monitor_snapshot',snapshot:data.body,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke) - }) - break; - default: - s.tx({f:'monitor_snapshot',snapshot:'...',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) - break; - } + s.getRawSnapshotFromMonitor(e.mon,'-s 200x200',function(data,isStaticFile){ + if((data[data.length-2] === 0xFF && data[data.length-1] === 0xD9)){ + if(!isStaticFile){ + fs.writeFile(s.dir.streams+e.ke+'/'+e.mid+'/icon.jpg',data,function(){}) + } + s.tx({ + f: 'monitor_snapshot', + snapshot: data.toString('base64'), + snapshot_format: 'b64', + mid: e.mid, + ke: e.ke + },'GRP_'+e.ke) + }else{ + s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke) + } + }) } }) }else{ @@ -777,10 +784,34 @@ module.exports = function(s,config,lang){ s.group[e.ke].mon[e.id].lastJpegDetectorFrame = d }) } - }else{ - s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){ - s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d}); - }) + }else if(s.ocv){ + if(s.ocv.connectionType !== 'ram'){ + s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){ + s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d}); + }) + }else{ + s.group[e.ke].mon[e.id].spawn.stdio[3].on('data',function(d){ + if(!s.group[e.ke].mon[e.id].detectorFrameSaveBuffer){ + s.group[e.ke].mon[e.id].detectorFrameSaveBuffer=[d] + }else{ + s.group[e.ke].mon[e.id].detectorFrameSaveBuffer.push(d) + } + if(d[d.length-2] === 0xFF && d[d.length-1] === 0xD9){ + var buffer = Buffer.concat(s.group[e.ke].mon[e.id].detectorFrameSaveBuffer); + var frameLocation = s.dir.streams + e.ke + '/' + e.id + '/' + s.gid(5) + '.jpg' + if(s.ocv){ + fs.writeFile(frameLocation,buffer,function(err){ + if(err){ + s.debugLog(err) + }else{ + s.ocvTx({f:'frameFromRam',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frameLocation:frameLocation}) + } + }) + } + s.group[e.ke].mon[e.id].detectorFrameSaveBuffer = null; + } + }) + } } } //frames to stream @@ -1192,6 +1223,92 @@ module.exports = function(s,config,lang){ }catch(err){} return false } + s.addOrEditMonitor = function(form,callback,user){ + var endData = { + ok: false + } + if(!form.mid){ + endData.msg = lang['No Monitor ID Present in Form'] + callback(endData) + return + } + form.mid = form.mid.replace(/[^\w\s]/gi,'').replace(/ /g,'') + form = s.cleanMonitorObjectForDatabase(form) + s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[form.ke,form.mid],function(er,r){ + var affectMonitor = false + var monitorQuery = [] + var monitorQueryValues = [] + var txData = { + f: 'monitor_edit', + mid: form.mid, + ke: form.ke, + mon: form + } + if(r&&r[0]){ + txData.new = false + Object.keys(form).forEach(function(v){ + if(form[v]&&form[v]!==''){ + monitorQuery.push(v+'=?') + if(form[v] instanceof Object){ + form[v] = s.s(form[v]) + } + monitorQueryValues.push(form[v]) + } + }) + monitorQuery = monitorQuery.join(',') + monitorQueryValues.push(form.ke) + monitorQueryValues.push(form.mid) + s.userLog(form,{type:'Monitor Updated',msg:'by user : '+user.uid}) + endData.msg = user.lang['Monitor Updated by user']+' : '+user.uid + s.sqlQuery('UPDATE Monitors SET '+monitorQuery+' WHERE ke=? AND mid=?',monitorQueryValues) + affectMonitor = true + }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) + ){ + txData.new = true + monitorQueryInsertValues = [] + Object.keys(form).forEach(function(v){ + if(form[v] && form[v] !== ''){ + monitorQuery.push(v) + monitorQueryInsertValues.push('?') + if(form[v] instanceof Object){ + form[v] = s.s(form[v]) + } + monitorQueryValues.push(form[v]) + } + }) + monitorQuery = monitorQuery.join(',') + monitorQueryInsertValues = monitorQueryInsertValues.join(',') + s.userLog(form,{type:'Monitor Added',msg:'by user : '+user.uid}) + endData.msg = user.lang['Monitor Added by user']+' : '+user.uid + s.sqlQuery('INSERT INTO Monitors ('+monitorQuery+') VALUES ('+monitorQueryInsertValues+')',monitorQueryValues) + affectMonitor = true + }else{ + txData.f = 'monitor_edit_failed' + txData.ff = 'max_reached' + endData.msg = user.lang.monitorEditFailedMaxReached + } + if(affectMonitor === true){ + form.details = JSON.parse(form.details) + endData.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(txData,'STR_'+form.ke) + } + s.tx(txData,'GRP_'+form.ke) + callback(!endData.ok,endData) + }) + } s.camera = function(x,e,cn){ // x = function or mode // e = monitor object diff --git a/libs/plugins.js b/libs/plugins.js index 44f989e..24eaf51 100644 --- a/libs/plugins.js +++ b/libs/plugins.js @@ -30,8 +30,15 @@ module.exports = function(s,config,lang){ 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.ocv = { + started: s.timeObject(), + id: cn.id, + plug: d.plug, + notice: d.notice, + isClientPlugin: true, + connectionType: d.connectionType + }; + cn.ocv = 1; s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU') break; } @@ -39,7 +46,14 @@ module.exports = function(s,config,lang){ //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}; + s.ocv = { + started:s.timeObject(), + id:"host", + plug:d.plug, + notice:d.notice, + isHostPlugin:true, + connectionType: d.connectionType + }; break; } } diff --git a/libs/socketio.js b/libs/socketio.js index 0f7058d..50e8031 100644 --- a/libs/socketio.js +++ b/libs/socketio.js @@ -6,6 +6,7 @@ var spawn = require('child_process').spawn; var jsonfile = require("jsonfile"); var onvif = require("node-onvif"); module.exports = function(s,config,lang,io){ + s.clientSocketConnection = {} //send data to detector plugin s.ocvTx=function(data){ if(!s.ocv){return} @@ -426,7 +427,15 @@ module.exports = function(s,config,lang,io){ // if(!s.group[d.ke].vid)s.group[d.ke].vid={}; if(!s.group[d.ke].users)s.group[d.ke].users={}; // s.group[d.ke].vid[cn.id]={uid:d.uid}; - s.group[d.ke].users[d.auth]={cnid:cn.id,uid:r.uid,mail:r.mail,details:JSON.parse(r.details),logged_in_at:s.timeObject(new Date).format(),login_type:'Dashboard'} + s.group[d.ke].users[d.auth] = { + cnid: cn.id, + uid: r.uid, + mail: r.mail, + details: JSON.parse(r.details), + logged_in_at: s.timeObject(new Date).format(), + login_type: 'Dashboard' + } + s.clientSocketConnection[cn.id] = cn try{s.group[d.ke].users[d.auth].details=JSON.parse(r.details)}catch(er){} if(s.group[d.ke].users[d.auth].details.get_server_log!=='0'){ cn.join('GRPLOG_'+d.ke) @@ -588,75 +597,8 @@ module.exports = function(s,config,lang,io){ } break; case'edit': - s.sqlQuery('SELECT details FROM Users WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,r){ - if(r&&r[0]){ - r=r[0]; - d.d=JSON.parse(r.details); - 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) - } - ///unchangeable from client side, so reset them in case they did. - d.form.details=JSON.parse(d.form.details) - s.beforeAccountSaveExtensions.forEach(function(extender){ - extender(d) - }) - //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_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 || 10000 - d.form.details=JSON.stringify(d.form.details) - /// - d.set=[],d.ar=[]; - if(d.form.pass&&d.form.pass!==''){d.form.pass=s.createHash(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){ - var user = Object.assign(d.form,{ke : d.ke}) - var userDetails = JSON.parse(d.form.details) - s.group[d.ke].sizeLimit = parseFloat(newSize) - s.onAccountSaveExtensions.forEach(function(extender){ - extender(s.group[d.ke],userDetails) - }) - s.unloadGroupAppExtensions.forEach(function(extender){ - extender(user) - }) - s.loadGroupApps(d) - } - tx({f:'user_settings_change',uid:d.uid,ke:d.ke,form:d.form}); - }); - } - } - }) + d.cnid = cn.id + s.accountSettingsEdit(d) break; } break; @@ -1428,6 +1370,7 @@ module.exports = function(s,config,lang,io){ if(cn.superSessionKey){ delete(s.superUsersApi[cn.superSessionKey]) } + delete(s.clientSocketConnection[cn.id]) }) }); } diff --git a/libs/sql.js b/libs/sql.js index 398fbeb..d9d3d7b 100644 --- a/libs/sql.js +++ b/libs/sql.js @@ -50,6 +50,9 @@ module.exports = function(s,config){ if(!onMoveOn){onMoveOn=function(){}} var mergedQuery = s.mergeQueryValues(query,values) s.debugLog('s.sqlQuery QUERY',mergedQuery) + if(!s.databaseEngine || !s.databaseEngine.raw){ + s.connectDatabase() + } return s.databaseEngine .raw(query,values) .asCallback(function(err,r){ @@ -70,11 +73,18 @@ module.exports = function(s,config){ } }) } + s.connectDatabase = function(){ + s.databaseEngine = require('knex')(s.databaseOptions) + } 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) + //add monitorStates to Preset ENUM + s.sqlQuery('ALTER TABLE `Presets` CHANGE COLUMN `type` `type` VARCHAR(50) NULL DEFAULT NULL AFTER `details`;',[],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) diff --git a/libs/startup.js b/libs/startup.js index f6d796b..3a7b937 100644 --- a/libs/startup.js +++ b/libs/startup.js @@ -29,6 +29,7 @@ module.exports = function(s,config,lang,io){ orphanedVideosForMonitors[monitor.ke][monitor.mid] += orphanedFilesCount } s.group[monitor.ke].mon_conf[monitor.mid] = monitor + s.sendMonitorStatus({id:monitor.mid,ke:monitor.ke,status:'Stopped'}); var monObj = Object.assign(monitor,{id : monitor.mid}) s.camera(monitor.mode,monObj) ++loadCompleted diff --git a/libs/user.js b/libs/user.js index 1ed2e34..6c595ac 100644 --- a/libs/user.js +++ b/libs/user.js @@ -230,6 +230,79 @@ module.exports = function(s,config){ s.group[e.ke].init[v]=ar[v] }) } - }); + }) + } + s.accountSettingsEdit = function(d){ + s.sqlQuery('SELECT details FROM Users WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,r){ + if(r&&r[0]){ + r=r[0]; + d.d=JSON.parse(r.details); + if(!d.d.sub || d.d.user_change !== "0"){ + if(d.cnid){ + if(d.d.get_server_log==='1'){ + s.clientSocketConnection[d.cnid].join('GRPLOG_'+d.ke) + }else{ + s.clientSocketConnection[d.cnid].leave('GRPLOG_'+d.ke) + } + } + ///unchangeable from client side, so reset them in case they did. + d.form.details=JSON.parse(d.form.details) + s.beforeAccountSaveExtensions.forEach(function(extender){ + extender(d) + }) + //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_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 || 10000 + d.form.details=JSON.stringify(d.form.details) + /// + d.set=[],d.ar=[]; + if(d.form.pass&&d.form.pass!==''){d.form.pass=s.createHash(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){ + var user = Object.assign(d.form,{ke : d.ke}) + var userDetails = JSON.parse(d.form.details) + s.group[d.ke].sizeLimit = parseFloat(newSize) + s.onAccountSaveExtensions.forEach(function(extender){ + extender(s.group[d.ke],userDetails) + }) + s.unloadGroupAppExtensions.forEach(function(extender){ + extender(user) + }) + s.loadGroupApps(d) + } + if(d.cnid)s.tx({f:'user_settings_change',uid:d.uid,ke:d.ke,form:d.form},d.cnid) + }) + } + } + }) } } diff --git a/libs/videos.js b/libs/videos.js index 614994f..4b54c34 100644 --- a/libs/videos.js +++ b/libs/videos.js @@ -230,17 +230,69 @@ module.exports = function(s,config,lang){ }) }) }) - 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(new Error()) console.log(lang['Database row does not exist'],queryValues) } }) } + s.deleteListOfVideos = function(videos){ + var query = 'DELETE FROM Videos WHERE ' + var videoQuery = [] + var queryValues = [] + videos.forEach(function(video){ + s.checkDetails(video) + //e = video object + video.dir = s.getVideoDirectory(video) + if(!video.filename && video.time){ + video.filename = s.formattedTime(video.time) + } + var filename, + time + if(video.filename.indexOf('.')>-1){ + filename = video.filename + }else{ + filename = video.filename+'.'+video.ext + } + if(video.filename && !video.time){ + time = s.nameToTime(filename) + }else{ + time = video.time + } + time = new Date(time) + fs.chmod(video.dir+filename,0o777,function(err){ + s.tx({ + f: 'video_delete', + filename: filename, + mid: video.id, + ke: video.ke, + time: s.nameToTime(filename), + end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss') + },'GRP_'+video.ke); + s.setDiskUsedForGroup(video,-(video.size / 1000000)) + fs.unlink(video.dir+filename,function(err){ + fs.stat(video.dir+filename,function(err){ + if(!err){ + s.file('delete',video.dir+filename) + } + }) + }) + }) + videoQuery.push('(`mid`=? AND `ke`=? AND `time`=?)') + queryValues = queryValues.concat([video.id,video.ke,time]) + }) + query += videoQuery.join(' OR ') + s.sqlQuery(query,queryValues,function(err){ + if(err){ + s.systemLog(lang['List of Videos Delete Error'],err) + } + }) + } s.deleteVideoFromCloudExtensions = {} s.deleteVideoFromCloudExtensionsRunner = function(e,storageType,video){ // e = user diff --git a/libs/webServer.js b/libs/webServer.js index bc86631..9cc5363 100644 --- a/libs/webServer.js +++ b/libs/webServer.js @@ -4,7 +4,52 @@ var https = require('https'); var express = require('express'); var app = express() module.exports = function(s,config,lang,io){ - var server = http.createServer(app); + //get page URL + if(!config.baseURL){ + config.baseURL = "" + }else if(config.baseURL !== ''){ + config.baseURL = s.checkCorrectPathEnding(config.baseURL) + } + //Render Configurations - Web Paths + if(config.webPaths === undefined){config.webPaths={}} + //main access URI + if(config.webPaths.home === undefined){config.webPaths.home='/'} + //Super User URI + if(config.webPaths.super === undefined){config.webPaths.super='/super'} + //Admin URI + if(config.webPaths.admin === undefined){config.webPaths.admin='/admin'} + //Libraries URI + if(config.webPaths.libs === undefined){config.webPaths.libs='/libs'} + //API Prefix + if(config.webPaths.apiPrefix === undefined){config.webPaths.apiPrefix = s.checkCorrectPathEnding(config.webPaths.home)}else{config.webPaths.apiPrefix = s.checkCorrectPathEnding(config.webPaths.apiPrefix)} + //Admin API Prefix + if(config.webPaths.adminApiPrefix === undefined){config.webPaths.adminApiPrefix=s.checkCorrectPathEnding(config.webPaths.admin)}else{config.webPaths.adminApiPrefix = s.checkCorrectPathEnding(config.webPaths.adminApiPrefix)} + //Super API Prefix + if(config.webPaths.superApiPrefix === undefined){config.webPaths.superApiPrefix=s.checkCorrectPathEnding(config.webPaths.super)}else{config.webPaths.superApiPrefix = s.checkCorrectPathEnding(config.webPaths.superApiPrefix)} + //Render Configurations - Page Render Paths + if(config.renderPaths === undefined){config.renderPaths={}} + //login page + if(config.renderPaths.index === undefined){config.renderPaths.index='pages/index'} + //dashboard page + if(config.renderPaths.home === undefined){config.renderPaths.home='pages/home'} + //sub-account administration page + if(config.renderPaths.admin === undefined){config.renderPaths.admin='pages/admin'} + //superuser page + if(config.renderPaths.super === undefined){config.renderPaths.super='pages/super'} + //2-Factor Auth page + if(config.renderPaths.factorAuth === undefined){config.renderPaths.factorAuth='pages/factor'} + //Streamer v1 (Dashcam Prototype) page + if(config.renderPaths.streamer === undefined){config.renderPaths.streamer='pages/streamer'} + //Streamer v2 (Dashcam) page + if(config.renderPaths.dashcam === undefined){config.renderPaths.dashcam='pages/dashcam'} + //embeddable widget page + if(config.renderPaths.embed === undefined){config.renderPaths.embed='pages/embed'} + //mjpeg full screen page + if(config.renderPaths.mjpeg === undefined){config.renderPaths.mjpeg='pages/mjpeg'} + //gridstack only page + if(config.renderPaths.grid === undefined){config.renderPaths.grid='pages/grid'} + //slick.js (cycle) page + if(config.renderPaths.cycle === undefined){config.renderPaths.cycle='pages/cycle'} //SSL options if(config.ssl&&config.ssl.key&&config.ssl.cert){ config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8') @@ -24,12 +69,47 @@ module.exports = function(s,config,lang,io){ serverHTTPS.listen(config.ssl.port,config.bindip,function(){ console.log('SSL '+lang.Shinobi+' : SSL Web Server Listening on '+config.ssl.port); }); - io.attach(serverHTTPS); + if(config.webPaths.home !== '/'){ + io.attach(serverHTTPS,{ + path:'/socket.io', + transports: ['websocket'] + }) + } + io.attach(serverHTTPS,{ + path:s.checkCorrectPathEnding(config.webPaths.home)+'socket.io', + transports: ['websocket'] + }) + io.attach(serverHTTPS,{ + path:s.checkCorrectPathEnding(config.webPaths.admin)+'socket.io', + transports: ['websocket'] + }) + io.attach(serverHTTPS,{ + path:s.checkCorrectPathEnding(config.webPaths.super)+'socket.io', + transports: ['websocket'] + }) } //start HTTP + var server = http.createServer(app); server.listen(config.port,config.bindip,function(){ console.log(lang.Shinobi+' : Web Server Listening on '+config.port); }); - io.attach(server); + if(config.webPaths.home !== '/'){ + io.attach(server,{ + path:'/socket.io', + transports: ['websocket'] + }) + } + io.attach(server,{ + path:s.checkCorrectPathEnding(config.webPaths.home)+'socket.io', + transports: ['websocket'] + }) + io.attach(server,{ + path:s.checkCorrectPathEnding(config.webPaths.admin)+'socket.io', + transports: ['websocket'] + }) + io.attach(server,{ + path:s.checkCorrectPathEnding(config.webPaths.super)+'socket.io', + transports: ['websocket'] + }) return app } diff --git a/libs/webServerAdminPaths.js b/libs/webServerAdminPaths.js index 3cc62e2..7cebd97 100644 --- a/libs/webServerAdminPaths.js +++ b/libs/webServerAdminPaths.js @@ -7,10 +7,6 @@ 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) */ @@ -21,7 +17,7 @@ module.exports = function(s,config,lang,app){ } if(user.details.sub){ endData.msg = user.lang['Not Permitted'] - closeResponse(res,endData) + s.closeJsonResponse(res,endData) return } var form = s.getPostData(req) @@ -56,7 +52,7 @@ module.exports = function(s,config,lang,app){ }else{ endData.msg = lang.postDataBroken } - closeResponse(res,endData) + s.closeJsonResponse(res,endData) },res,req) }) /** @@ -69,7 +65,7 @@ module.exports = function(s,config,lang,app){ } if(user.details.sub){ endData.msg = user.lang['Not Permitted'] - closeResponse(res,endData) + s.closeJsonResponse(res,endData) return } var uid = s.getPostData(req,'uid',false) @@ -90,7 +86,7 @@ module.exports = function(s,config,lang,app){ mail: mail },'ADM_'+req.params.ke) endData.ok = true - closeResponse(res,endData) + s.closeJsonResponse(res,endData) },res,req) }) /** @@ -109,7 +105,7 @@ module.exports = function(s,config,lang,app){ s.auth(req.params,function(user){ if(user.details.sub){ endData.msg = user.lang['Not an Administrator Account'] - closeResponse(res,endData) + s.closeJsonResponse(res,endData) return } var form = s.getPostData(req) @@ -159,7 +155,9 @@ module.exports = function(s,config,lang,app){ config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id', config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id/:f' ], function (req,res){ - req.ret={ok:false}; + 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){ @@ -167,88 +165,28 @@ module.exports = function(s,config,lang,app){ 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 + endData.msg = user.lang.monitorEditText1; + res.end(s.prettyPrint(endData)) + return } + form.mid = req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,'') 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)) - }) + if(form && form.name){ + s.checkDetails(form) + form.ke = req.params.ke + s.addOrEditMonitor(form,function(err,endData){ + res.end(s.prettyPrint(endData)) + },user) }else{ - req.ret.msg=user.lang.monitorEditText1; - res.end(s.prettyPrint(req.ret)) + endData.msg = user.lang.monitorEditText1; + res.end(s.prettyPrint(endData)) } }else{ - req.ret.msg=user.lang['Not Permitted']; - res.end(s.prettyPrint(req.ret)) + endData.msg = user.lang['Not Permitted'] + res.end(s.prettyPrint(endData)) } }else{ if(!user.details.sub || user.details.allmonitors === '1' || user.details.monitor_edit.indexOf(req.params.id) > -1 || hasRestrictions && user.details.monitor_create === '1'){ @@ -281,12 +219,12 @@ module.exports = function(s,config,lang,app){ } }) } - req.ret.ok=true; - req.ret.msg='Monitor Deleted by user : '+user.uid - res.end(s.prettyPrint(req.ret)) + endData.ok=true; + endData.msg='Monitor Deleted by user : '+user.uid + res.end(s.prettyPrint(endData)) }else{ - req.ret.msg=user.lang['Not Permitted']; - res.end(s.prettyPrint(req.ret)) + endData.msg=user.lang['Not Permitted']; + res.end(s.prettyPrint(endData)) } } },res,req) @@ -328,11 +266,11 @@ module.exports = function(s,config,lang,app){ },'GRP_' + req.params.ke) endData.ok = true } - closeResponse(res,endData) + s.closeJsonResponse(res,endData) }) }else{ endData.msg = lang.postDataBroken - closeResponse(res,endData) + s.closeJsonResponse(res,endData) } },res,req) }) @@ -359,7 +297,7 @@ module.exports = function(s,config,lang,app){ form:'APIs' },'GRP_' + req.params.ke) endData.msg = lang.postDataBroken - closeResponse(res,endData) + s.closeJsonResponse(res,endData) return } var row = { @@ -381,11 +319,11 @@ module.exports = function(s,config,lang,app){ endData.ok = true delete(s.api[row.code]) } - closeResponse(res,endData) + s.closeJsonResponse(res,endData) }) }else{ endData.msg = lang.postDataBroken - closeResponse(res,endData) + s.closeJsonResponse(res,endData) } },res,req) }) @@ -421,8 +359,143 @@ module.exports = function(s,config,lang,app){ endData.ke = user.ke endData.keys = rows } - closeResponse(res,endData) + s.closeJsonResponse(res,endData) }) },res,req) }) + /** + * API : Administrator : Change Group Preset. Currently affects Monitors only. + */ + app.all([ + config.webPaths.apiPrefix+':auth/monitorStates/:ke/:stateName', + config.webPaths.apiPrefix+':auth/monitorStates/:ke/:stateName/:action', + config.webPaths.adminApiPrefix+':auth/monitorStates/:ke/:stateName', + config.webPaths.adminApiPrefix+':auth/monitorStates/:ke/:stateName/:action', + ],function (req,res){ + s.auth(req.params,function(user){ + var endData = { + ok : false + } + if(user.details.sub){ + endData.msg = user.lang['Not Permitted'] + s.closeJsonResponse(res,endData) + return + } + var findPreset = function(callback){ + s.sqlQuery("SELECT * FROM Presets WHERE ke=? AND type=? AND name=? LIMIT 1",[req.params.ke,'monitorStates',req.params.stateName],function(err,presets){ + var preset + var notFound = false + if(presets && presets[0]){ + preset = presets[0] + s.checkDetails(preset) + }else{ + notFound = true + } + callback(notFound,preset) + }) + } + switch(req.params.action){ + case'insert':case'edit': + var form = s.getPostData(req) + s.checkDetails(form) + if(!form || !form.monitors){ + endData.msg = user.lang['Form Data Not Found'] + s.closeJsonResponse(res,endData) + return + } + findPreset(function(notFound,preset){ + if(notFound === true){ + endData.msg = lang["Inserted State Configuration"] + var details = { + monitors : form.monitors + } + var insertData = { + ke: req.params.ke, + name: req.params.stateName, + details: s.s(details), + type: 'monitorStates' + } + s.sqlQuery('INSERT INTO Presets ('+Object.keys(insertData).join(',')+') VALUES (?,?,?,?)',Object.values(insertData)) + s.tx({ + f: 'add_group_state', + details: details, + ke: req.params.ke, + name: req.params.stateName + },'GRP_'+req.params.ke) + }else{ + endData.msg = lang["Edited State Configuration"] + var details = Object.assign(preset.details,{ + monitors : form.monitors + }) + s.sqlQuery('UPDATE Presets SET details=? WHERE ke=? AND name=?',[s.s(details),req.params.ke,req.params.stateName]) + s.tx({ + f: 'edit_group_state', + details: details, + ke: req.params.ke, + name: req.params.stateName + },'GRP_'+req.params.ke) + } + endData.ok = true + s.closeJsonResponse(res,endData) + }) + break; + case'delete': + findPreset(function(notFound,preset){ + if(notFound === true){ + endData.msg = user.lang['State Configuration Not Found'] + s.closeJsonResponse(res,endData) + }else{ + s.sqlQuery('DELETE FROM Presets WHERE ke=? AND name=?',[req.params.ke,req.params.stateName],function(err){ + if(!err){ + endData.msg = lang["Deleted State Configuration"] + endData.ok = true + } + s.closeJsonResponse(res,endData) + }) + } + }) + break; + default://change monitors according to state + findPreset(function(notFound,preset){ + if(notFound === false){ + var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND ' + var monitorQuery = [] + var sqlQueryValues = [req.params.ke] + var monitorPresets = {} + preset.details.monitors.forEach(function(monitor){ + monitorQuery.push('mid=?') + sqlQueryValues.push(monitor.mid) + monitorPresets[monitor.mid] = monitor + }) + sqlQuery += '('+monitorQuery.join(' OR ')+')' + s.sqlQuery(sqlQuery,sqlQueryValues,function(err,monitors){ + if(monitors && monitors[0]){ + monitors.forEach(function(monitor){ + s.checkDetails(monitor) + s.checkDetails(monitorPresets[monitor.mid]) + var monitorPreset = monitorPresets[monitor.mid] + monitorPreset.details = Object.assign(monitor.details,monitorPreset.details) + monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset)) + monitor.details = JSON.stringify(monitor.details) + s.addOrEditMonitor(Object.assign(monitor,{}),function(err,endData){ + + },user) + }) + endData.ok = true + s.tx({f:'change_group_state',ke:req.params.ke,name:req.params.stateName},'GRP_'+req.params.ke) + s.closeJsonResponse(res,endData) + }else{ + endData.msg = user.lang['State Configuration has no monitors associated'] + s.closeJsonResponse(res,endData) + } + }) + }else{ + endData.msg = user.lang['State Configuration Not Found'] + s.closeJsonResponse(res,endData) + } + }) + break; + } + },res,req) + }) } diff --git a/libs/webServerPaths.js b/libs/webServerPaths.js index 47b94be..c01f26f 100644 --- a/libs/webServerPaths.js +++ b/libs/webServerPaths.js @@ -11,54 +11,15 @@ 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){ +module.exports = function(s,config,lang,app,io){ if(config.productType==='Pro'){ var LdapAuth = require('ldapauth-fork'); } - //get page URL - if(!config.baseURL){ - config.baseURL = "" - }else if(config.baseURL !== ''){ - config.baseURL = s.checkCorrectPathEnding(config.baseURL) + s.renderPage = function(req,res,paths,passables,callback){ + passables.window = {} + passables.originalURL = s.getOriginalUrl(req) + res.render(paths,passables,callback) } - //Render Configurations - Web Paths - if(config.webPaths === undefined){config.webPaths={}} - //main access URI - if(config.webPaths.home === undefined){config.webPaths.home='/'} - //Super User URI - if(config.webPaths.super === undefined){config.webPaths.super='/super'} - //Admin URI - if(config.webPaths.admin === undefined){config.webPaths.admin='/admin'} - //API Prefix - if(config.webPaths.apiPrefix === undefined){config.webPaths.apiPrefix='/'}else{config.webPaths.apiPrefix = s.checkCorrectPathEnding(config.webPaths.apiPrefix)} - //Admin API Prefix - if(config.webPaths.adminApiPrefix === undefined){config.webPaths.adminApiPrefix='/admin/'}else{config.webPaths.adminApiPrefix = s.checkCorrectPathEnding(config.webPaths.adminApiPrefix)} - //Super API Prefix - if(config.webPaths.superApiPrefix === undefined){config.webPaths.superApiPrefix='/super/'}else{config.webPaths.superApiPrefix = s.checkCorrectPathEnding(config.webPaths.superApiPrefix)} - //Render Configurations - Page Render Paths - if(config.renderPaths === undefined){config.renderPaths={}} - //login page - if(config.renderPaths.index === undefined){config.renderPaths.index='pages/index'} - //dashboard page - if(config.renderPaths.home === undefined){config.renderPaths.home='pages/home'} - //sub-account administration page - if(config.renderPaths.admin === undefined){config.renderPaths.admin='pages/admin'} - //superuser page - if(config.renderPaths.super === undefined){config.renderPaths.super='pages/super'} - //2-Factor Auth page - if(config.renderPaths.factorAuth === undefined){config.renderPaths.factorAuth='pages/factor'} - //Streamer v1 (Dashcam Prototype) page - if(config.renderPaths.streamer === undefined){config.renderPaths.streamer='pages/streamer'} - //Streamer v2 (Dashcam) page - if(config.renderPaths.dashcam === undefined){config.renderPaths.dashcam='pages/dashcam'} - //embeddable widget page - if(config.renderPaths.embed === undefined){config.renderPaths.embed='pages/embed'} - //mjpeg full screen page - if(config.renderPaths.mjpeg === undefined){config.renderPaths.mjpeg='pages/mjpeg'} - //gridstack only page - if(config.renderPaths.grid === undefined){config.renderPaths.grid='pages/grid'} - //slick.js (cycle) page - if(config.renderPaths.cycle === undefined){config.renderPaths.cycle='pages/cycle'} //child node proxy check //params = parameters //cb = callback @@ -72,6 +33,10 @@ module.exports = function(s,config,lang,app){ cb() } } + s.closeJsonResponse = function(res,endData){ + res.setHeader('Content-Type', 'application/json') + res.end(s.prettyPrint(endData)) + } //get post data s.getPostData = function(req,target,parseJSON){ if(!target)target = 'data' @@ -93,13 +58,18 @@ module.exports = function(s,config,lang,app){ } ////Pages app.enable('trust proxy'); - app.use('/libs',express.static(s.mainDirectory + '/web/libs')); + if(config.webPaths.home !== '/'){ + app.use('/libs',express.static(s.mainDirectory + '/web/libs')) + } + app.use(s.checkCorrectPathEnding(config.webPaths.home)+'libs',express.static(s.mainDirectory + '/web/libs')) + app.use(s.checkCorrectPathEnding(config.webPaths.admin)+'libs',express.static(s.mainDirectory + '/web/libs')) + app.use(s.checkCorrectPathEnding(config.webPaths.super)+'libs',express.static(s.mainDirectory + '/web/libs')) app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.set('views', s.mainDirectory + '/web'); app.set('view engine','ejs'); //add template handler - if(config.renderPaths.handler!==undefined){require(s.mainDirectory+'/web/'+config.renderPaths.handler+'.js').addHandlers(s,app,io)} + if(config.renderPaths.handler!==undefined){require(s.mainDirectory+'/web/'+config.renderPaths.handler+'.js').addHandlers(s,app,io,config)} /** * API : Logout @@ -118,7 +88,7 @@ module.exports = function(s,config,lang,app){ * Page : Login Screen */ app.get(config.webPaths.home, function (req,res){ - res.render(config.renderPaths.index,{lang:lang,config:config,screen:'dashboard',originalURL:s.getOriginalUrl(req)},function(err,html){ + s.renderPage(req,res,config.renderPaths.index,{lang:lang,config:config,screen:'dashboard'},function(err,html){ if(err){ s.systemLog(err) } @@ -129,7 +99,7 @@ module.exports = function(s,config,lang,app){ * Page : Administrator Login Screen */ app.get(config.webPaths.admin, function (req,res){ - res.render(config.renderPaths.index,{lang:lang,config:config,screen:'admin',originalURL:s.getOriginalUrl(req)},function(err,html){ + s.renderPage(req,res,config.renderPaths.index,{lang:lang,config:config,screen:'admin'},function(err,html){ if(err){ s.systemLog(err) } @@ -141,7 +111,7 @@ module.exports = function(s,config,lang,app){ */ app.get(config.webPaths.super, function (req,res){ - res.render(config.renderPaths.index,{lang:lang,config:config,screen:'super',originalURL:s.getOriginalUrl(req)},function(err,html){ + s.renderPage(req,res,config.renderPaths.index,{lang:lang,config:config,screen:'super'},function(err,html){ if(err){ s.systemLog(err) } @@ -171,23 +141,48 @@ module.exports = function(s,config,lang,app){ /** * API : Login handler. Dashboard, Streamer, Dashcam Administrator, Superuser */ - app.post([config.webPaths.home,s.checkCorrectPathEnding(config.webPaths.home)+':screen'],function (req,res){ + app.post([ + config.webPaths.home, + config.webPaths.admin, + config.webPaths.super, + s.checkCorrectPathEnding(config.webPaths.home)+':screen', + s.checkCorrectPathEnding(config.webPaths.admin)+':screen', + s.checkCorrectPathEnding(config.webPaths.super)+':screen', + ],function (req,res){ req.ip = s.getClientIp(req) if(req.query.json === 'true'){ res.header("Access-Control-Allow-Origin",req.headers.origin); } + var screenChooser = function(screen){ + var search = function(screen){ + if(req.url.indexOf(screen) > -1){ + return true + } + return false + } + switch(true){ + case search(config.webPaths.admin): + return 'admin' + break; + case search(config.webPaths.super): + return 'super' + break; + default: + return 'dashboard' + break; + } + } // brute check if(s.failedLoginAttempts[req.body.mail] && s.failedLoginAttempts[req.body.mail].failCount >= 5){ if(req.query.json=='true'){ res.end(s.prettyPrint({ok:false})) }else{ - res.render(config.renderPaths.index,{ - failedLogin:true, - message:lang.failedLoginText1, - lang:lang, - config:config, - screen:req.params.screen, - originalURL:s.getOriginalUrl(req) + s.renderPage(req,res,config.renderPaths.index,{ + failedLogin: true, + message: lang.failedLoginText1, + lang: lang, + config: config, + screen: screenChooser(req.params.screen) },function(err,html){ if(err){ s.systemLog(err) @@ -209,9 +204,8 @@ module.exports = function(s,config,lang,app){ res.setHeader('Content-Type', 'application/json'); res.end(s.prettyPrint(data)) }else{ - data.originalURL = s.getOriginalUrl(req) data.screen=req.params.screen - res.render(focus,data,function(err,html){ + s.renderPage(req,res,focus,data,function(err,html){ if(err){ s.systemLog(err) } @@ -241,13 +235,12 @@ module.exports = function(s,config,lang,app){ res.setHeader('Content-Type', 'application/json') res.end(s.prettyPrint({ok:false})) }else{ - res.render(config.renderPaths.index,{ - failedLogin:true, - message:lang.failedLoginText2, - lang:lang, - config:config, - screen:req.params.screen, - originalURL:s.getOriginalUrl(req) + s.renderPage(req,res,config.renderPaths.index,{ + failedLogin: true, + message: lang.failedLoginText2, + lang: lang, + config: config, + screen: screenChooser(req.params.screen) },function(err,html){ if(err){ s.systemLog(err) @@ -344,7 +337,7 @@ module.exports = function(s,config,lang,app){ r.details=JSON.parse(r.details); r.lang=s.getLanguageFile(r.details.lang) req.factorAuth=function(cb){ - if(r.details.factorAuth==="1"){ + if(r.details.factorAuth === "1"){ if(!r.details.acceptedMachines||!(r.details.acceptedMachines instanceof Object)){ r.details.acceptedMachines={} } @@ -671,14 +664,13 @@ module.exports = function(s,config,lang,app){ if(req.path.indexOf('/cycle/') > -1){ page = config.renderPaths.cycle } - res.render(page,{ + s.renderPage(req,res,page,{ data:Object.assign(req.params,req.query), baseUrl:req.protocol+'://'+req.hostname, config:config, lang:user.lang, $user:user, monitors:r, - originalURL:s.getOriginalUrl(req), query:req.query }); }) @@ -1490,17 +1482,22 @@ module.exports = function(s,config,lang,app){ req.ext=req.params.file.split('.')[1]; var total = fs.statSync(req.dir).size; if (req.headers['range']) { - var range = req.headers.range; - var parts = range.replace(/bytes=/, "").split("-"); - var partialstart = parts[0]; - var partialend = parts[1]; - - var start = parseInt(partialstart, 10); - var end = partialend ? parseInt(partialend, 10) : total-1; - var chunksize = (end-start)+1; - var file = fs.createReadStream(req.dir, {start: start, end: end}); - req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext } - req.writeCode=206 + try{ + var range = req.headers.range; + var parts = range.replace(/bytes=/, "").split("-"); + var partialstart = parts[0]; + var partialend = parts[1]; + var start = parseInt(partialstart, 10); + var end = partialend ? parseInt(partialend, 10) : total-1; + var chunksize = (end-start)+1; + var file = fs.createReadStream(req.dir, {start: start, end: end}); + req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext } + req.writeCode=206 + }catch(err){ + req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext}; + var file = fs.createReadStream(req.dir) + req.writeCode=200 + } } else { req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext}; var file=fs.createReadStream(req.dir) @@ -1844,4 +1841,27 @@ module.exports = function(s,config,lang,app){ } },res,req); }) + /** + * API : Account Edit from Dashboard + */ + app.all(config.webPaths.apiPrefix+':auth/accounts/:ke/edit',function (req,res){ + s.auth(req.params,function(user){ + var endData = { + ok : false + } + var form = s.getPostData(req) + if(form){ + endData.ok = true + s.accountSettingsEdit({ + ke: req.params.ke, + uid: user.uid, + form: form, + cnid: user.cnid + }) + }else{ + endData.msg = lang.postDataBroken + } + s.closeJsonResponse(res,endData) + },res,req) + }) } diff --git a/libs/webServerStreamPaths.js b/libs/webServerStreamPaths.js index 7ac6509..ad5e490 100644 --- a/libs/webServerStreamPaths.js +++ b/libs/webServerStreamPaths.js @@ -26,7 +26,7 @@ module.exports = function(s,config,lang,app){ 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)}); + s.renderPage(req,res,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.']) @@ -102,7 +102,7 @@ module.exports = function(s,config,lang,app){ 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)}); + s.renderPage(req,res,config.renderPaths.mjpeg,{url:config.webPaths.apiPrefix + req.params.auth+'/mjpeg/'+req.params.ke+'/'+req.params.id,originalURL:s.getOriginalUrl(req)}); res.end() }else{ s.auth(req.params,function(user){ diff --git a/web/libs/js/basic.js b/web/libs/js/basic.js index 5c98c92..1610677 100644 --- a/web/libs/js/basic.js +++ b/web/libs/js/basic.js @@ -34,3 +34,10 @@ tool.getVideoImage = function (path, secs, callback) { }; video.src = path; } +tool.checkCorrectPathEnding = function(x){ + var length=x.length + if(x.charAt(length-1)!=='/'){ + x=x+'/' + } + return x +} diff --git a/web/libs/js/flv.shinobi.js b/web/libs/js/flv.shinobi.js index 3310137..1ec69c9 100644 --- a/web/libs/js/flv.shinobi.js +++ b/web/libs/js/flv.shinobi.js @@ -3902,7 +3902,8 @@ return (new Date(now.getTime() + now.getTimezoneOffset() * 60000)); } var _this = this; - var t = _this._ws = io(e.url,{ + var t = _this._ws = io(e.config.url,{ + path: e.config.path, transports:['websocket'] }); l._ws = t; @@ -6277,4 +6278,4 @@ }, {}] }, {}, [21])(21) }); -//# sourceMappingURL=flv.min.js.map \ No newline at end of file +//# sourceMappingURL=flv.min.js.map diff --git a/web/libs/js/main.dash2.js b/web/libs/js/main.dash2.js index a5f7f9a..160ad3f 100644 --- a/web/libs/js/main.dash2.js +++ b/web/libs/js/main.dash2.js @@ -7,7 +7,11 @@ window.chartColors = { purple: 'rgb(153, 102, 255)', grey: 'rgb(201, 203, 207)' }; -$user.details=JSON.parse($user.details) +try{ + $user.details = JSON.parse($user.details) +}catch(err){ + +} $.ccio={ fr:$('#files_recent'), mon:{} @@ -209,7 +213,7 @@ switch($user.details.lang){ url=url+'/' } }else{ - url = '<%-originalURL%>' + url = '<%-window.libURL%>' } return url break; @@ -575,7 +579,7 @@ switch($user.details.lang){ k.e=$('#monitor_live_'+d.mid+user.auth_token+' .stream-element'); $.ccio.init('jpegModeStop',d,user); k.run=function(){ - k.e.attr('src',user.auth_token+'/jpeg/'+d.ke+'/'+d.mid+'/s.jpg?time='+(new Date()).getTime()) + k.e.attr('src',$.ccio.init('location',user)+user.auth_token+'/jpeg/'+d.ke+'/'+d.mid+'/s.jpg?time='+(new Date()).getTime()) } k.e.load(function(){ $.ccio.mon[d.ke+d.mid+user.auth_token].jpegInterval=setTimeout(k.run,1000/k.jpegInterval); @@ -1876,7 +1880,7 @@ switch($user.details.lang){ return ii.o } //websocket functions -$.users={} +$.users = {} $.ccio.globalWebsocket=function(d,user){ if(d.f!=='monitor_frame'&&d.f!=='os'&&d.f!=='video_delete'&&d.f!=='detector_trigger'&&d.f!=='detector_record_timeout_start'&&d.f!=='log'){$.ccio.log(d);} if(!user){ @@ -2127,16 +2131,7 @@ $.ccio.globalWebsocket=function(d,user){ if($.ccio.op().jpeg_on===true){ $.ccio.init('jpegMode',$.ccio.mon[d.ke+d.id+user.auth_token]); }else{ - var url = $.ccio.init('location',user); - var prefix = 'ws' - if(location.protocol==='https:'){ - prefix = 'wss' - } - if(url==''){ - url = prefix+'://'+location.host+location.pathname - }else{ - url = prefix+'://'+url.split('://')[1] - } + var path = tool.checkCorrectPathEnding(location.pathname)+'socket.io' switch(d.d.stream_type){ case'jpeg': $.ccio.init('jpegMode',$.ccio.mon[d.ke+d.id+user.auth_token]); @@ -2145,7 +2140,7 @@ $.ccio.globalWebsocket=function(d,user){ if($.ccio.mon[d.ke+d.id+user.auth_token].Base64 && $.ccio.mon[d.ke+d.id+user.auth_token].Base64.connected){ $.ccio.mon[d.ke+d.id+user.auth_token].Base64.disconnect() } - $.ccio.mon[d.ke+d.id+user.auth_token].Base64 = io(url,{transports: ['websocket'], forceNew: false}) + $.ccio.mon[d.ke+d.id+user.auth_token].Base64 = io(location.origin,{ path: path, transports: ['websocket'], forceNew: false}) var ws = $.ccio.mon[d.ke+d.id+user.auth_token].Base64 var buffer ws.on('diconnect',function(){ @@ -2153,7 +2148,6 @@ $.ccio.globalWebsocket=function(d,user){ }) ws.on('connect',function(){ ws.emit('Base64',{ - url: url, auth: user.auth_token, uid: user.uid, ke: d.ke, @@ -2224,7 +2218,8 @@ $.ccio.globalWebsocket=function(d,user){ ke:d.ke, uid:user.uid, id:d.id, - url: url, + url: location.origin, + path: path, onError : onPoseidonError }) $.ccio.mon[d.ke+d.id+user.auth_token].Poseidon.start(); @@ -2261,7 +2256,8 @@ $.ccio.globalWebsocket=function(d,user){ id:d.id, maxLatency:d.d.stream_flv_maxLatency, hasAudio:false, - url: url + url: location.origin, + path: path } }else{ options = { @@ -2338,14 +2334,13 @@ $.ccio.globalWebsocket=function(d,user){ $.ccio.mon[d.ke+d.id+user.auth_token].h265HttpStream.abort() } if(d.d.stream_flv_type==='ws'){ - $.ccio.mon[d.ke+d.id+user.auth_token].h265Socket = io(url,{transports: ['websocket'], forceNew: false}) + $.ccio.mon[d.ke+d.id+user.auth_token].h265Socket = io(location.origin,{ path: path, transports: ['websocket'], forceNew: false}) var ws = $.ccio.mon[d.ke+d.id+user.auth_token].h265Socket ws.on('diconnect',function(){ console.log('h265Socket Stream Disconnected') }) ws.on('connect',function(){ ws.emit('h265',{ - url: url, auth: user.auth_token, uid: user.uid, ke: d.ke, @@ -2469,7 +2464,9 @@ $.ccio.globalWebsocket=function(d,user){ } $.ccio.init('monitorInfo',d) $.gR.drawList(); - $.ccio.init('note',{title:'Monitor Saved',text:''+d.mon.name+' '+d.mon.mid+' has been saved.',type:'success'}); + if(!d.silenceNote){ + $.ccio.init('note',{title:'Monitor Saved',text:''+d.mon.name+' '+d.mon.mid+' has been saved.',type:'success'}); + } break; case'monitor_starting': // switch(d.mode){case'start':d.mode='Watch';break;case'record':d.mode='Record';break;} @@ -2632,7 +2629,7 @@ $.ccio.globalWebsocket=function(d,user){ } } $user.ws=io(location.origin,{ - path : location.pathname+'socket.io' + path : tool.checkCorrectPathEnding(location.pathname)+'socket.io' }); $user.ws.on('connect',function (d){ $(document).ready(function(e){ @@ -2751,7 +2748,6 @@ $user.ws.on('f',function (d){ d.currentlyEditing=$.aM.e.attr('mid') if(d.currentlyEditing&&d.currentlyEditing!==''){ d.currentlyEditing=JSON.parse(JSON.parse($.ccio.mon[d.currentlyEditing].details).detector_cascades) - console.log(d.currentlyEditing) $.each(d.currentlyEditing,function(m,b){ d.e=$('.detector_cascade_selection[value="'+m+'"]').prop('checked',true) d.p=d.e.parents('.mdl-js-switch') @@ -3001,6 +2997,7 @@ $.zO.initCanvas=function(){ $.zO.f.find('[name="sensitivity"]').val('') $.zO.f.find('[name="max_sensitivity"]').val('') $.zO.f.find('[name="threshold"]').val('') + $.zO.f.find('[name="color_threshold"]').val('') $.zO.rp.empty() }else{ e.cord=$.zO.regionViewerDetails.cords[e.val]; @@ -3016,6 +3013,7 @@ $.zO.initCanvas=function(){ $.zO.f.find('[name="sensitivity"]').val(e.cord.sensitivity) $.zO.f.find('[name="max_sensitivity"]').val(e.cord.max_sensitivity) $.zO.f.find('[name="threshold"]').val(e.cord.threshold) + $.zO.f.find('[name="color_threshold"]').val(e.cord.color_threshold) $.zO.e.find('.canvas_holder canvas').remove(); $.zO.initLiveStream() @@ -3120,7 +3118,7 @@ $.zO.e.on('click','.add',function(e){ } }) $.zO.regionViewerDetails.cords=e.save; - $.zO.regionViewerDetails.cords[e.gid]={name:e.gid,sensitivity:0.0005,max_sensitivity:'',threshold:1,points:[[0,0],[0,100],[100,0]]}; + $.zO.regionViewerDetails.cords[e.gid]={name:e.gid,sensitivity:0.0005,max_sensitivity:'',threshold:1,color_threshold:9,points:[[0,0],[0,100],[100,0]]}; $.zO.rl.append(''); $.zO.rl.val(e.gid) $.zO.rl.change(); @@ -3576,6 +3574,7 @@ $.aM.generateDefaultMonitorSettings=function(){ "detector_sensitivity": "", "detector_max_sensitivity": "", "detector_threshold": "1", + "detector_color_threshold": "", "cords": "[]", "detector_buffer_vcodec": "auto", "detector_buffer_fps": "", @@ -4204,6 +4203,10 @@ $.apM.f.submit(function(e){ if(!e.s.ip||e.s.ip.length<7){e.er.push('Enter atleast one IP')} if(e.er.length>0){$.apM.e.find('.msg').html(e.er.join('
'));return;} $.each(e.s,function(n,v){e.s[n]=v.trim()}) + // e.s = { + // "ip": "", + // "details": "{\"get_monitors\":\"1\",\"control_monitors\":\"1\",\"get_logs\":\"1\",\"watch_stream\":\"1\",\"watch_snapshot\":\"1\",\"watch_videos\":\"1\",\"delete_videos\":\"1\"}" + // } $.post($.ccio.init('location',$user)+$user.auth_token+'/api/'+$user.ke+'/add',{data:JSON.stringify(e.s)},function(d){ $.ccio.log(d) }) @@ -5695,7 +5698,7 @@ $('body') } if(!e.d.cords||e.d.cords===''){ e.d.cords={ - red:{ name:"red",sensitivity:0.0005, max_sensitivity:"",points:[[0,0],[0,100],[100,0]] }, + red:{ name:"red",sensitivity:0.0005, max_sensitivity:"",color_threshold:"",points:[[0,0],[0,100],[100,0]] }, } } $.zO.regionViewerDetails=e.d; @@ -6104,14 +6107,14 @@ $('body') }) } //set dropdown toggle preferences - e.o=$.ccio.op().dropdown_toggle; + e.o = $.ccio.op().dropdown_toggle if(e.o){ $.each(e.o,function(n,v){ $('[dropdown_toggle="'+n+'"]').val(v).change() }) } //set localStorage input values - e.o=$.ccio.op(); + e.o = $.ccio.op() if(e.o){ $.each(e.o,function(n,v){ if(typeof v==='string'){ diff --git a/web/libs/js/poseidon.js b/web/libs/js/poseidon.js index 3ef6b37..7a64a22 100644 --- a/web/libs/js/poseidon.js +++ b/web/libs/js/poseidon.js @@ -243,7 +243,7 @@ var Poseidon = function () { } this._playing = true; var _this = this; - this._socket = io(_this._monitor.url, { transports: ['websocket'], forceNew: false }); + this._socket = io(_this._monitor.url, { path: _this._monitor.path, transports: ['websocket'], forceNew: false }); this._addSocketEvents(); if (this._startstop) { this._startstop.disabled = false; diff --git a/web/pages/admin.ejs b/web/pages/admin.ejs index a06649e..0a29f86 100644 --- a/web/pages/admin.ejs +++ b/web/pages/admin.ejs @@ -1,14 +1,17 @@ +<% + window.libURL = originalURL + global.s.checkCorrectPathEnding(config.webPaths.admin) +%> <% include blocks/header %> - - - - - - - - - + + + + + + + + +
@@ -80,6 +83,7 @@ <% include blocks/confirm.ejs %> <% include blocks/subpermissions.ejs %> + @@ -89,7 +93,9 @@ - - + + + <% cleanLang = function(string){ if(!string){string=''} diff --git a/web/pages/blocks/help.ejs b/web/pages/blocks/help.ejs index d7620b1..cc91e75 100644 --- a/web/pages/blocks/help.ejs +++ b/web/pages/blocks/help.ejs @@ -10,7 +10,7 @@
+
+ +
+
+ +
+
+ +
-
+

<%-lang['Object Detection']%> <%-lang['Plugin']%> : <%-lang['Not Connected']%>

- + - +
Stream
@@ -17,11 +17,14 @@ requires https or firefox
- - + + + - - - - + + + + + + + + + + +