summaryrefslogtreecommitdiff
path: root/www/js
diff options
context:
space:
mode:
Diffstat (limited to 'www/js')
-rwxr-xr-xwww/js/DataModel.js2193
-rw-r--r--www/js/DevOptionsCtrl.js139
-rw-r--r--www/js/EventCtrl.js3002
-rw-r--r--www/js/EventDateTimeFilterCtrl.js138
-rw-r--r--www/js/EventModalCtrl.js1873
-rw-r--r--www/js/EventServer.js584
-rw-r--r--www/js/EventServerSettingsCtrl.js360
-rw-r--r--www/js/EventsGraphsCtrl.js250
-rw-r--r--www/js/EventsModalGraphCtrl.js409
-rw-r--r--www/js/FirstUseCtrl.js95
-rw-r--r--www/js/HelpCtrl.js92
-rw-r--r--www/js/ImportantMessageCtrl.js35
-rw-r--r--www/js/InvalidApiCtrl.js37
-rw-r--r--www/js/LogCtrl.js315
-rw-r--r--www/js/LoginCtrl.js860
-rw-r--r--www/js/LowVersionCtrl.js24
-rw-r--r--www/js/MenuController.js55
-rw-r--r--www/js/MonitorCtrl.js550
-rw-r--r--www/js/MonitorModalCtrl.js1768
-rw-r--r--www/js/MontageCtrl.js2024
-rw-r--r--www/js/MontageHistoryCtrl.js1496
-rw-r--r--www/js/NewsCtrl.js132
-rw-r--r--www/js/PortalLoginCtrl.js454
-rw-r--r--www/js/StateCtrl.js410
-rw-r--r--www/js/TimelineCtrl.js1608
-rw-r--r--www/js/TimelineModalCtrl.js492
-rw-r--r--www/js/WizardCtrl.js848
-rwxr-xr-x[-rw-r--r--]www/js/app.js2181
-rw-r--r--www/js/controllers.js29
-rw-r--r--www/js/ionicUtils.js32
30 files changed, 22461 insertions, 24 deletions
diff --git a/www/js/DataModel.js b/www/js/DataModel.js
new file mode 100755
index 00000000..8f702c66
--- /dev/null
+++ b/www/js/DataModel.js
@@ -0,0 +1,2193 @@
+/* jshint -W041 */
+
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console, URI, moment, localforage, CryptoJS, Connection */
+
+// This is my central data respository and common functions
+// that many other controllers use
+// It's grown over time. I guess I may have to split this into multiple services in the future
+
+angular.module('zmApp.controllers')
+
+.service('NVRDataModel', ['$http', '$q', '$ionicLoading', '$ionicBackdrop', '$fileLogger', 'zm', '$rootScope', '$ionicContentBanner', '$timeout', '$cordovaPinDialog', '$ionicPopup', '$localstorage', '$state', '$ionicNativeTransitions', '$translate', '$cordovaSQLite',
+ function($http, $q, $ionicLoading, $ionicBackdrop, $fileLogger,
+ zm, $rootScope, $ionicContentBanner, $timeout, $cordovaPinDialog,
+ $ionicPopup, $localstorage, $state, $ionicNativeTransitions, $translate)
+ {
+
+ var zmAppVersion = "unknown";
+ var isBackground = false;
+ var justResumed = false;
+ var monitorsLoaded = 0;
+ //var montageSize = 3;
+ var monitors = [];
+ var multiservers = [];
+ var oldevents = [];
+ var migrationComplete = false;
+
+ var tz = "";
+ var isTzSupported = false;
+
+ var languages = [
+ {
+ text: 'English',
+ value: 'en'
+ },
+ {
+ text: 'العربية',
+ value: 'ar'
+ },
+ {
+ text: 'Deutsch',
+ value: 'de'
+ },
+ {
+ text: 'Español',
+ value: 'es'
+ },
+
+ {
+ text: 'Français',
+ value: 'fr'
+ },
+
+ {
+ text: 'Italiano',
+ value: 'it'
+ },
+ {
+ text:'Magyar',
+ value:'hu'
+ },
+ {
+ text: 'Nederlands',
+ value: 'nl'
+ },
+ {
+ text: 'Polski',
+ value: 'pl'
+ },
+ {
+ text: 'Portugese',
+ value: 'pt'
+ },
+ {
+ text: 'Русский',
+ value: 'ru'
+ },
+
+
+
+ /* {
+ text: 'Hindi',
+ value: 'hi'
+ }*/
+ ];
+
+ var serverGroupList = {};
+ var defaultLang = 'en';
+ var isFirstUse = true;
+ var lastUpdateCheck = null;
+ var latestBlogPostChecked = null;
+ var loginData = {
+ 'serverName': '',
+ 'username': '',
+ 'password': '',
+ 'fallbackConfiguration': '',
+ 'url': '', // This is the ZM portal path
+ 'apiurl': '', // This is the API path
+ 'eventServer': '', //experimental Event server address
+ 'maxMontage': "100", //total # of monitors to display in montage
+ 'streamingurl': "",
+ 'maxFPS': "3", // image streaming FPS
+ 'montageQuality': "50", // montage streaming quality in %
+ 'singleImageQuality': "100", // event single streaming quality in %
+ 'monSingleImageQuality': "100", // live view quality
+ 'montageHistoryQuality': "50",
+ 'useSSL': false, // "1" if HTTPS
+ 'keepAwake': true, // don't dim/dim during live view
+ 'isUseAuth': true, // true if user wants ZM auth
+ 'isUseEventServer': false, // true if you configure the websocket event server
+ 'disablePush': false, // true if only websocket mode is desired
+ 'eventServerMonitors': '', // list of monitors to notify from ES
+ 'eventServerInterval': '', // list of intervals for all monitors
+ 'refreshSec': '2', // timer value for frame change in sec
+ 'refreshSecLowBW': 8,
+ 'enableLogs': true,
+ 'enableDebug': true, // if enabled with log messages with "debug"
+ 'usePin': false,
+ 'pinCode': '',
+ 'canSwipeMonitors': true,
+ 'persistMontageOrder': false,
+ 'onTapScreen': "",
+ 'enableh264': true,
+ 'gapless': false,
+ 'montageOrder': '',
+ 'montageHiddenOrder': '',
+ 'montageArraySize': '0',
+ 'showMontageSubMenu': false,
+ 'graphSize': 2000,
+ 'enableAlarmCount': true,
+ 'minAlarmCount': 1,
+ 'montageSize': '3',
+ 'useNphZms': true,
+ 'useNphZmsForEvents': true,
+ 'packMontage': false,
+ 'exitOnSleep': false,
+ 'forceNetworkStop': false,
+ 'defaultPushSound': false,
+ 'enableBlog': true,
+ 'use24hr': false,
+ 'packeryPositions': '',
+ 'currentMontageProfile': '',
+ 'packeryPositionsArray': {},
+ 'EHpackeryPositions': '',
+ 'packerySizes': '',
+ 'timelineModalGraphType': 'all',
+ 'resumeDelay': 0,
+ 'language': 'en',
+ 'reachability': true,
+ 'forceImageModePath': false,
+ 'disableNative': false,
+ 'vibrateOnPush': true,
+ 'soundOnPush': true,
+ 'cycleMonitors': false,
+ 'cycleMontage': false,
+ 'cycleMontageInterval': 10, // 10sec
+ 'cycleMonitorsInterval': 10, // 10sec
+ 'enableLowBandwidth': false,
+ 'autoSwitchBandwidth': false,
+ 'disableAlarmCheckMontage': false,
+ 'useLocalTimeZone': true,
+ 'fastLogin': true,
+ 'followTimeLine': false,
+ 'timelineScale': -1,
+ 'hideArchived': false,
+ 'videoPlaybackSpeed': 2,
+ 'enableGIFMP4': false,
+ 'enableStrictSSL': false,
+ 'enableSlowLoading': false,
+
+ };
+
+ var defaultLoginData = angular.copy(loginData);
+
+ var configParams = {
+ 'ZM_EVENT_IMAGE_DIGITS': '-1',
+ 'ZM_PATH_ZMS': ''
+ };
+
+
+ function setSSLCerts()
+ {
+ if (!window.cordova) return;
+ if (!loginData.enableStrictSSL)
+ {
+
+ //alert("Enabling insecure SSL");
+ log(">>>> Disabling strict SSL checking (turn off in Dev Options if you can't connect)");
+ cordova.plugins.certificates.trustUnsecureCerts(true);
+
+ }
+ else
+ {
+
+ log(">>>> Enabling strict SSL checking (turn off in Dev Options if you can't connect)");
+ cordova.plugins.certificates.trustUnsecureCerts(false);
+ }
+ }
+
+
+ // credit: http://stackoverflow.com/questions/4994201/is-object-empty
+ function isEmpty(obj)
+ {
+
+ // null and undefined are "empty"
+ if (obj == null) return true;
+
+ // Assume if it has a length property with a non-zero value
+ // that that property is correct.
+ if (obj.length > 0) return false;
+ if (obj.length === 0) return true;
+
+ // Otherwise, does it have any properties of its own?
+ // Note that this doesn't handle
+ // toString and valueOf enumeration bugs in IE < 9
+ for (var key in obj)
+ {
+ if (hasOwnProperty.call(obj, key)) return false;
+ }
+
+ return true;
+ }
+
+ function getBandwidth()
+ {
+ // if mode is not on always return high
+ if (loginData.enableLowBandwidth == false)
+ {
+ return "highbw";
+ }
+ // if mode is force on, return low
+ if (loginData.enableLowBandwidth == true && loginData.autoSwitchBandwidth != true)
+ {
+ return "lowbw";
+ }
+ if (loginData.enableLowBandwidth == true && loginData.autoSwitchBandwidth == true && $rootScope.platformOS == 'desktop')
+ {
+ return "highbw";
+ }
+ // else return real state
+
+ var networkState = navigator.connection.type;
+ var strState;
+ switch (networkState)
+ {
+
+ case Connection.WIFI:
+ strState = "highbw";
+ break;
+ case Connection.ETHERNET:
+ strState = "highbw";
+ break;
+ default:
+ strState = "lowbw";
+ break;
+
+ }
+ return strState;
+ }
+
+ //--------------------------------------------------------------------------
+ // uses fileLogger to write logs to file for later investigation
+ //--------------------------------------------------------------------------
+
+ // separate out a debug so we don't do this if comparison for normal logs
+ function debug(val)
+ {
+ if (loginData.enableDebug && loginData.enableLogs)
+ {
+ if (val !== undefined)
+ {
+ var regex1 = /"password":".*?"/g;
+ var regex2 = /&pass=.*?(?=["&]|$)/g;
+
+ //console.log ("VAL IS " + val);
+ val = val.replace(regex1, "<password removed>");
+ val = val.replace(regex2, "<password removed>");
+ }
+ $fileLogger.debug(val);
+ //console.log (val);
+ }
+ }
+
+ function log(val, logtype)
+ {
+ if (loginData.enableLogs)
+ {
+ if (val !== undefined)
+ {
+ var regex1 = /"password":".*?"/g;
+ var regex2 = /&pass=.*?(?=["&]|$)/g;
+
+ //console.log ("VAL IS " + val);
+ val = val.replace(regex1, "<password removed>");
+ val = val.replace(regex2, "<password removed>");
+
+ }
+ // make sure password is removed
+ //"username":"zmninja","password":"xyz",
+ //val = val.replace(/\"password:\",
+ $fileLogger.log(logtype, val);
+ // console.log (val);
+ }
+ }
+
+ function reloadMonitorDisplayStatus()
+ {
+ debug("Loading hidden/unhidden status for profile:"+loginData.currentMontageProfile);
+
+ var positionsStr = loginData.packeryPositions;
+ //console.log ("positionStr="+positionsStr);
+ var positions = {};
+ if (loginData.packeryPositions != '' && loginData.packeryPositions != undefined)
+ {
+ console.log ("positions="+loginData.packeryPositions);
+
+
+ positions = JSON.parse(positionsStr);
+ for (var m = 0; m < monitors.length; m++)
+ {
+ var positionFound = false;
+ for (var p = 0; p < positions.length; p++)
+ {
+ if (monitors[m].Monitor.Id == positions[p].attr)
+ {
+ monitors[m].Monitor.listDisplay = positions[p].display;
+ positionFound = true;
+ debug("DataModel: Setting MID:" + monitors[m].Monitor.Id + " to " + monitors[m].Monitor.listDisplay);
+ }
+
+ }
+ if (!positionFound)
+ {
+ if (loginData.currentMontageProfile != $translate.instant('kMontageDefaultProfile'))
+ {
+ monitors[m].Monitor.listDisplay = 'noshow';
+ console.log("*************DISABLE NEW MONITOR");
+ }
+ else // make sure we add it because its show all view
+ {
+ monitors[m].Monitor.listDisplay = 'show';
+ console.log("*************ENABLE NEW MONITOR");
+ }
+
+
+ }
+
+ }
+
+ }
+ else // if there are no packery positions, make sure all are displayed!
+ {
+ debug ("no packery profile, making sure monitors are show");
+ for (var m1 = 0; m1 < monitors.length; m1++)
+ {
+ monitors[m1].Monitor.listDisplay = 'show';
+
+ }
+
+
+ }
+ }
+
+ function setLogin(newLogin)
+ {
+ loginData = angular.copy(newLogin);
+ serverGroupList[loginData.serverName] = angular.copy(loginData);
+
+ var ct = CryptoJS.AES.encrypt(JSON.stringify(serverGroupList), zm.cipherKey).toString();
+
+ //console.log ("****serverLogin was encrypted to " + ct);
+ //$localstorage.setObject("serverGroupList", serverGroupList);
+ localforage.setItem("serverGroupList", ct, function(err)
+ {
+ if (err) log("localforage store error " + JSON.stringify(err));
+ });
+ //$localstorage.set("defaultServerName", loginData.serverName);
+ localforage.setItem("defaultServerName", loginData.serverName, function(err)
+ {
+ if (err) log("localforage store error " + JSON.stringify(err));
+ });
+
+ }
+
+ //credit: https://gist.github.com/alexey-bass/1115557
+ function versionCompare(left, right)
+ {
+ if (typeof left + typeof right != 'stringstring')
+ return false;
+
+ var a = left.split('.');
+ var b = right.split('.');
+ var i = 0;
+ var len = Math.max(a.length, b.length);
+
+ for (; i < len; i++)
+ {
+ if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i])))
+ {
+ return 1;
+ }
+ else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i])))
+ {
+ return -1;
+ }
+ }
+
+ return 0;
+ }
+
+ //--------------------------------------------------------------------------
+ // Banner display of messages
+ //--------------------------------------------------------------------------
+ function displayBanner(mytype, mytext, myinterval, mytimer)
+ {
+
+ var contentBannerInstance =
+ $ionicContentBanner.show(
+ {
+ text: mytext || 'no text',
+ interval: myinterval || 2000,
+ //autoClose: mytimer || 6000,
+ type: mytype || 'info',
+ transition: 'vertical',
+ //cancelOnStateChange: false
+ });
+
+ $timeout(function()
+ {
+ contentBannerInstance();
+ }, mytimer || 6000);
+ }
+
+ return {
+
+ //-------------------------------------------------------------
+ // used by various controllers to log messages to file
+ //-------------------------------------------------------------
+
+ migrationComplete: function()
+ {
+ migrationComplete = true;
+ },
+
+ isEmpty: function(obj)
+ {
+ return isEmpty(obj);
+ },
+
+ log: function(val, type)
+ {
+ var logtype = 'info';
+ if (type != undefined)
+ logtype = type;
+ log(val, logtype);
+
+ },
+
+ debug: function(val)
+ {
+
+ debug(val);
+ },
+
+ setLastUpdateCheck: function(val)
+ {
+ lastUpdateCheck = val;
+ localforage.setItem("lastUpdateCheck", lastUpdateCheck);
+ },
+
+ getLastUpdateCheck: function()
+ {
+ return lastUpdateCheck;
+ },
+
+ setLatestBlogPostChecked: function(val)
+ {
+ console.log (">>>>>>>>>>>> Setting blog date: " + val);
+ latestBlogPostChecked = val;
+ localforage.setItem("latestBlogPostChecked", latestBlogPostChecked);
+ },
+
+ getLatestBlogPostChecked: function()
+ {
+ return latestBlogPostChecked;
+ },
+
+ // This function is called when the app is ready to run
+ // sets up various variables
+ // including persistent login data for the ZM apis and portal
+ // The reason I need both is because as of today, there is no way
+ // to access images using the API and they are authenticated via
+ // the ZM portal authentication, which is pretty messy. But unless
+ // the ZM authors fix this and streamline the access of images
+ // from APIs, I don't have an option
+
+ zmStateGo: function(state, p1, p2)
+ {
+ if ($rootScope.platformOS == 'desktop')
+ $state.go(state, p1, p2);
+ else
+ $ionicNativeTransitions.stateGo(state, p1, p2);
+ },
+
+ // used when an empty server profile is created
+ getDefaultLoginObject: function()
+ {
+ return angular.copy(defaultLoginData);
+ },
+
+ getReachableConfig: function(skipFirst)
+ {
+ var d = $q.defer();
+ if (loginData.serverName == "")
+ {
+ log("Reachable: No server name configured, likely first use?");
+ d.reject("No servers");
+ return d.promise;
+ }
+
+ var chainURLs = [];
+ var savedLoginData = angular.copy(loginData);
+
+ //log ("Making sure " + loginData.serverName + " is reachable...");
+ var tLd = serverGroupList[loginData.serverName];
+ if (skipFirst && tLd.fallbackConfiguration)
+ {
+ tLd = serverGroupList[tLd.fallbackConfiguration];
+ if (!tLd)
+ {
+ d.reject("No available severs");
+ loginData = savedLoginData;
+ return d.promise;
+
+ }
+ }
+
+ var keepBuilding = true;
+ while (keepBuilding == true && tLd)
+ {
+ if (arrayObjectIndexOf(chainURLs, tLd.url + "/index.php", "url") == -1 && tLd.url !== undefined && tLd.url != '') // no loop
+ {
+ log("Adding to chain stack: " + tLd.serverName + ">" + tLd.url);
+ chainURLs.push(
+ {
+ url: tLd.url + "/index.php",
+ server: tLd.serverName
+ });
+ log("Fallback of " + tLd.serverName + " is " + tLd.fallbackConfiguration);
+ if (tLd.fallbackConfiguration)
+ {
+ tLd = serverGroupList[tLd.fallbackConfiguration];
+ if (tLd === undefined)
+ {
+ // This can happen if the fallback profile was deleted
+ log("Looks like a server object was deleted, but is still in fallback");
+ keepBuilding = false;
+ }
+ }
+ else
+ {
+ log("reached end of chain loop");
+ }
+ }
+ else
+ {
+ log("detected loop when " + tLd.serverName + " fallsback to " + tLd.fallbackConfiguration);
+ keepBuilding = false;
+ }
+ }
+
+ //contactedServers.push(loginData.serverName);
+ findFirstReachableUrl(chainURLs).then(function(firstReachableUrl)
+ {
+ d.resolve(firstReachableUrl);
+ // also make sure loginData points to this now
+
+ loginData = angular.copy(serverGroupList[firstReachableUrl.server]);
+
+ setLogin(loginData);
+ //$localstorage.set("defaultServerName",firstReachableUrl.server);
+
+ log("Based on reachability, first serverName will be " + firstReachableUrl.server);
+ //console.log("set login Data to " + JSON.stringify(loginData));
+
+ return d.promise;
+ // OK: do something with firstReachableUrl
+ }, function()
+ {
+ d.reject("No servers reachable");
+ loginData = savedLoginData;
+ return d.promise;
+ // KO: no url could be reached
+ });
+
+ function arrayObjectIndexOf(myArray, searchTerm, property)
+ {
+ for (var i = 0, len = myArray.length; i < len; i++)
+ {
+ if (myArray[i][property] === searchTerm)
+ return i;
+ }
+ return -1;
+ }
+
+ function findFirstReachableUrl(urls)
+ {
+ if (urls.length > 0 && $rootScope.userCancelledAuth != true)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kTrying') + ' ' + urls[0].server
+ });
+ log("Reachability test.." + urls[0].url);
+
+ if (loginData.reachability)
+ {
+
+ //console.log ("************* AUGH");
+ var hDelay = loginData.enableSlowLoading? zm.largeHttpTimeout:zm.httpTimeout;
+ return $http({method:'GET', timeout:hDelay, url:urls[0].url}).then(function()
+ {
+ log("Success: reachability on " + urls[0].url);
+ $ionicLoading.hide();
+ return urls[0];
+ }, function(err)
+ {
+ log("Failed reachability on " + urls[0].url + " with error " + JSON.stringify(err));
+ return findFirstReachableUrl(urls.slice(1));
+ });
+ }
+ else
+ {
+ log("Reachability is disabled in config, faking this test and returning success on " + urls[0]);
+ return urls[0];
+ }
+ }
+ else
+ {
+ $ionicLoading.hide();
+ return $q.reject("No reachable URL");
+
+ }
+
+ }
+
+ return d.promise;
+
+ },
+
+ init: function()
+ {
+ // console.log("****** DATAMODEL INIT SERVICE CALLED ********");
+
+ log("ZMData init: checking for stored variables & setting up log file");
+
+ localforage.getItem("latestBlogPostChecked")
+ .then (function (val) {latestBlogPostChecked = val;},
+ function (err) {latestBlogPostChecked = null;});
+
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kRetrievingProfileData'),
+ });
+
+ localforage.getItem("serverGroupList").then(function(val)
+ {
+ // decrypt it now
+
+ var decodedVal;
+
+ if (typeof val == 'string')
+ {
+ log("user profile encrypted, decoding...");
+ var bytes = CryptoJS.AES.decrypt(val.toString(), zm.cipherKey);
+ decodedVal = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
+
+ }
+ else
+ {
+ log("user profile not encrypted");
+ decodedVal = val;
+ }
+
+ //decodedVal = val;
+
+ // debug("user profile retrieved:" + JSON.stringify(decodedVal));
+
+ $ionicLoading.hide();
+ serverGroupList = decodedVal;
+
+ // console.log(">>>> DECRYPTED serverGroupList " + JSON.stringify(serverGroupList));
+ var demoServer = "{\"serverName\":\"zmNinjaDemo\",\"username\":\"zmninja\",\"password\":\"zmNinja$xc129\",\"url\":\"https://demo.zoneminder.com/zm\",\"apiurl\":\"https://demo.zoneminder.com/zm/api\",\"eventServer\":\"\",\"maxMontage\":\"40\",\"streamingurl\":\"https://demo.zoneminder.com/cgi-bin-zm\",\"maxFPS\":\"3\",\"montageQuality\":\"50\",\"singleImageQuality\":\"100\",\"montageHistoryQuality\":\"50\",\"useSSL\":true,\"keepAwake\":true,\"isUseAuth\":\"1\",\"isUseEventServer\":false,\"disablePush\":false,\"eventServerMonitors\":\"\",\"eventServerInterval\":\"\",\"refreshSec\":\"2\",\"enableDebug\":false,\"usePin\":false,\"pinCode\":\"\",\"canSwipeMonitors\":true,\"persistMontageOrder\":false,\"onTapScreen\":\"Events\",\"enableh264\":true,\"gapless\":false,\"montageOrder\":\"\",\"montageHiddenOrder\":\"\",\"montageArraySize\":\"0\",\"graphSize\":2000,\"enableAlarmCount\":true,\"montageSize\":\"3\",\"useNphZms\":true,\"useNphZmsForEvents\":true,\"packMontage\":false,\"exitOnSleep\":false,\"forceNetworkStop\":false,\"defaultPushSound\":false,\"enableBlog\":true,\"use24hr\":false, \"packeryPositions\":\"\"}";
+ var demoS = JSON.parse(demoServer);
+ //console.log("JSON parsed demo" + JSON.stringify(demoS));
+
+ var isFoundDemo = false;
+ var as = Object.keys(serverGroupList);
+ for (var x = 0; x < as.length; x++)
+ {
+ if (as[x] == 'zmNinjaDemo')
+ isFoundDemo = true;
+ //console.log ("************ FOUND SERVER NAME " + as[x]);
+ // if serverGroupList[x]
+ }
+
+ // Don't add the demo if there is another server
+ // because this means the user deleted it
+
+ if (!isFoundDemo && as.length == 0)
+ {
+ debug("Pushing demo server config to server groups");
+ //serverGroupList.push(demoS);
+ serverGroupList[demoS.serverName] = angular.copy(demoS);
+ }
+
+ var sname;
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kRetrievingProfileData'),
+ });
+ localforage.getItem("defaultServerName")
+ .then(function(val)
+ {
+ $ionicLoading.hide();
+ //console.log ("!!!!!!!!!!!!!!!!!!default server name is " + sname);
+ sname = val;
+ // console.log("!!!!!!!!!!!!!!!!!!!Got VAL " + sname);
+ var loadedData = serverGroupList[sname];
+ // console.log(">>>>>>>>>>> loadedData is: " + JSON.stringify(loadedData));
+ if (!isEmpty(loadedData))
+ {
+ loginData = loadedData;
+
+ // old version hacks for new variables
+
+ // always true Oct 27 2016
+ loginData.persistMontageOrder = true;
+ loginData.enableh264 = true;
+
+ if (typeof loginData.enableAlarmCount === 'undefined')
+ {
+ debug("enableAlarmCount does not exist, setting to true");
+ loginData.enableAlarmCount = true;
+ }
+
+ if (typeof loginData.onTapScreen == 'undefined')
+ {
+ loginData.onTapScreen = $translate.instant('kTapMontage');
+ }
+
+ if (loginData.onTapScreen != $translate.instant('kTapMontage') &&
+ loginData.onTapScreen != $translate.instant('kTapEvents') &&
+ loginData.onTapScreen != $translate.instant('kTapLiveMonitor'))
+ {
+ log("Invalid onTap setting found, resetting");
+ loginData.onTapScreen = $translate.instant('kMontage');
+ }
+
+ if (typeof loginData.minAlarmCount === 'undefined')
+ {
+ debug("minAlarmCount does not exist, setting to true");
+ loginData.minAlarmCount = 1;
+ }
+
+ if (typeof loginData.montageSize == 'undefined')
+ {
+ debug("montageSize does not exist, setting to 2 (2 per col)");
+ loginData.montageSize = 2;
+ }
+
+ if (typeof loginData.useNphZms == 'undefined')
+ {
+ debug("useNphZms does not exist. Setting to true");
+ loginData.useNphZms = true;
+ }
+
+ if (typeof loginData.useNphZmsForEvents == 'undefined')
+ {
+ debug("useNphZmsForEvents does not exist. Setting to true");
+ loginData.useNphZmsForEvents = true;
+ }
+
+ if (typeof loginData.forceImageModePath == 'undefined')
+ {
+ debug("forceImageModePath does not exist. Setting to false");
+ loginData.forceImageModePath = false;
+ }
+
+ if (typeof loginData.reachability == 'undefined')
+ {
+ debug("reachability does not exist. Setting to true");
+ loginData.reachability = true;
+ }
+ // force it - this may not be the problem
+ loginData.reachability = true;
+
+ // and now, force enable it
+ loginData.useNphZms = true;
+ loginData.useNphZmsForEvents = true;
+
+ if (typeof loginData.packMontage == 'undefined')
+ {
+ debug("packMontage does not exist. Setting to false");
+ loginData.packMontage = false;
+ }
+
+ if (typeof loginData.forceNetworkStop == 'undefined')
+ {
+ debug("forceNetwork does not exist. Setting to false");
+ loginData.forceNetworkStop = false;
+ }
+
+ if (typeof loginData.enableLogs == 'undefined')
+ {
+ debug("enableLogs does not exist. Setting to true");
+ loginData.enableLogs = true;
+ }
+
+ if (typeof loginData.defaultPushSound == 'undefined')
+ {
+ debug("defaultPushSound does not exist. Setting to false");
+ loginData.defaultPushSound = false;
+ }
+
+ if (typeof loginData.exitOnSleep == 'undefined')
+ {
+ debug("exitOnSleep does not exist. Setting to false");
+ loginData.exitOnSleep = false;
+ }
+
+ if (typeof loginData.enableBlog == 'undefined')
+ {
+ debug("enableBlog does not exist. Setting to true");
+ loginData.enableBlog = true;
+
+ }
+
+ if (typeof loginData.packeryPositionsArray == 'undefined')
+ {
+ debug("packeryPositionsArray does not exist. Setting to empty");
+ loginData.packeryPositionsArray = {};
+
+ }
+
+
+ if (typeof loginData.packeryPositions == 'undefined')
+ {
+ debug("packeryPositions does not exist. Setting to empty");
+ loginData.packeryPositions = "";
+
+ }
+
+ if (typeof loginData.EHpackeryPositions == 'undefined')
+ {
+ debug("EHpackeryPositions does not exist. Setting to empty");
+ loginData.EHpackeryPositions = "";
+
+ }
+
+ if (typeof loginData.packerySizes == 'undefined')
+ {
+ debug("packerySizes does not exist. Setting to empty");
+ loginData.packerySizes = "";
+
+ }
+
+ if (typeof loginData.use24hr == 'undefined')
+ {
+ debug("use24hr does not exist. Setting to false");
+ loginData.use24hr = false;
+
+ }
+
+ if (typeof timelineModalGraphType == 'undefined')
+ {
+ debug("timeline graph type not set. Setting to all");
+ loginData.timelineModalGraphType = $translate.instant('kGraphAll');
+ //console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + loginData.timelineModalGraphType);
+ }
+
+ if (typeof loginData.resumeDelay == 'undefined')
+ {
+ debug("resumeDelay does not exist. Setting to 0");
+ loginData.resumeDelay = 0;
+
+ }
+ // override resumeDelay - it was developed on a wrong assumption
+ loginData.resumeDelay = 0;
+
+ if (typeof loginData.montageHistoryQuality == 'undefined')
+ {
+ debug("montageHistoryQuality does not exist. Setting to 50");
+ loginData.montageHistoryQuality = "50";
+
+ }
+
+ if (typeof loginData.disableNative == 'undefined')
+ {
+ debug("disableNative not found, setting to false");
+ loginData.disableNative = false;
+
+ }
+
+ if (typeof loginData.vibrateOnPush == 'undefined')
+ {
+ debug("vibrate on push not found, setting to true");
+ loginData.vibrateOnPush = true;
+
+ }
+
+ if (typeof loginData.soundOnPush == 'undefined')
+ {
+ debug("sound on push not found, setting to true");
+ loginData.soundOnPush = true;
+
+ }
+
+ if (typeof loginData.cycleMonitors == 'undefined')
+ {
+
+ loginData.cycleMonitors = false;
+
+ }
+
+ if (typeof loginData.cycleMonitorsInterval == 'undefined')
+ {
+
+ loginData.cycleMonitorsInterval = 10;
+
+ }
+
+ if (typeof loginData.cycleMontage == 'undefined')
+ {
+
+ loginData.cycleMontage = false;
+
+ }
+
+ if (typeof loginData.cycleMontageInterval == 'undefined')
+ {
+
+ loginData.cycleMontageInterval = 10;
+
+ }
+
+ if (typeof loginData.enableLowBandwidth == 'undefined')
+ {
+
+ loginData.enableLowBandwidth = false;
+
+ }
+ // wtf is wrong with this ternary?
+ //$rootScope.runMode = (loginData.enableLowBandwith==true)? "low": "normal";
+
+ if (typeof loginData.autoSwitchBandwidth == 'undefined')
+ {
+
+ loginData.autoSwitchBandwidth = false;
+
+ }
+
+ $rootScope.runMode = getBandwidth();
+ log("Setting DataModel init bandwidth to: " + $rootScope.runMode);
+
+ if (typeof loginData.refreshSecLowBW == 'undefined')
+ {
+
+ loginData.refreshSecLowBW = 8;
+
+ }
+
+ if (typeof loginData.disableAlarmCheckMontage == 'undefined')
+ {
+
+ loginData.disableAlarmCheckMontage = false;
+
+ }
+
+ if (typeof loginData.useLocalTimeZone == 'undefined')
+ {
+
+ loginData.useLocalTimeZone = true;
+
+ }
+
+ if (typeof loginData.fastLogin == 'undefined')
+ {
+
+ loginData.fastLogin = true;
+
+ }
+
+ if (typeof loginData.currentMontageProfile == 'undefined')
+ {
+
+ loginData.currentMontageProfile = '';
+
+ }
+
+ if (typeof loginData.followTimeLine == 'undefined')
+ {
+
+ loginData.followTimeLine = false;
+
+ }
+
+ if (typeof loginData.timelineScale == 'undefined')
+ {
+
+ loginData.timelineScale = -1;
+
+ }
+
+
+ if (typeof loginData.showMontageSubMenu == 'undefined')
+ {
+
+ loginData.showMontageSubMenu = false;
+
+ }
+
+
+
+ if (typeof loginData.monSingleImageQuality == 'undefined')
+ {
+
+ loginData.monSingleImageQuality = 100;
+
+ }
+
+ if (typeof loginData.hideArchived == 'undefined')
+ {
+
+ loginData.hideArchived = false;
+
+ }
+
+ if (typeof loginData.videoPlaybackSpeed == 'undefined')
+ {
+
+ loginData.videoPlaybackSpeed = 2;
+
+ }
+
+ if (typeof loginData.enableGIFMP4 == 'undefined')
+ {
+
+ loginData.enableGIFMP4 = true;
+
+ }
+
+ if (typeof loginData.enableSlowLoading == 'undefined')
+ {
+
+ loginData.enableSlowLoading = false;
+
+ }
+ log ("SlowDelay is: "+loginData.enableSlowLoading);
+
+ if (typeof loginData.enableStrictSSL == 'undefined')
+ {
+
+ loginData.enableStrictSSL = false;
+
+ }
+
+ log("DataModel init recovered this loginData as " + JSON.stringify(loginData));
+ }
+ else
+ {
+ log("defaultServer configuration NOT found. Keeping login at defaults");
+ }
+
+ // now set up SSL - need to do it after data return
+ // from local forage
+ setSSLCerts();
+
+
+ // FIXME: HACK: This is the latest entry point into dataModel init, so start portal login after this
+ // not the neatest way
+ $rootScope.$emit('init-complete');
+ });
+
+ monitorsLoaded = 0;
+ //console.log("Getting out of NVRDataModel init");
+ $rootScope.showBlog = loginData.enableBlog;
+ //debug("loginData structure values: " + JSON.stringify(loginData));
+
+ });
+
+ },
+
+ isForceNetworkStop: function()
+ {
+ return loginData.forceNetworkStop;
+ },
+
+ setJustResumed: function(val)
+ {
+ justResumed = true;
+ },
+
+ stopNetwork: function(str)
+ {
+ var s = "";
+ if (str) s = str + ":";
+ if (justResumed)
+ {
+ // we don't call stop as we did stop on pause
+ log(s + " Not calling window stop as we just resumed");
+ justResumed = false;
+ }
+ else
+ {
+ log(s + " Calling window.stop()");
+ window.stop();
+ }
+ },
+
+ isLoggedIn: function()
+ {
+
+ if ((loginData.username != "" && loginData.password != "" && loginData.url != "" &&
+ loginData.apiurl != "") || (loginData.isUseAuth != '1'))
+ {
+ return 1;
+ }
+ else
+ {
+
+ return 0;
+
+ }
+ },
+
+ getLanguages: function()
+ {
+ return languages;
+ },
+
+ setDefaultLanguage: function(l, permanent)
+ {
+
+ if (!l) l = 'en';
+ defaultLang = l;
+ var d = $q.defer();
+ if (permanent)
+ {
+ //window.localStorage.setItem("defaultLang", l);
+
+ //console.log("setting default lang");
+ localforage.setItem("defaultLang", l)
+ .then(function(val)
+ {
+ log("Set language in localforage to: " + val);
+ });
+ }
+
+ //console.log("invoking translate use with " + l);
+ $translate.use(l).then(function(data)
+ {
+ log("Device Language is:" + data);
+ moment.locale(data);
+ $translate.fallbackLanguage('en');
+ d.resolve(data);
+ return d.promise;
+ }, function(error)
+ {
+ log("Device Language error: " + error);
+ $translate.use('en');
+ moment.locale('en');
+ d.resolve('en');
+ return d.promise;
+ });
+ return d.promise;
+ },
+
+ getDefaultLanguage: function()
+ {
+ return defaultLang;
+ //return window.localStorage.getItem("defaultLang");
+
+ },
+
+ reloadMonitorDisplayStatus: function()
+ {
+ return reloadMonitorDisplayStatus();
+ },
+
+ getLogin: function()
+ {
+
+ return angular.copy(loginData);
+ },
+
+ getServerGroups: function()
+ {
+ return angular.copy(serverGroupList);
+ },
+
+ setServerGroups: function(sg)
+ {
+ serverGroupList = angular.copy(sg);
+ },
+
+ getKeepAwake: function()
+ {
+ return (loginData.keepAwake == '1') ? true : false;
+ },
+
+ setAppVersion: function(ver)
+ {
+ zmAppVersion = ver;
+ },
+
+ getAppVersion: function()
+ {
+ return (zmAppVersion);
+ },
+
+ setBackground: function(val)
+ {
+ isBackground = val;
+ },
+
+ isBackground: function()
+ {
+ return isBackground;
+ },
+
+ isFirstUse: function()
+ {
+ // console.log("isFirstUse is " + isFirstUse);
+ return isFirstUse;
+ // return ((window.localStorage.getItem("isFirstUse") == undefined) ? true : false);
+
+ },
+
+ versionCompare: function(l, r)
+ {
+ return versionCompare(l, r);
+ },
+
+ //-----------------------------------------------------------------
+ // Allow the option to reset first use if I need it in future
+ //-----------------------------------------------------------------
+ setFirstUse: function(val)
+ {
+ //window.localStorage.setItem("isFirstUse", val ? "1" : "0");
+ //localforage.setItem("isFirstUse", val,
+ // function(err) {if (err) log ("localforage error, //storing isFirstUse: " + JSON.stringify(err));});
+ isFirstUse = val;
+ localforage.setItem("isFirstUse", val);
+ //console.log (">>>>>>setting isFirstUse to " + val);
+
+ },
+
+ getTimeFormat: function()
+ {
+ return (loginData.use24hr ? "HH:mm" : "hh:mm a");
+ },
+
+ getTimeFormatSec: function()
+ {
+ return (loginData.use24hr ? "HH:mm:ss" : "hh:mm:ss a");
+ },
+
+ //------------------------------------------------------------------
+ // switches screen to 'always on' or 'auto'
+ //------------------------------------------------------------------
+ setAwake: function(val)
+ {
+
+ //console.log ("**** setAwake called with:" + val);
+ // log("Switching screen always on to " + val);
+ if (val)
+ {
+
+ if (window.cordova != undefined)
+ {
+ window.plugins.insomnia.keepAwake();
+ }
+ else
+ {
+ //console.log ("Skipping insomnia, cordova does not exist");
+ }
+ }
+ else
+ {
+ if (window.cordova != undefined)
+ {
+ window.plugins.insomnia.allowSleepAgain();
+ }
+ else
+ {
+ //console.log ("Skipping insomnia, cordova does not exist");
+ }
+
+ }
+
+ },
+
+ //--------------------------------------------------------------------------
+ // writes all params to local storage. FIXME: Move all of this into a JSON
+ // object
+ //--------------------------------------------------------------------------
+ setLogin: function(newLogin)
+ {
+
+ setLogin(newLogin);
+ $rootScope.showBlog = newLogin.enableBlog;
+
+ },
+
+ //-------------------------------------------------------
+ // returns API version or none
+ //-------------------------------------------------------
+ getAPIversion: function()
+ {
+ debug("getAPIversion called");
+ var d = $q.defer();
+ var apiurl = loginData.apiurl + '/host/getVersion.json';
+ $http.get(apiurl)
+ .then(function(success)
+ {
+ if (success.data.version)
+ {
+ $rootScope.apiValid = true;
+ d.resolve(success.data.version);
+ }
+ else
+ {
+ $rootScope.apiValid = false;
+ d.reject("-1.-1.-1");
+ }
+ return (d.promise);
+
+ },
+ function(error)
+ {
+ debug("getAPIversion error handler " + JSON.stringify(error));
+ d.reject("-1.-1.-1");
+ $rootScope.apiValid = false;
+ return (d.promise);
+ });
+ return (d.promise);
+
+ },
+
+ displayBanner: function(mytype, mytext, myinterval, mytimer)
+ {
+ displayBanner(mytype, mytext, myinterval, mytimer);
+ },
+
+ isReCaptcha: function()
+ {
+ var d = $q.defer();
+
+ var myurl = loginData.url;
+ log("Checking if reCaptcha is enabled in ZM...");
+ $http.get(myurl)
+ .then(function(success)
+ {
+ if (success.data.search("g-recaptcha") != -1)
+ {
+ // recaptcha enable. zmNinja won't work
+ log("ZM has recaptcha enabled", "error");
+ displayBanner('error', ['Recaptcha must be disabled in Zoneminder', $rootScope.appName + ' will not work with recaptcha'], "", 8000);
+ d.resolve(true);
+ return (d.promise);
+
+ }
+ else
+ {
+ d.resolve(false);
+ log("ZM has recaptcha disabled - good");
+ return (d.promise);
+ }
+ });
+ return (d.promise);
+ },
+
+ //-----------------------------------------------------------------------------
+ // Grabs the computed auth key for streaming
+ // FIXME: Currently a hack - does a screen parse - convert to API based support
+ //-----------------------------------------------------------------------------
+
+ // need a mid as restricted users won't be able to get
+ // auth with just &watch
+ getAuthKey: function(mid, ck)
+ {
+ var d = $q.defer();
+
+ if (!mid)
+ {
+ log("Deferring auth key, as monitorId unknown");
+ d.resolve("");
+ return (d.promise);
+ }
+
+ // Skipping monitor number as I only need an auth key
+ // so no need to generate an image
+ var myurl = loginData.url + "/index.php?view=watch&mid=" + mid + "&connkey=" + ck;
+ debug("DataModel: Getting auth from " + myurl + " with mid=" + mid);
+ $http.get(myurl)
+ .then(function(success)
+ {
+ // console.log ("**** RESULT IS " + JSON.stringify(success));
+ // Look for auth=
+ var auth = success.data.match("auth=(.*?)&");
+ if (auth && (auth[1] != null))
+ {
+ log("DataModel: Extracted a stream authentication key of: " + auth[1]);
+ d.resolve("&auth=" + auth[1]);
+ }
+ else
+ {
+ log("DataModel: Did not find a stream auth key, looking for user=");
+ auth = success.data.match("user=(.*?)&");
+ if (auth && (auth[1] != null))
+ {
+ log("DataModel: Found simple stream auth mode (user=)");
+ d.resolve("&user=" + loginData.username + "&pass=" + loginData.password);
+ }
+ else
+ {
+ log("Data Model: Did not find any stream mode of auth");
+ d.resolve("");
+ }
+ return (d.promise);
+ }
+
+ },
+ function(error)
+ {
+ log("DataModel: Error resolving auth key " + JSON.stringify(error));
+ d.resolve("");
+ return (d.promise);
+ });
+ return (d.promise);
+
+ },
+
+ //-----------------------------------------------------------------------------
+ // This function returns the numdigits for padding capture images
+ //-----------------------------------------------------------------------------
+
+ getKeyConfigParams: function(forceReload)
+ {
+
+ var d = $q.defer();
+
+ if (forceReload == 1 || configParams.ZM_EVENT_IMAGE_DIGITS == '-1')
+ {
+ var apiurl = loginData.apiurl;
+ var myurl = apiurl + '/configs/viewByName/ZM_EVENT_IMAGE_DIGITS.json';
+ debug("Config URL for digits is:" + myurl);
+ $http.get(myurl)
+ .success(function(data)
+ {
+ log("ZM_EVENT_IMAGE_DIGITS is " + data.config.Value);
+ configParams.ZM_EVENT_IMAGE_DIGITS = data.config.Value;
+ d.resolve(configParams.ZM_EVENT_IMAGE_DIGITS);
+ return (d.promise);
+
+ })
+ .error(function(err)
+ {
+ log("Error retrieving ZM_EVENT_IMAGE_DIGITS" + JSON.stringify(err), "error");
+ log("Taking a guess, setting ZM_EVENT_IMAGE_DIGITS to 5");
+ // FIXME: take a plunge and keep it at 5?
+ configParams.ZM_EVENT_IMAGE_DIGITS = 5;
+ d.resolve(configParams.ZM_EVENT_IMAGE_DIGITS);
+ return (d.promise);
+ });
+ }
+ else
+ {
+ log("ZM_EVENT_IMAGE_DIGITS is already configured for " +
+ configParams.ZM_EVENT_IMAGE_DIGITS);
+ d.resolve(configParams.ZM_EVENT_IMAGE_DIGITS);
+ }
+ return (d.promise);
+
+ },
+
+ //--------------------------------------------------------------------------
+ // Useful to know what ZMS is using as its cgi-bin. If people misconfigure
+ // the setting in the app, they can check their logs
+ //--------------------------------------------------------------------------
+ getPathZms: function()
+ {
+ var d = $q.defer();
+ var apiurl = loginData.apiurl;
+ var myurl = apiurl + '/configs/viewByName/ZM_PATH_ZMS.json';
+ debug("Config URL for ZMS PATH is:" + myurl);
+ $http.get(myurl)
+ .success(function(data)
+ {
+ configParams.ZM_PATH_ZMS = data.config.Value;
+ d.resolve(configParams.ZM_PATH_ZMS);
+ return (d.promise);
+ })
+ .error(function(error)
+ {
+ log("Can't retrieving ZM_PATH_ZMS: " + JSON.stringify(error));
+ d.resolve("");
+ return (d.promise);
+ });
+ return (d.promise);
+
+ },
+ //--------------------------------------------------------------------------
+ // returns high or low BW mode
+ //--------------------------------------------------------------------------
+ getBandwidth: function()
+ {
+ return getBandwidth();
+ },
+
+ //-----------------------------------------------------------------------------
+ // This function returns a list of monitors
+ // if forceReload == 1 then it will force an HTTP API request to get a list of monitors
+ // if 0. then it will return back the previously loaded monitor list if one exists, else
+ // will issue a new HTTP API to get it
+
+ // I've wrapped this function in my own promise even though http returns a promise.
+ //-----------------------------------------------------------------------------
+ //
+
+ // returns a non promise version
+ // so if monitors is null, it will return null
+ // As of now, this is only used by EventServer.js to
+ // send the right list of monitors after registration
+ // token
+ getMonitorsNow: function()
+ {
+ return monitors;
+ },
+
+ getMonitors: function(forceReload)
+ {
+ //console.log("** Inside ZMData getMonitors with forceReload=" + forceReload);
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kLoadingMonitors'),
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 200,
+ showDelay: 0
+ });
+
+ var d = $q.defer();
+ if ((monitorsLoaded == 0) || (forceReload == 1)) // monitors are empty or force reload
+ {
+ //console.log("NVRDataModel: Invoking HTTP get to load monitors");
+ log((forceReload == 1) ? "getMonitors:Force reloading all monitors" : "getMonitors:Loading all monitors");
+ var apiurl = loginData.apiurl;
+ var myurl = apiurl + "/monitors.json";
+ //console.log ("API:"+myurl);
+ $http.get(myurl /*,{timeout:15000}*/ )
+ .success(function(data)
+ {
+ //console.log("HTTP success got " + JSON.stringify(data.monitors));
+ monitors = data.monitors;
+ monitors.sort(function(a, b)
+ {
+ return parseInt(a.Monitor.Sequence) - parseInt(b.Monitor.Sequence);
+ });
+ //console.log("promise resolved inside HTTP success");
+ monitorsLoaded = 1;
+
+ reloadMonitorDisplayStatus();
+
+ debug("Now trying to get multi-server data, if present");
+ $http.get(apiurl + "/servers.json")
+ .success(function(data)
+ {
+ // We found a server list API, so lets make sure
+ // we get the hostname as it will be needed for playback
+ log("multi server list loaded" + JSON.stringify(data));
+ multiservers = data.servers;
+
+ for (var i = 0; i < monitors.length; i++)
+ {
+
+ // make them all show for now
+ monitors[i].Monitor.listDisplay = 'show';
+ monitors[i].Monitor.isAlarmed = false;
+ monitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+
+ var serverFound = false;
+ for (var j = 0; j < multiservers.length; j++)
+ {
+ //console.log ("Comparing " + multiservers[j].Server.Id + " AND " + monitors[i].Monitor.ServerId);
+ if (multiservers[j].Server.Id == monitors[i].Monitor.ServerId)
+ {
+ //console.log ("Found match");
+ serverFound = true;
+ break;
+ }
+
+ }
+ if (serverFound)
+ {
+
+ debug("Monitor " + monitors[i].Monitor.Id + " has a recording server hostname of " + multiservers[j].Server.Hostname);
+
+ // Now here is the logic, I need to retrieve serverhostname,
+ // and slap on the host protocol and path. Meh.
+
+ var p = URI.parse(loginData.streamingurl);
+ var s = URI.parse(multiservers[j].Server.Hostname);
+
+ debug("recording server parsed is " + JSON.stringify(s));
+ debug("portal parsed is " + JSON.stringify(p));
+
+ var st = "";
+ var baseurl = "";
+
+ st += (s.scheme ? s.scheme : p.scheme) + "://"; // server scheme overrides
+
+ // if server doesn't have a protocol, what we want is in path
+ if (!s.host)
+ {
+ s.host = s.path;
+ s.path = undefined;
+ }
+
+ st += s.host;
+
+ if (p.port || s.port)
+ {
+ st += (s.port ? ":" + s.port : ":" + p.port);
+
+ }
+
+ baseurl = st;
+
+ st += (s.path ? s.path : p.path);
+
+ //console.log ("----------STREAMING URL PARSED AS " + st);
+
+ monitors[i].Monitor.streamingURL = st;
+ monitors[i].Monitor.baseURL = baseurl;
+ // starting 1.30 we have fid=xxx mode to return images
+ monitors[i].Monitor.imageMode = (versionCompare($rootScope.apiVersion, "1.30") == -1) ? "path" : "fid";
+ debug("API " + $rootScope.apiVersion + ": Monitor " + monitors[i].Monitor.Id + " will use " + monitors[i].Monitor.imageMode + " for direct image access");
+
+ //debug ("Streaming URL for Monitor " + monitors[i].Monitor.Id + " is " + monitors[i].Monitor.streamingURL );
+ //debug ("Base URL for Monitor " + monitors[i].Monitor.Id + " is " + monitors[i].Monitor.baseURL );
+
+ }
+ else
+ {
+ //monitors[i].Monitor.listDisplay = 'show';
+ monitors[i].Monitor.isAlarmed = false;
+ monitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ monitors[i].Monitor.streamingURL = loginData.streamingurl;
+ monitors[i].Monitor.baseURL = loginData.url;
+ monitors[i].Monitor.imageMode = (versionCompare($rootScope.apiVersion, "1.30") == -1) ? "path" : "fid";
+
+ // but now check if forced path
+ if (loginData.forceImageModePath)
+ {
+ debug("Overriding, setting image mode to true as you have requested force enable");
+ monitors[i].Monitor.imageMode = 'path';
+ }
+
+ debug("API " + $rootScope.apiVersion + ": Monitor " + monitors[i].Monitor.Id + " will use " + monitors[i].Monitor.imageMode + " for direct image access");
+ }
+ }
+ // now get packery hide if applicable
+ reloadMonitorDisplayStatus();
+ d.resolve(monitors);
+ })
+ .error(function(err)
+ {
+ log("multi server list loading error");
+ multiservers = [];
+
+ for (var i = 0; i < monitors.length; i++)
+ {
+ //monitors[i].Monitor.listDisplay = 'show';
+ monitors[i].Monitor.isAlarmed = false;
+ monitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ monitors[i].Monitor.streamingURL = loginData.streamingurl;
+ monitors[i].Monitor.baseURL = loginData.url;
+ monitors[i].Monitor.imageMode = (versionCompare($rootScope.apiVersion, "1.30") == -1) ? "path" : "fid";
+ debug("API " + $rootScope.apiVersion + ": Monitor " + monitors[i].Monitor.Id + " will use " + monitors[i].Monitor.imageMode + " for direct image access");
+
+ }
+ d.resolve(monitors);
+
+ });
+
+ $ionicLoading.hide();
+ log("Monitor load was successful, loaded " + monitors.length + " monitors");
+
+ })
+ .error(function(err)
+ {
+ //console.log("HTTP Error " + err);
+ log("Monitor load failed " + JSON.stringify(err), "error");
+ // To keep it simple for now, I'm translating an error
+ // to imply no monitors could be loaded. FIXME: conver to proper error
+ monitors = [];
+ //console.log("promise resolved inside HTTP fail");
+ displayBanner('error', ['error retrieving monitor list', 'please try again']);
+ d.resolve(monitors);
+ $ionicLoading.hide();
+ monitorsLoaded = 0;
+ });
+ return d.promise;
+
+ }
+ else // monitors are loaded
+ {
+ //console.log("Returning pre-loaded list of " + monitors.length + " monitors");
+ log("Returning pre-loaded list of " + monitors.length + " monitors");
+ d.resolve(monitors);
+ //console.log ("Returning"+JSON.stringify(monitors));
+ $ionicLoading.hide();
+ return d.promise;
+ }
+
+ },
+
+ //-----------------------------------------------------------------------------
+ //
+ //-----------------------------------------------------------------------------
+ setMonitors: function(mon)
+ {
+ //console.log("ZMData setMonitors called with " + mon.length + " monitors");
+ monitors = mon;
+ },
+
+ processFastLogin: function()
+ {
+ var d = $q.defer();
+ if (1)
+ {
+ d.reject("not implemented");
+ return d.promise;
+ }
+ console.log("inside processFastLogin");
+ if (!loginData.fastLogin)
+ {
+ console.log("Fast login not set");
+ d.reject("fast login not enabled");
+ debug("fast login not enabled");
+ return d.promise;
+
+ }
+ else //fastlogin is on
+ {
+ localforage.getItem("lastLogin")
+ .then(function(succ)
+ {
+ console.log("fast login DB found");
+ var dt = moment(succ);
+
+ if (dt.isValid())
+ {
+ debug("Got last login as " + dt.toString());
+ if (moment.duration(moment().diff(dt)).asHours() >= 2)
+ {
+ d.reject("duration since last login >=2hrs, need to relogin");
+ return d.promise;
+ }
+ else
+ {
+ d.resolve("fast login is valid, less then 2 hrs");
+ return d.promise;
+ }
+ }
+ else
+ {
+ console.log("Invalid date found");
+ d.reject("last-login invalid");
+ return d.promise;
+
+ }
+ },
+ function(e)
+ {
+ console.log("fastlogin DB not found");
+ d.reject("last-login not found, fastlogin rejected");
+ return d.promise;
+ });
+
+ }
+ return d.promise;
+ },
+
+ // returns if this mid is hidden or not
+ isNotHidden: function(mid)
+ {
+ var notHidden = true;
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (monitors[i].Monitor.Id == mid)
+ {
+ notHidden = (monitors[i].Monitor.listDisplay == 'show') ? true : false;
+ break;
+ }
+
+ }
+ return notHidden;
+
+ },
+
+ getLocalTimeZoneNow: function()
+ {
+ return moment.tz.guess();
+ },
+ //returns TZ value immediately (sync)
+
+ getTimeZoneNow: function()
+ {
+ // console.log ("getTimeZoneNow: " + tz ? tz : moment.tz.guess());
+ return tz ? tz : moment.tz.guess();
+ },
+
+ // returns server timezone, failing which local timezone
+ // always resolves true
+
+ isTzSupported: function()
+ {
+ return isTzSupported;
+ },
+
+ getTimeZone: function(isForce)
+ {
+
+ var d = $q.defer();
+ if (!tz || isForce)
+ {
+
+ log("First invocation of TimeZone, asking server");
+ var apiurl = loginData.apiurl + '/host/getTimeZone.json';
+ $http.get(apiurl)
+ .then(function(success)
+ {
+ tz = success.data.tz;
+ d.resolve(tz);
+ debug("Timezone API response is:" + success.data.tz);
+ if (success.data.tz !== undefined)
+ isTzSupported = true;
+ else
+ isTzSupported = false;
+ $rootScope.$emit('tz-updated');
+ return (d.promise);
+
+ },
+ function(error)
+ {
+ tz = moment.tz.guess();
+ debug("Timezone API error handler, guessing local:" + tz);
+ d.resolve(tz);
+ isTzSupported = false;
+ return (d.promise);
+ });
+
+ }
+ else
+ {
+ d.resolve(tz);
+ return d.promise;
+ }
+
+ return d.promise;
+ },
+
+ //-----------------------------------------------------------------------------
+ // When I display events in the event controller, this is the first function I call
+ // This returns the total number of pages
+ // I then proceed to display pages in reverse order to display the latest events first
+ // I also reverse sort them in NVRDataModel to sort by date
+ // All this effort because the ZM APIs return events in sorted order, oldest first. Yeesh.
+ //-----------------------------------------------------------------------------
+
+ getEventsPages: function(monitorId, startTime, endTime)
+ {
+ //console.log("********** INSIDE EVENTS PAGES ");
+ var apiurl = loginData.apiurl;
+
+ var myurl = apiurl + "/events/index";
+ if (monitorId != 0)
+ myurl = myurl + "/MonitorId:" + monitorId;
+ if (startTime)
+ myurl = myurl + "/StartTime >=:" + startTime;
+ if (endTime)
+ myurl = myurl + "/EndTime <=:" + endTime;
+
+ myurl = myurl + "/AlarmFrames >=:" + (loginData.enableAlarmCount ? loginData.minAlarmCount : 0);
+
+ myurl = myurl + ".json";
+ //console.log (">>>>>Constructed URL " + myurl);
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kCalcEventSize') + '...',
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 200,
+ showDelay: 0
+ });
+
+ //var myurl = (monitorId == 0) ? apiurl + "/events.json?page=1" : apiurl + "/events/index/MonitorId:" + monitorId + ".json?page=1";
+ var d = $q.defer();
+ $http.get(myurl)
+ .success(function(data)
+ {
+ $ionicLoading.hide();
+ //console.log ("**** EVENTS PAGES I GOT "+JSON.stringify(data));
+ //console.log("**** PAGE COUNT IS " + data.pagination.pageCount);
+ d.resolve(data.pagination);
+ return d.promise;
+ })
+ .error(function(error)
+ {
+ $ionicLoading.hide();
+ // console.log("*** ERROR GETTING TOTAL PAGES ***");
+ log("Error retrieving page count of events " + JSON.stringify(error), "error");
+ displayBanner('error', ['error retrieving event page count', 'please try again']);
+
+ d.reject(error);
+ return d.promise;
+ });
+ return d.promise;
+
+ },
+
+ //-----------------------------------------------------------------------------
+ // This function returns events for specific monitor or all monitors
+ // You get here by tapping on events in the monitor screen or from
+ // the menu events option
+ // monitorId == 0 means all monitors (ZM starts from 1)
+ //-----------------------------------------------------------------------------
+
+ getEvents: function(monitorId, pageId, loadingStr, startTime, endTime)
+ {
+
+ //console.log("ZMData getEvents called with ID=" + monitorId + "and Page=" + pageId);
+
+ if (!loadingStr)
+ {
+ loadingStr = $translate.instant('kLoadingEvents')+"...";
+ }
+ //if (loadingStr) loa
+
+ if (loadingStr != 'none')
+ {
+ $ionicLoading.show(
+ {
+ template: loadingStr,
+ animation: 'fade-in',
+ showBackdrop: true,
+ maxWidth: 200,
+ showDelay: 0,
+ duration: zm.loadingTimeout, //specifically for Android - http seems to get stuck at times
+ });
+ }
+
+ var d = $q.defer();
+ var myevents = [];
+ var apiurl = loginData.apiurl;
+
+ var myurl = apiurl + "/events/index";
+ if (monitorId != 0)
+ myurl = myurl + "/MonitorId:" + monitorId;
+ if (startTime)
+ myurl = myurl + "/StartTime >=:" + startTime;
+ if (endTime)
+ myurl = myurl + "/EndTime <=:" + endTime;
+
+ myurl = myurl + "/AlarmFrames >=:" + (loginData.enableAlarmCount ? loginData.minAlarmCount : 0);
+ myurl = myurl + ".json";
+
+ if (pageId)
+ {
+ myurl = myurl + "?page=" + pageId;
+ }
+ else
+ {
+ //console.log("**** PAGE WAS " + pageId);
+ }
+
+ // Simulated data
+
+ // myurl = "https://api.myjson.com/bins/4jx44.json";
+
+ //console.log (">>>>>Constructed URL " + myurl);
+
+ $http.get(myurl /*,{timeout:15000}*/ )
+ .success(function(data)
+ {
+ if (loadingStr != 'none') $ionicLoading.hide();
+ //myevents = data.events;
+ myevents = data.events.reverse();
+ if (monitorId == 0)
+ {
+ oldevents = myevents;
+ }
+ //console.log (JSON.stringify(data));
+ // console.log("DataModel Returning " + myevents.length + "events for page" + pageId);
+ d.resolve(myevents);
+ return d.promise;
+
+ })
+ .error(function(err)
+ {
+ if (loadingStr != 'none') $ionicLoading.hide();
+ displayBanner('error', ['error retrieving event list', 'please try again']);
+ //console.log("HTTP Events error " + err);
+ log("Error fetching events for page " + pageId + " Err: " + JSON.stringify(err), "error");
+ // I need to reject this as I have infinite scrolling
+ // implemented in EventCtrl.js --> and if it does not know
+ // it got an error going to the next page, it will get into
+ // an infinite loop as we are at the bottom of the list always
+
+ d.reject(myevents);
+
+ // FIXME: Check what pagination does to this logic
+ if (monitorId == 0)
+ {
+ oldevents = [];
+ }
+ return d.promise;
+ });
+ return d.promise;
+ },
+
+ //-----------------------------------------------------------------------------
+ //
+ //-----------------------------------------------------------------------------
+ getMontageSize: function()
+ {
+ return loginData.montageSize;
+ },
+
+ //-----------------------------------------------------------------------------
+ //
+ //-----------------------------------------------------------------------------
+ setMontageSize: function(montage)
+ {
+ loginData.montageSize = montage;
+ },
+
+ //-----------------------------------------------------------------------------
+ //
+ //-----------------------------------------------------------------------------
+ getMonitorsLoaded: function()
+ {
+ // console.log("**** Inside promise function ");
+ var deferred = $q.defer();
+ if (monitorsLoaded != 0)
+ {
+ deferred.resolve(monitorsLoaded);
+ }
+
+ return deferred.promise;
+ },
+
+ //-----------------------------------------------------------------------------
+ //
+ //-----------------------------------------------------------------------------
+ setMonitorsLoaded: function(loaded)
+ {
+ // console.log("ZMData.setMonitorsLoaded=" + loaded);
+ monitorsLoaded = loaded;
+ },
+
+ //-----------------------------------------------------------------------------
+ // returns the next monitor ID in the list
+ // used for swipe next
+ //-----------------------------------------------------------------------------
+ getNextMonitor: function(monitorId, direction)
+ {
+ var id = parseInt(monitorId);
+ var foundIndex = -1;
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (parseInt(monitors[i].Monitor.Id) == id)
+ {
+ foundIndex = i;
+ break;
+ }
+ }
+ if (foundIndex != -1)
+ {
+ foundIndex = foundIndex + direction;
+ // wrap around if needed
+ if (foundIndex < 0) foundIndex = monitors.length - 1;
+ if (foundIndex >= monitors.length) foundIndex = 0;
+ return (monitors[foundIndex].Monitor.Id);
+ }
+ else
+ {
+ log("getNextMonitor could not find monitor " + monitorId);
+ return (monitorId);
+ }
+
+ },
+
+ //-----------------------------------------------------------------------------
+ // Given a monitor Id it returns the monitor name
+ // FIXME: Can I do a better job with associative arrays?
+ //-----------------------------------------------------------------------------
+ getMonitorName: function(id)
+ {
+ var idnum = parseInt(id);
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (parseInt(monitors[i].Monitor.Id) == idnum)
+ {
+ // console.log ("Matched, exiting getMonitorname");
+ return monitors[i].Monitor.Name;
+ }
+
+ }
+ return "(Unknown)";
+ },
+
+ getMonitorObject: function(id)
+ {
+ var idnum = parseInt(id);
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (parseInt(monitors[i].Monitor.Id) == idnum)
+ {
+ // console.log ("Matched, exiting getMonitorname");
+ return monitors[i];
+ }
+
+ }
+ return "(Unknown)";
+ },
+
+ getImageMode: function(id)
+ {
+ var idnum = parseInt(id);
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (parseInt(monitors[i].Monitor.Id) == idnum)
+ {
+ // console.log ("Matched, exiting getMonitorname");
+ return monitors[i].Monitor.imageMode;
+ }
+
+ }
+ return "(Unknown)";
+ },
+
+ getStreamingURL: function(id)
+ {
+ var idnum = parseInt(id);
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (parseInt(monitors[i].Monitor.Id) == idnum)
+ {
+ // console.log ("Matched, exiting getMonitorname");
+ return monitors[i].Monitor.streamingURL;
+ }
+
+ }
+ return "(Unknown)";
+ },
+
+ getBaseURL: function(id)
+ {
+ var idnum = parseInt(id);
+ for (var i = 0; i < monitors.length; i++)
+ {
+ if (parseInt(monitors[i].Monitor.Id) == idnum)
+ {
+ // console.log ("Matched, exiting getMonitorname");
+ return monitors[i].Monitor.baseURL;
+ }
+
+ }
+ return "(Unknown)";
+ },
+
+ };
+ }
+]);
diff --git a/www/js/DevOptionsCtrl.js b/www/js/DevOptionsCtrl.js
new file mode 100644
index 00000000..970c690d
--- /dev/null
+++ b/www/js/DevOptionsCtrl.js
@@ -0,0 +1,139 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('zmApp.DevOptionsCtrl', ['$scope', '$rootScope', '$ionicModal', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$ionicPopup', '$http', '$q', '$ionicLoading', '$ionicHistory', '$state', 'SecuredPopups', '$translate', function($scope, $rootScope, $ionicModal, zm, NVRDataModel, $ionicSideMenuDelegate, $ionicPopup, $http, $q, $ionicLoading, $ionicHistory, $state, SecuredPopups, $translate)
+{
+
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ // $scope.this.will.crash = 1;
+
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //----------------------------------------------------------------
+ // Save anyway when you exit
+ //----------------------------------------------------------------
+
+ $scope.$on('$ionicView.beforeLeave', function()
+ {
+ saveDevOptions();
+
+ });
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log("**VIEW ** DevOptions Ctrl Entered");
+ $scope.loginData = NVRDataModel.getLogin();
+
+ NVRDataModel.setAwake(false);
+ });
+
+ $scope.isTzSupported = function()
+ {
+ return NVRDataModel.isTzSupported();
+ };
+
+ $scope.getTimeZoneNow = function()
+ {
+ return NVRDataModel.getTimeZoneNow();
+ };
+
+ //------------------------------------------------------------------
+ // Perform the login action when the user submits the login form
+ //------------------------------------------------------------------
+
+ function saveDevOptions()
+ {
+ NVRDataModel.debug("SaveDevOptions: called");
+
+ if (parseInt($scope.loginData.cycleMonitorsInterval) < zm.minCycleTime)
+ {
+ $scope.loginData.cycleMonitorsInterval = zm.minCycleTime.toString();
+ }
+ if ((parseInt($scope.loginData.maxFPS) < 0) || (parseInt($scope.loginData.maxFPS) > zm.maxFPS))
+ {
+ $scope.loginData.maxFPS = zm.defaultFPS.toString();
+ }
+
+ if (parseInt($scope.loginData.refreshSec) <= 0)
+ {
+ NVRDataModel.debug("SaveDevOptions: refresh sec was too low at " +
+ $scope.loginData.refreshSec + " reset to 1");
+ $scope.loginData.refreshSec = 1;
+
+ }
+
+ if ((parseInt($scope.loginData.montageQuality) < zm.safeMontageLimit) ||
+ (parseInt($scope.loginData.montageQuality) > 100))
+ {
+ $scope.loginData.montageQuality = 100;
+ }
+
+ if ((parseInt($scope.loginData.singleImageQuality) < zm.safeImageQuality) ||
+ (parseInt($scope.loginData.singleImageQuality) > 100))
+ {
+ $scope.loginData.singleImageQuality = zm.safeImageQuality.toString();
+ }
+
+ NVRDataModel.debug("SaveDevOptions: Saving to disk");
+ NVRDataModel.setLogin($scope.loginData);
+ NVRDataModel.getMonitors(1);
+
+ }
+
+ $scope.saveDevOptions = function()
+ {
+
+ saveDevOptions();
+ // $rootScope.zmPopup.close();
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kSettingsSaved'),
+ template: "{{'kExploreEnjoy' | translate }} {{$root.appName}}",
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ }).then(function(res)
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ });
+
+ };
+ //------------------------------------------------------------------
+ // controller main
+ //------------------------------------------------------------------
+
+}]);
diff --git a/www/js/EventCtrl.js b/www/js/EventCtrl.js
new file mode 100644
index 00000000..260ecf47
--- /dev/null
+++ b/www/js/EventCtrl.js
@@ -0,0 +1,3002 @@
+/* jshint -W041 */
+/*jshint bitwise: false*/
+/* jslint browser: true*/
+/* global saveAs, cordova,StatusBar,angular,console,moment, MobileAccessibility, gifshot, ReadableStream , LibraryHelper, GifWriter, NeuQuant, LocalFileSystem, FileError*/
+
+// This is the controller for Event view. StateParams is if I recall the monitor ID.
+// This was before I got access to the new APIs. FIXME: Revisit this code to see what I am doing with it
+// and whether the new API has a better mechanism
+
+angular.module('zmApp.controllers')
+
+// alarm frames filter
+.filter('selectFrames', function($filter, $translate)
+{
+
+ // Create the return function and set the required parameter name to **input**
+ return function(input, typeOfFrames)
+ {
+
+ var out = [];
+
+ angular.forEach(input, function(item)
+ {
+
+ if (typeOfFrames == $translate.instant('kShowTimeDiffFrames'))
+ {
+ if (item.type == $translate.instant('kShowTimeDiffFrames'))
+ out.push(item);
+ }
+ else
+ out.push(item);
+
+ });
+
+ return out;
+ };
+
+})
+
+.controller('zmApp.EventCtrl', ['$scope', '$rootScope', 'zm', 'NVRDataModel', 'message', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$ionicPlatform', '$ionicSlideBoxDelegate', '$ionicPosition', '$ionicPopover', '$ionicPopup', 'EventServer', '$sce', '$cordovaBadge', '$cordovaLocalNotification', '$q', 'carouselUtils', '$translate', '$cordovaFileTransfer', '$cordovaFile', '$ionicListDelegate',function($scope, $rootScope, zm, NVRDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $ionicPlatform, $ionicSlideBoxDelegate, $ionicPosition, $ionicPopover, $ionicPopup, EventServer, $sce, $cordovaBadge, $cordovaLocalNotification, $q, carouselUtils, $translate, $cordovaFileTransfer, $cordovaFile, $ionicListDelegate)
+{
+
+ // events in last 5 minutes
+ // TODO https://server/zm/api/events/consoleEvents/5%20minute.json
+
+ //---------------------------------------------------
+ // Controller main
+ //---------------------------------------------------
+
+ var loginData;
+ var oldEvent;
+ var scrollbynumber;
+ var eventImageDigits = 5; // failsafe
+ var eventsPage;
+ var moreEvents;
+ var pageLoaded;
+ var enableLoadMore;
+ var lData;
+ var showHiddenMonitors;
+ var ionRangeWatcher;
+ var mycarouselWatcher;
+ var nolangFrom;
+ var nolangTo;
+
+ $scope.typeOfFrames = $translate.instant('kShowTimeDiffFrames');
+ $scope.outlineMotion = false;
+ $scope.outlineMotionParam = "";
+ var eventsListScrubHeight = eventsListScrubHeight;
+ var eventsListDetailsHeight = eventsListDetailsHeight;
+
+ //---------------------------------------------------
+ // initial code
+ //---------------------------------------------------
+
+ //we come here is TZ is updated after the view loads
+ $rootScope.$on('tz-updated', function()
+ {
+ $scope.tzAbbr = NVRDataModel.getTimeZoneNow();
+ NVRDataModel.debug("Timezone API updated timezone to " + NVRDataModel.getTimeZoneNow());
+ });
+
+ $rootScope.$on("language-changed", function()
+ {
+ NVRDataModel.log(">>>>>>>>>>>>>>> language changed");
+ doRefresh();
+ });
+
+ $scope.$on('$ionicView.afterEnter', function()
+ {
+ //console.log ("********* AFTER ENTER");
+ //
+ $ionicListDelegate.canSwipeItems(true);
+ NVRDataModel.debug ("enabling options swipe");
+
+ // see if we come from monitors, if so, don't filter events
+ if ($ionicHistory.backTitle() == 'Monitors')
+ {
+ showHiddenMonitors = true;
+ }
+ else
+ {
+ showHiddenMonitors = false;
+ }
+
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ $scope.tzAbbr = moment().tz(moment.tz.guess()).zoneAbbr();
+ }
+ else
+ {
+ $scope.tzAbbr = moment().tz(NVRDataModel.getTimeZoneNow()).zoneAbbr();
+ }
+
+ $scope.events = [];
+ getInitialEvents();
+ setupWatchers();
+ footerExpand();
+ });
+
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+
+ //console.log ("********* BEFORE ENTER");
+ //
+ $scope.gifshotSupported = true;
+ document.addEventListener("pause", onPause, false);
+ //console.log("I got STATE PARAM " + $stateParams.id);
+ $scope.id = parseInt($stateParams.id, 10);
+ $scope.showEvent = $stateParams.playEvent || false;
+
+ console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
+
+ NVRDataModel.log("EventCtrl called with: EID=" + $scope.id + " playEvent = " + $scope.showEvent);
+
+ // This is the only view that hardcodes row size due to
+ // collection repeat, so lets re-get the text size if it has changed
+ // note that there may be a delay as its a callback - so might involve
+ // a UI jiggle
+
+ if (window.cordova)
+ MobileAccessibility.getTextZoom(getTextZoomCallback);
+
+ eventsListDetailsHeight = parseInt(zm.eventsListDetailsHeight * $rootScope.textScaleFactor);
+ eventsListScrubHeight = parseInt(zm.eventsListScrubHeight * $rootScope.textScaleFactor);
+
+ NVRDataModel.debug(">>>height of list/scrub set to " + eventsListDetailsHeight + " and " + eventsListScrubHeight);
+
+ pageLoaded = false;
+ enableLoadMore = true;
+
+ $scope.mycarousel = {
+ index: 0
+ };
+
+ $scope.ionRange = {
+ index: 1
+ };
+ $scope.animationInProgress = false;
+
+ $scope.hours = [];
+ $scope.days = [];
+ $scope.weeks = [];
+ $scope.months = [];
+
+ $scope.eventList = {
+ showDelete: false
+ };
+
+ $scope.slides = []; // will hold scrub frames
+ $scope.totalEventTime = 0; // used to display max of progress bar
+ $scope.currentEventTime = 0;
+ oldEvent = ""; // will hold previous event that had showScrub = true
+ scrollbynumber = 0;
+ $scope.eventsBeingLoaded = true;
+ $scope.FrameArray = []; // will hold frame info from detailed Events API
+ loginData = NVRDataModel.getLogin();
+ NVRDataModel.getKeyConfigParams(0)
+ .then(function(data)
+ {
+ //console.log ("***GETKEY: " + JSON.stringify(data));
+ eventImageDigits = parseInt(data);
+ NVRDataModel.log("Image padding digits reported as " + eventImageDigits);
+ });
+
+ $scope.showSearch = false;
+ eventsPage = 1;
+ moreEvents = true;
+ $scope.viewTitle = {
+ title: ""
+ };
+ $scope.search = {
+ text: ""
+
+ };
+ $scope.myfilter = "";
+
+ $scope.loginData = NVRDataModel.getLogin();
+ $scope.playbackURL = $scope.loginData.url;
+
+ });
+
+ function getEventObject(eid)
+ {
+
+ var apiurl = NVRDataModel.getLogin().apiurl + '/events/' + eid + '.json';
+
+ $http.get(apiurl)
+ .success(function(data) {})
+ .error(function(err) {});
+
+ }
+
+ function getTextZoomCallback(tz)
+ {
+ $rootScope.textScaleFactor = parseFloat(tz + "%") / 100.0;
+ NVRDataModel.debug("text zoom factor is " + $rootScope.textScaleFactor);
+ }
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+
+ $ionicPlatform.registerBackButtonAction(function(e)
+ {
+ e.preventDefault();
+ if ($scope.modal != undefined && $scope.modal.isShown())
+ {
+ // switch off awake, as liveview is finished
+ NVRDataModel.debug("Modal is open, closing it");
+ NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+ }
+ else
+ {
+ NVRDataModel.debug("Modal is closed, so toggling or exiting");
+ if (!$ionicSideMenuDelegate.isOpenLeft())
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+
+ }
+ else
+ {
+ navigator.app.exitApp();
+ }
+
+ }
+
+ }, 1000);
+
+ //--------------------------------------
+ // monitor the slider for carousels
+ //--------------------------------------
+ function setupWatchers()
+ {
+ NVRDataModel.debug("Setting up carousel watchers");
+
+ ionRangeWatcher = $scope.$watch('ionRange.index', function()
+ {
+ // console.log ("Watching index");
+ $scope.mycarousel.index = parseInt($scope.ionRange.index) - 1;
+ if (carouselUtils.getStop() == true)
+ return;
+
+ //console.log ("***ION RANGE CHANGED TO " + $scope.mycarousel.index);
+ });
+
+ mycarouselWatcher = $scope.$watch('mycarousel.index', function()
+ {
+
+ if ($scope.event && $scope.ionRange.index == parseInt($scope.event.Event.Frames) - 1)
+ {
+ if (!$scope.modal || $scope.modal.isShown() == false)
+ {
+ // console.log("quick scrub playback over");
+ carouselUtils.setStop(true);
+ $scope.ionRange.index = 0;
+ $scope.mycarousel.index = 1;
+ }
+
+ }
+ if (carouselUtils.getStop() == true)
+ return;
+ $scope.ionRange.index = ($scope.mycarousel.index + 1).toString();
+ // console.log ("***IONRANGE RANGE CHANGED TO " + $scope.ionRange.index);
+
+ });
+
+ }
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+
+ function getInitialEvents()
+ {
+ NVRDataModel.debug("getInitialEvents called");
+ var lData = NVRDataModel.getLogin();
+
+ // If you came from Monitors, disregard hidden monitors in montage
+ /* if (lData.persistMontageOrder && stackState != "Monitors") {
+ var tempMon = message;
+ $scope.monitors = NVRDataModel.applyMontageMonitorPrefs(tempMon, 2)[0];
+ } else*/
+ $scope.monitors = message;
+
+ if ($scope.monitors.length == 0)
+ {
+ var pTitle = $translate.instant('kNoMonitors');
+ $ionicPopup.alert(
+ {
+ title: pTitle,
+ template: "{{'kCheckCredentials' | translate }}",
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+
+ $scope.events = [];
+
+ // First get total pages and then
+ // start from the latest. If this fails, nothing displays
+
+ NVRDataModel.debug("EventCtrl: grabbing # of event pages");
+ nolangFrom = "";
+ nolangTo = "";
+ if ($rootScope.fromString)
+ nolangFrom = moment($rootScope.fromString).locale('en').format("YYYY-MM-DD HH:mm:ss");
+ if ($rootScope.toString)
+ nolangTo = moment($rootScope.toString).locale('en').format("YYYY-MM-DD HH:mm:ss");
+ NVRDataModel.getEventsPages($scope.id, nolangFrom, nolangTo)
+ .then(function(data)
+ {
+ eventsPage = data.pageCount || 1;
+ NVRDataModel.debug("EventCtrl: found " + eventsPage + " pages of events");
+
+ pageLoaded = true;
+ $scope.viewTitle.title = data.count;
+ NVRDataModel.debug("EventCtrl: grabbing events for: id=" + $scope.id + " Date/Time:" + $rootScope.fromString +
+ "-" + $rootScope.toString);
+ nolangFrom = "";
+ nolangTo = "";
+ if ($rootScope.fromString)
+ nolangFrom = moment($rootScope.fromString).locale('en').format("YYYY-MM-DD HH:mm:ss");
+ if ($rootScope.toString)
+ nolangTo = moment($rootScope.toString).locale('en').format("YYYY-MM-DD HH:mm:ss");
+
+ NVRDataModel.getEvents($scope.id, eventsPage, "", nolangFrom, nolangTo)
+ .then(function(data)
+ {
+
+ var myevents = data;
+ NVRDataModel.debug("EventCtrl: success, got " + myevents.length + " events");
+ var loginData = NVRDataModel.getLogin();
+ for (var i = 0; i < myevents.length; i++)
+ {
+
+ var idfound = true;
+ if (loginData.persistMontageOrder)
+ {
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == myevents[i].Event.MonitorId && (NVRDataModel.isNotHidden(myevents[i].Event.MonitorId) || showHiddenMonitors))
+ {
+
+ idfound = true;
+ break;
+ }
+ }
+ }
+
+ myevents[i].Event.humanizeTime = humanizeTime(myevents[i].Event.StartTime);
+ myevents[i].Event.streamingURL = NVRDataModel.getStreamingURL(myevents[i].Event.MonitorId);
+ myevents[i].Event.baseURL = NVRDataModel.getBaseURL(myevents[i].Event.MonitorId);
+ myevents[i].Event.imageMode = NVRDataModel.getImageMode(myevents[i].Event.MonitorId);
+
+ //console.log ("***** MULTISERVER STREAMING URL FOR EVENTS " + myevents[i].Event.streamingURL);
+
+ // console.log ("***** MULTISERVER BASE URL FOR EVENTS " + myevents[i].Event.baseURL);
+
+ myevents[i].Event.MonitorName = NVRDataModel.getMonitorName(myevents[i].Event.MonitorId);
+ myevents[i].Event.ShowScrub = false;
+ myevents[i].Event.height = eventsListDetailsHeight;
+ // now construct base path
+ myevents[i].Event.BasePath = computeBasePath(myevents[i]);
+ myevents[i].Event.relativePath = computeRelativePath(myevents[i]);
+
+ // in multiserver BasePath is login url for frames
+ // http://login.url/index.php?view=frame&eid=19696772&fid=21
+
+ // console.log ("COMPARING "+NVRDataModel.getLogin().url+ " TO " +myevents[i].Event.baseURL);
+ if (NVRDataModel.getLogin().url != myevents[i].Event.baseURL)
+ {
+ //NVRDataModel.debug ("Multi server, changing base");
+ myevents[i].Event.baseURL = NVRDataModel.getLogin().url;
+
+ }
+
+ if (myevents[i].Event.imageMode == 'path')
+ //if (1)
+ myevents[i].Event.videoPath = myevents[i].Event.baseURL + "/events/" + myevents[i].Event.relativePath + myevents[i].Event.DefaultVideo;
+ else
+ myevents[i].Event.videoPath = myevents[i].Event.baseURL + "/index.php?view=view_video&eid=" + myevents[i].Event.Id;
+
+ if (idfound)
+ {
+ $scope.events.push(myevents[i]);
+ }
+ else
+ {
+ //console.log ("Skipping Event MID = " + myevents[i].Event.MonitorId);
+ }
+
+ } //for
+
+ //$scope.events = myevents;
+ // we only need to stop the template from loading when the list is empty
+ // so this can be false once we have _some_ content
+ // FIXME: check reload
+ $scope.eventsBeingLoaded = false;
+ // to avoid only few events being displayed
+ // if last page has less events
+ //console.log("**Loading Next Page ***");
+ if (myevents.length < 50)
+ {
+ NVRDataModel.debug("EventCtrl:loading one more page just in case we don't have enough to display");
+ loadMore();
+ }
+ });
+
+ });
+ }
+
+ //-------------------------------------------------------
+ // Tapping on a frame shows this image
+ //------------------------------------------------------
+
+ function SaveSuccess()
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDone'),
+ noBackdrop: true,
+ duration: 1000
+ });
+ NVRDataModel.debug("ModalCtrl:Photo saved successfuly");
+ }
+
+ function SaveError(e)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kErrorSave'),
+ noBackdrop: true,
+ duration: 2000
+ });
+ NVRDataModel.log("Error saving image: " + e.message);
+ //console.log("***ERROR");
+ }
+
+ function saveNow(imgsrc, r, f)
+ {
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSavingSnapshot') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ var url = imgsrc;
+ NVRDataModel.log("saveNow: File path to grab is " + url);
+
+ var img = new Image();
+ img.onload = function()
+ {
+ // console.log("********* ONLOAD");
+ var canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ var context = canvas.getContext('2d');
+ context.drawImage(img, 0, 0);
+
+ var imageDataUrl = canvas.toDataURL('image/jpeg', 1.0);
+ var imageData = imageDataUrl.replace(/data:image\/jpeg;base64,/, '');
+
+ if ($rootScope.platformOS != "desktop")
+ {
+ try
+ {
+
+ cordova.exec(
+ SaveSuccess,
+ SaveError,
+ 'Canvas2ImagePlugin',
+ 'saveImageDataToLibrary', [imageData]
+ );
+ // carouselUtils.setStop(curState);
+ }
+ catch (e)
+ {
+
+ SaveError(e.message);
+ // carouselUtils.setStop(curState);
+ }
+ }
+ else
+ {
+
+ var fname = r + f + ".png";
+ fname = fname.replace(/\//, "-");
+ fname = fname.replace(/\.jpg/, '');
+
+ canvas.toBlob(function(blob)
+ {
+ saveAs(blob, fname);
+ SaveSuccess();
+ });
+ }
+ };
+ try
+ {
+ img.src = url;
+ // console.log ("SAVING IMAGE SOURCE");
+ }
+ catch (e)
+ {
+ SaveError(e.message);
+ }
+
+ }
+
+
+
+
+ function writeFile2( path, file, blob, isAppend)
+ {
+ var csize = 4 * 1024 * 1024; // 4MB
+ var d = $q.defer();
+ NVRDataModel.debug ("Inside writeFile2 with blob size="+blob.size);
+
+ // nothing more to write, so all good?
+ if (!blob.size)
+ {
+ NVRDataModel.debug ("writeFile2 all done");
+ d.resolve(true);
+ return $q.resolve(true);
+ }
+
+
+ if (!isAppend)
+ {
+ // return the delegated promise, even if it fails
+ return $cordovaFile.writeFile(path, file, blob.slice(0,csize), true)
+ .then (function (succ) {
+ return writeFile2(path,file,blob.slice(csize),true);
+ });
+ }
+ else
+ {
+ // return the delegated promise, even if it fails
+ return $cordovaFile.writeExistingFile(path, file, blob.slice(0,csize))
+ .then (function (succ) {
+ return writeFile2(path,file,blob.slice(csize),true);
+ });
+ }
+
+
+ }
+
+ function writeFile(path, __filename, __data){
+ var d = $q.defer();
+ console.log ("inside write file");
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, __data.size+5000, onFileSystemSuccess, fail);
+
+ function fail(e)
+ {
+ var msg = '';
+
+ switch (e.code) {
+ case FileError.QUOTA_EXCEEDED_ERR:
+ msg = 'QUOTA_EXCEEDED_ERR';
+ break;
+ case FileError.NOT_FOUND_ERR:
+ msg = 'NOT_FOUND_ERR';
+ break;
+ case FileError.SECURITY_ERR:
+ msg = 'SECURITY_ERR';
+ break;
+ case FileError.INVALID_MODIFICATION_ERR:
+ msg = 'INVALID_MODIFICATION_ERR';
+ break;
+ case FileError.INVALID_STATE_ERR:
+ msg = 'INVALID_STATE_ERR';
+ break;
+ default:
+ msg = 'Unknown Error';
+ break;
+ }
+
+ console.log('Error: ' + msg);
+ }
+ function onFileSystemSuccess()
+ {
+ console.log ("Got temporary FS");
+ window.resolveLocalFileSystemURL(path, function(dir){
+ dir.getFile(__filename, {create:true}, function(file){
+ file.createWriter(function(fileWriter){
+ //var blob = new Blob([__data], {type:'text/plain'});
+ console.log ("about to write "+__data.size+" bytes");
+ //var blob = new Blob([__data], {type:'text/plain'});
+ fileWriter.write(__data);
+ fileWriter.onwrite = function(e) {
+ NVRDataModel.debug ("write complete");
+ d.resolve();
+ return d.promise;
+ };
+
+ fileWriter.onerror = function(e) {
+ NVRDataModel.debug ("write error in filewriter:"+JSON.stringify(e));
+ d.reject();
+ return d.promise;
+ };
+
+ });
+ });
+
+ },
+ function (err) {
+ d.reject(err);
+ return d.promise;
+ });
+ }
+ return d.promise;
+ }
+
+
+ function moveImageToGallery(fname)
+ {
+ // this is https://github.com/terikon/cordova-plugin-photo-library
+
+ NVRDataModel.debug("moveImageToGallery called with " + fname);
+ cordova.plugins.photoLibrary.saveImage(fname, "zmNinja",onSuccess, onError);
+ //LibraryHelper.saveImageToLibrary(onSuccess, onError, fname, "zmNinja");
+
+ function onSuccess(results)
+ {
+
+ NVRDataModel.debug("Removing temp file");
+
+ if ($rootScope.platformOS == 'ios') {
+ $cordovaFile.removeFile(cordova.file.documentsDirectory, "temp-file.gif");
+ }
+ else
+ $cordovaFile.removeFile(cordova.file.dataDirectory, "temp-file.gif");
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDone'),
+ noBackdrop: true,
+ duration: 2000
+ });
+
+
+ }
+
+ function onError(error)
+ {
+ console.log("Error: " + error);
+
+ }
+ }
+
+ $scope.downloadFileToDevice = function(path, eid)
+ {
+
+ NVRDataModel.setAwake(true);
+ var tp;
+ if ($rootScope.platformOS == 'ios')
+ tp = cordova.file.documentsDirectory + "temp-video.mp4";
+ else
+ tp = cordova.file.dataDirectory + "temp-video.mp4";
+
+ var th = true;
+ var opt = {};
+ //path = "http://techslides.com/demos/sample-videos/small.mp4";
+
+ NVRDataModel.debug("Saving temporary video to: " + tp);
+ $cordovaFileTransfer.download(path, tp, opt, th)
+ .then(function(result)
+ {
+ NVRDataModel.debug("Moving to gallery...");
+ var ntp;
+ ntp = tp.indexOf('file://') === 0 ? tp.slice(7) : tp;
+
+ $timeout(function()
+ {
+ $ionicLoading.hide();
+ });
+ moveToGallery(ntp, eid + "-video");
+ NVRDataModel.setAwake(false);
+ // Success!
+ }, function(err)
+ {
+ NVRDataModel.setAwake(false);
+ NVRDataModel.log("Error=" + JSON.stringify(err));
+
+ $timeout(function()
+ {
+ $ionicLoading.show(
+ {
+
+ template: $translate.instant('kError'),
+ noBackdrop: true,
+ duration: 3000
+ });
+ });
+ // Error
+ }, function(progress)
+ {
+ var p = Math.round((progress.loaded / progress.total) * 100);
+
+ $ionicLoading.show(
+ {
+
+ template: $translate.instant('kPleaseWait') + "...(" + p + "%)",
+ noBackdrop: true
+ });
+
+ });
+
+ function moveToGallery(path, fname)
+ {
+
+ NVRDataModel.debug("moveToGallery called with " + path);
+ LibraryHelper.saveVideoToLibrary(onSuccess, onError, path, fname);
+
+ function onSuccess(results)
+ {
+ NVRDataModel.debug("Removing temp file");
+
+ if ($rootScope.platformOS == 'ios')
+ $cordovaFile.removeFile(cordova.file.documentsDirectory, "temp-video.mp4");
+ else
+ $cordovaFile.removeFile(cordova.file.dataDirectory, "temp-video.mp4");
+
+ }
+
+ function onError(error)
+ {
+ console.log("Error: " + error);
+
+ }
+ }
+
+ };
+
+ $scope.mp4warning = function()
+ {
+ $ionicPopup.alert(
+ {
+ title: $translate.instant('kNote'),
+ template: "{{'kVideoMp4Warning' | translate }}",
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ };
+
+ $scope.showImage = function(p, r, f, fid, e, imode, id, parray, ndx)
+ {
+ var img;
+
+ //console.log ("HERE");
+ $scope.kFrame = $translate.instant('kFrame');
+ $scope.kEvent = $translate.instant('kEvent');
+ $scope.ndx = ndx;
+ $scope.parray = parray;
+ $scope.imode = imode;
+
+ // note ndx may be incorrect if we are looking
+ // at unique frames;
+
+ // NVRDataModel.debug("Hello");
+ if ($scope.typeOfFrames == $translate.instant('kShowTimeDiffFrames'))
+ {
+
+ var ic;
+
+ for (ic = 0; ic < $scope.parray.length; ic++)
+ {
+ if ($scope.parray[ic].frameid == fid)
+ break;
+ }
+
+ NVRDataModel.debug("Readjusting selected frame ID from:" + $scope.ndx + " to actual frame ID of:" + ic);
+ $scope.ndx = ic;
+ }
+ else
+ {
+ NVRDataModel.debug("No index adjustment necessary as we are using all frames");
+ }
+
+ // console.log ("Image Mode " + imode);
+ // console.log ("parray : " + JSON.stringify(parray));
+ // console.log ("index: " + ndx);
+ if ($scope.imode == 'path')
+ {
+ if ($scope.outlineMotion)
+ $scope.imgsrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].aname;
+ else
+ $scope.imgsrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].fname;
+ $scope.fallbackImgSrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].fname;
+ }
+
+ else
+ {
+ $scope.imgsrc = p + "/index.php?view=image&fid=" + $scope.parray[$scope.ndx].id+$scope.outlineMotionParam;
+ $scope.fallbackImgSrc = p + "/index.php?view=image&fid=" + $scope.parray[$scope.ndx].id;
+
+ }
+
+ //$rootScope.zmPopup = $ionicPopup.alert({title: kFrame+':'+fid+'/'+kEvent+':'+e,template:img, cssClass:'popup80'});
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ template: '<center>' + $translate.instant('kFrame') + ':{{parray[ndx].frameid}}@{{prettifyTimeSec(parray[ndx].time)}}</center><br/><img ng-src="{{imgsrc}}" fallback-src="{{fallbackImgSrc}}" width="100%" />',
+ title: $translate.instant('kImages') + " (" + $translate.instant($scope.typeOfFrames) + ")",
+ subTitle: 'use left and right arrows to change',
+ scope: $scope,
+ cssClass: 'popup95',
+ buttons: [
+
+ {
+ text: '',
+ type: 'button-assertive button-small ion-camera',
+ onTap: function(e)
+ {
+ e.preventDefault();
+ saveNow($scope.imgsrc, r, parray[$scope.ndx].fname);
+
+ }
+ },
+
+ {
+ // left 1
+ text: '',
+ type: 'button-small button-energized ion-chevron-left',
+ onTap: function(e)
+ {
+ // look for next frame that matches the type of frame
+ // we are showing (all or diff timestamps);
+
+ // console.log ("TYPE OF FRAMES: " + $scope.typeOfFrames);
+ var nndx = null;
+ var alltype = $translate.instant('kShowAllFrames');
+ for (var i = $scope.ndx - 1; i >= 0; i--)
+ {
+ if ($scope.parray[i].type == $scope.typeOfFrames || $scope.typeOfFrames == alltype)
+ {
+ nndx = i;
+ break;
+ }
+ }
+ if (nndx == null) nndx = $scope.ndx;
+ $scope.ndx = nndx;
+
+ if ($scope.imode == 'path')
+ {
+ if ($scope.outlineMotion)
+ $scope.imgsrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].aname;
+ else
+ $scope.imgsrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].fname;
+ $scope.fallbackImgSrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].fname;
+ }
+
+ else
+ {
+ $scope.imgsrc = p + "/index.php?view=image&fid=" + $scope.parray[$scope.ndx].id+$scope.outlineMotionParam;
+ $scope.fallbackImgSrc = p + "/index.php?view=image&fid=" + $scope.parray[$scope.ndx].id;
+
+ }
+
+
+ e.preventDefault();
+
+ }
+ },
+ {
+ // right 1
+ text: '',
+ type: 'button-small button-energized ion-chevron-right',
+ onTap: function(e)
+ {
+
+ // look for next frame that matches the type of frame
+ // we are showing (all or diff timestamps);
+
+ // console.log ("TYPE OF FRAMES: " + $scope.typeOfFrames);
+ var nndx = null;
+ var alltype = $translate.instant('kShowAllFrames');
+ for (var i = $scope.ndx + 1; i < $scope.parray.length; i++)
+ {
+ //console.log ("Comparing: " +$scope.parray[i].type +" to " + $scope.typeOfFrames);
+ if ($scope.parray[i].type == $scope.typeOfFrames || $scope.typeOfFrames == alltype)
+ {
+ nndx = i;
+ break;
+ }
+ }
+ if (nndx == null) nndx = $scope.ndx;
+ $scope.ndx = nndx;
+
+ if ($scope.imode == 'path')
+ {
+ if ($scope.outlineMotion)
+ $scope.imgsrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].aname;
+ else
+ $scope.imgsrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].fname;
+ $scope.fallbackImgSrc = p + "/index.php?view=image&path=" + r + $scope.parray[$scope.ndx].fname;
+ }
+
+ else
+ {
+ $scope.imgsrc = p + "/index.php?view=image&fid=" + $scope.parray[$scope.ndx].id+$scope.outlineMotionParam;
+ $scope.fallbackImgSrc = p + "/index.php?view=image&fid=" + $scope.parray[$scope.ndx].id;
+
+ }
+
+
+ e.preventDefault();
+
+ }
+ },
+
+ {
+ text: '',
+ type: 'button-positive button-small ion-checkmark-round',
+ onTap: function(e) {
+
+ }
+ }
+ ]
+ });
+
+ };
+
+
+
+ $scope.toggleMotionOutline = function()
+ {
+ $scope.outlineMotion = !$scope.outlineMotion;
+ if ($scope.outlineMotion)
+ $scope.outlineMotionParam = "&show=analyse";
+ else
+ $scope.outlineMotionParam = "";
+ };
+
+ $scope.toggleTypeOfAlarms = function()
+ {
+ // "kShowAllFrames" : "all",
+ // "kShowTimeDiffFrames" : "different timestamps"
+
+ if ($scope.typeOfFrames == $translate.instant('kShowAllFrames'))
+ {
+ $scope.typeOfFrames = $translate.instant('kShowTimeDiffFrames');
+ }
+ else
+ {
+ $scope.typeOfFrames = $translate.instant('kShowAllFrames');
+ }
+ };
+
+ // not explictly handling error --> I have a default "No events found" message
+ // displayed in the template if events list is null
+
+ //--------------------------------------------------------------------------
+ // This is what the pullup bar calls depending on what range is specified
+ //--------------------------------------------------------------------------
+ $scope.showEvents = function(val, unit, monitorId)
+ {
+ NVRDataModel.debug("ShowEvents called with val:" + val + " unit:" + unit + " for Monitor:" + monitorId);
+
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+
+ // we have to convert from and to, to server time
+ var mToDate = moment().tz(NVRDataModel.getTimeZoneNow());
+ var mFromDate = moment().subtract(parseInt(val), unit).tz(NVRDataModel.getTimeZoneNow());
+
+ // console.log("Moment Dates:" + mFromDate.format() + " TO " + mToDate.format());
+
+ $rootScope.fromTime = mFromDate.toDate();
+ $rootScope.toTime = mToDate.toDate();
+ $rootScope.fromDate = $rootScope.fromTime;
+ $rootScope.toDate = $rootScope.toTime;
+
+ NVRDataModel.debug("From: " + $rootScope.fromTime);
+ NVRDataModel.debug("To: " + $rootScope.toTime);
+
+ //$rootScope.fromDate = fromDate.toDate();
+ //$rootScope.toDate = toDate.toDate();
+ $rootScope.isEventFilterOn = true;
+ $rootScope.fromString = mFromDate
+ .format("YYYY-MM-DD") + " " + mFromDate.format("HH:mm:ss");
+
+ $rootScope.toString = mToDate
+ .format("YYYY-MM-DD") + " " + mToDate
+ .format("HH:mm:ss");
+
+ // console.log("**************From String: " + $rootScope.fromString);
+ // console.log("**************To String: " + $rootScope.toString);
+
+ // reloading - may solve https://github.com/pliablepixels/zmNinja/issues/36
+ // if you are in the same mid event page $state.go won't work
+ $state.go("events",
+ {
+ "id": monitorId,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ // credit:http://stackoverflow.com/a/20151856/1361529
+ function base64toBlob(base64Data, contentType)
+ {
+ contentType = contentType || '';
+ var sliceSize = 1024;
+ var byteCharacters = atob(base64Data);
+ var bytesLength = byteCharacters.length;
+ var slicesCount = Math.ceil(bytesLength / sliceSize);
+ var byteArrays = new Array(slicesCount);
+
+ for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex)
+ {
+ var begin = sliceIndex * sliceSize;
+ var end = Math.min(begin + sliceSize, bytesLength);
+
+ var bytes = new Array(end - begin);
+ for (var offset = begin, i = 0; offset < end; ++i, ++offset)
+ {
+ bytes[i] = byteCharacters[offset].charCodeAt(0);
+ }
+ byteArrays[sliceIndex] = new Uint8Array(bytes);
+ }
+ return new Blob(byteArrays,
+ {
+ type: contentType
+ });
+ }
+
+ //----------------------------------------------------------
+ // create an array of images
+ // too keep memory manageable, we are only going to pick up alarmed frames
+ // and that too, max 1ps
+ // --------------------------------------------------------------
+ function prepareImages(e)
+ {
+ var d = $q.defer();
+ var imglist = [];
+ var myurl = loginData.apiurl + '/events/' + e.Event.Id + ".json";
+ $http.get(myurl)
+ .then(function(succ)
+ {
+ var data = succ.data;
+ var fps = 0;
+ var lastTime = "";
+
+ for (var i = 0; i < data.event.Frame.length; i++)
+ {
+ if (data.event.Frame[i].Type == "Alarm")
+ //if (1)
+ {
+ var fname;
+ //console.log ("PATH="+e.Event.imageMode);
+ if (e.Event.imageMode == 'path')
+ //if (1)
+ {
+ var rfp = padToN(data.event.Frame[i].FrameId, eventImageDigits) + "-capture.jpg";
+ fname = e.Event.baseURL + "/index.php?view=image&width=" + zm.maxGifWidth + "&path=" + e.Event.relativePath + rfp;
+ }
+ else
+ {
+ fname = e.Event.baseURL + "/index.php?view=image&width=" + zm.maxGifWidth + "&fid=" + data.event.Frame[i].Id;
+ }
+
+ if (data.event.Frame[i].TimeStamp != lastTime /*|| fps < 2*/ )
+
+ {
+ imglist.push(fname);
+ //fps = data.event.Frame[i].TimeStamp != lastTime ? 0 : fps+1;
+ lastTime = data.event.Frame[i].TimeStamp;
+ }
+
+ }
+
+ }
+
+ // next up make sure we are not processing more than 100 images
+
+ while (imglist.length > zm.maxGifCount2)
+ {
+ NVRDataModel.debug("Too many images: " + imglist.length + ", deleting alternate frames to keep it <=" + zm.maxGifCount2);
+
+ for (var l = 0; l < imglist.length; l++)
+ {
+ imglist.splice(l + 1, 2);
+ if (imglist.length <= zm.maxGifCount2) break;
+ }
+
+ }
+ NVRDataModel.debug("final image list length is:" + imglist.length);
+
+ d.resolve(imglist);
+ return d.promise;
+ },
+ function(err)
+ {
+ d.reject(err);
+ return d.promise;
+ });
+ return d.promise;
+ }
+
+ // force image to be of zm.maxGifWidth. TBD: rotated foo
+ function adjustAspect(e)
+ {
+
+ var w = zm.maxGifWidth;
+ var h = Math.round(e.Event.Height / e.Event.Width * zm.maxGifWidth);
+ return {
+ w: w,
+ h: h
+ };
+
+ }
+
+ // for devices - handle permission before you download
+ $scope.permissionsDownload = function(e)
+ {
+ if ($rootScope.platformOS == 'desktop')
+ {
+ gifAlert(e);
+ }
+ else
+ {
+
+ console.log("in perms");
+ cordova.plugins.photoLibrary.getLibrary(
+ function(library)
+ {
+ gifAlert(e);
+ },
+ function(err)
+ {
+ if (err.startsWith('Permission'))
+ {
+ // call requestAuthorization, and retry
+ cordova.plugins.photoLibrary.requestAuthorization(
+ function()
+ {
+ // User gave us permission to his library, retry reading it!
+ gifAlert(e);
+ },
+ function(err)
+ {
+ NVRDataModel.log("ERROR with saving permissions " + err);
+ // User denied the access
+ }, // if options not provided, defaults to {read: true}.
+ {
+ read: true,
+ write: true
+ }
+ );
+ }
+ // Handle error - it's not permission-related
+ }
+ );
+
+ }
+ };
+
+ // make sure the user knows the GIF is not full fps/all frames
+ function gifAlert(e)
+ {
+ if(navigator.userAgent.toLowerCase().indexOf('crosswalk') == -1) {
+ $ionicPopup.confirm(
+ {
+ title: $translate.instant('kNote'),
+ template: "{{'kGifWarning' | translate }}",
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ }).then(function(res)
+ {
+ if (res)
+ {
+ downloadAsGif2(e);
+ }
+ else
+ NVRDataModel.debug ("User cancelled GIF");
+
+ });
+ }
+ else
+ {
+ $ionicPopup.alert({
+ title:$translate.instant ('kNote'),
+ template:"{{'kGifNoCrosswalk' | translate}}"
+ });
+ }
+
+
+ }
+
+ // convert to base64 - devices need this to save to gallery
+ function blobToBase64(blob)
+ {
+ NVRDataModel.debug("converting blob to base64...");
+ var d = $q.defer();
+ var reader = new window.FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = function()
+ {
+ var base64data = reader.result;
+ //console.log(base64data );
+ d.resolve(base64data);
+ return d.promise;
+
+ };
+ return d.promise;
+ }
+
+ // part of neuquant conversion
+ function componentizedPaletteToArray(paletteRGB)
+ {
+ var paletteArray = [],
+ i, r, g, b;
+ for (i = 0; i < paletteRGB.length; i += 3)
+ {
+ r = paletteRGB[i];
+ g = paletteRGB[i + 1];
+ b = paletteRGB[i + 2];
+ paletteArray.push(r << 16 | g << 8 | b);
+ }
+ return paletteArray;
+ }
+
+ // part of neuquant conversion
+ function dataToRGB(data, width, height)
+ {
+ var i = 0,
+ length = width * height * 4,
+ rgb = [];
+ while (i < length)
+ {
+ rgb.push(data[i++]);
+ rgb.push(data[i++]);
+ rgb.push(data[i++]);
+ i++;
+ }
+ return rgb;
+ }
+
+ // credit Jimmy Warting
+ // https://github.com/jimmywarting/StreamSaver.js/issues/38
+ // he stream-ized and cleaned up the gif creation process
+ // using GifWriter.js
+ function createGif(files, w, h)
+ {
+
+ var cv = document.getElementById("canvas");
+ var ctx = cv.getContext("2d");
+ var pixels = new Uint8Array(w * h);
+ var totalImages = files.length;
+ var processedImages = 0;
+
+ cv.width = w;
+ cv.height = h;
+
+ var rs = new ReadableStream(
+ {
+ // Each time pull gets called you should get the pixel data and
+ // enqueue it as if it would be good old gif.addFrame()
+ pull: function pull(controller)
+ {
+ var frame = files.shift();
+ if (!frame) {controller.close(); return;}
+
+ return $http(
+ {
+ url: frame,
+ responseType: "blob"
+ })
+ .then(function(res)
+ {
+
+ return res.data.image();
+ })
+ .then(function(img)
+ {
+ processedImages++;
+
+ var p = Math.round(processedImages / totalImages * 100);
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...(" + p + "%)",
+ noBackdrop: true
+ });
+
+ console.log("URL=" + frame);
+ URL.revokeObjectURL(img.src);
+ ctx.drawImage(img, 0, 0);
+
+ var data = ctx.getImageData(0, 0, w, h).data;
+ var rgbComponents = dataToRGB(data, w, h);
+ var nq = new NeuQuant(rgbComponents, rgbComponents.length, 15);
+ var paletteRGB = nq.process();
+ var paletteArray = new Uint32Array(componentizedPaletteToArray(paletteRGB));
+ var numberPixels = w * h;
+ var k = 0,
+ i, r, g, b;
+
+ for (i = 0; i < numberPixels; i++)
+ {
+ r = rgbComponents[k++];
+ g = rgbComponents[k++];
+ b = rgbComponents[k++];
+ pixels[i] = nq.map(r, g, b);
+ }
+
+ controller.enqueue([0, 0, w, h, pixels,
+ {
+ palette: paletteArray,
+ delay: 100, // 1 second
+ }]);
+ });
+ }
+ });
+
+ return new GifWriter(rs, w, h,
+ {
+ loop: null
+ });
+ }
+
+
+
+ function downloadAsGif2(e)
+ {
+ $rootScope.isDownloading = true;
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: 20000
+ });
+ NVRDataModel.setAwake(true);
+
+ prepareImages(e)
+ .then(function(files)
+ {
+ return $http(
+ {
+ url: files[0],
+ responseType: "blob"
+ })
+ .then(function(res)
+ {
+ return res.data.image();
+ })
+ .then(function(img)
+ {
+ URL.revokeObjectURL(img.src); // Revoke object URL to free memory
+ var stream = createGif(files, img.width, img.height);
+ //var fileStream = streamSaver.createWriteStream('image.gif');
+
+ var chunks = [];
+ var reader = stream.getReader();
+
+ function pull()
+ {
+ return reader.read().then(function(result)
+ {
+ chunks.push(result.value);
+ return result.done ? chunks : pull();
+ });
+ }
+
+ pull().then(function(chunks)
+ {
+ var blob = new Blob(chunks,
+ {
+ type: "image/gif"
+
+ });
+
+ //alert ("WE ARE DONE!");
+ if ($rootScope.platformOS == 'desktop')
+ {
+ saveAs(blob, e.Event.Id + "-video.gif");
+ $ionicLoading.hide();
+ }
+ else
+ {
+ // write blob to file
+ var tp;
+ if ($rootScope.platformOS == 'ios')
+ tp = cordova.file.documentsDirectory;
+ else
+ tp = cordova.file.dataDirectory;
+ var th = true, opt = {};
+
+ $ionicLoading.show(
+ {
+
+ template:"writing to file...",
+ noBackdrop: true,
+ });
+
+ //var bloburl = URL.createObjectURL(blob);
+ //NVRDataModel.debug ("blob-url is:"+bloburl);
+
+ writeFile2(tp,"temp-file.gif",blob,false)
+ .then (function (succ) {
+ NVRDataModel.debug ("write to file successful");
+ console.log( "write file successful");
+ $ionicLoading.hide();
+
+ var ntp = tp;
+ //ntp = tp.indexOf('file://') === 0 ? tp.slice(7) : tp;
+
+ ntp = ntp+"temp-file.gif";
+ console.log ("ntp="+ntp);
+
+ moveImageToGallery(ntp);
+ $rootScope.isDownloading = false;
+
+ }, function (err) {
+ $rootScope.isDownloading = false;
+ $ionicLoading.hide();
+ NVRDataModel.debug ("error writing to file "+JSON.stringify(err));
+
+
+ });
+ }
+
+ });
+ });
+
+ },
+ function(err)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.setAwake(false);
+ NVRDataModel.log("Error getting frames");
+ $rootScope.isDownloading = false;
+ }
+
+ );
+
+ }
+
+ // NOT USED - WILL REMOVE AFTER TESTING OTHER METHOD MORE
+ function downloadAsGif(e)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: 20000
+ });
+
+ prepareImages(e)
+ .then(function(imgs)
+ {
+
+ console.log("TOTAL IMAGES TO GIF=" + imgs.length);
+ //console.log(JSON.stringify(imgs));
+
+ var ad = adjustAspect(e);
+ //console.log("SAVING W=" + ad.w + " H=" + ad.h);
+ NVRDataModel.setAwake(true);
+ gifshot.createGIF(
+ {
+
+ 'gifWidth': ad.w,
+ 'gifHeight': ad.h,
+ 'images': imgs,
+ 'interval': 1,
+ //'loop':null,
+ 'sampleInterval': 20,
+ //'frameDur':5, // 1/2 a sec
+ 'text': 'zmNinja',
+ 'crossOrigin': 'use-credentials',
+ 'progressCallback': function(cp)
+ {
+ var p = Math.round(cp * 100);
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...(" + p + "%)",
+ noBackdrop: true
+ });
+ }
+ }, function(obj)
+ {
+ NVRDataModel.setAwake(false);
+ if (!obj.error)
+ {
+ //console.log(obj.image);
+
+ var blob;
+
+ if ($rootScope.platformOS == 'desktop')
+ {
+
+ obj.image = obj.image.replace(/data:image\/gif;base64,/, '');
+ blob = base64toBlob(obj.image, "image/gif");
+ var f = NVRDataModel.getMonitorName(e.Event.MonitorId);
+ f = f + "-" + e.Event.Id + ".gif";
+ saveAs(blob, f);
+ $ionicLoading.hide();
+ }
+
+ else
+ {
+ NVRDataModel.debug("Saving blob to gallery...");
+ var album = "zmNinja";
+ cordova.plugins.photoLibrary.saveImage(obj.image, album,
+ function()
+ {
+ $ionicLoading.hide();
+ NVRDataModel.debug("Event saved");
+ },
+ function(err)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.debug("Saving ERROR=" + err);
+ });
+
+ }
+
+ }
+ else
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("Error creating GIF");
+ }
+ });
+ },
+ function(err)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("Error getting frames");
+ }
+
+ );
+ }
+
+ $scope.archiveUnarchiveEvent = function (ndx,eid)
+ {
+ //https://server/zm/api/events/11902.json -XPUT -d"Event[Archived]=1"
+ //
+ $ionicListDelegate.closeOptionButtons();
+
+ NVRDataModel.debug ("Archiving request for EID="+eid);
+ var loginData = NVRDataModel.getLogin();
+ var apiArchive = loginData.apiurl + "/events/" + eid + ".json";
+ var setArchiveBit = ($scope.events[ndx].Event.Archived == '0') ? "1":"0";
+
+ NVRDataModel.debug ("Calling archive with:"+apiArchive+ " and Archive="+setArchiveBit);
+ //put(url, data, [config]);
+
+ // $http.put(apiArchive,"Event[Archived]="+setArchiveBit)
+ //
+ $ionicLoading.show(
+ {
+ template: "{{'kPleaseWait' | translate}}...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+ $http({
+
+ method: 'POST',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ // console.log("****RETURNING " + foo);
+ NVRDataModel.debug("MonitorCtrl: parmeters constructed: " + foo);
+ return foo;
+ },
+ url: apiArchive,
+ data: {
+ "Event[Archived]":setArchiveBit
+
+ }
+ })
+ .then (function (success) {
+
+ NVRDataModel.log ("archiving response: "+ JSON.stringify(success));
+ if (success.data.message == 'Error')
+ {
+ $ionicLoading.show(
+ {
+ template: "{{'kError' | translate}}...",
+ noBackdrop: true,
+ duration: 1500
+ });
+
+ }
+ else
+ {
+
+
+ $ionicLoading.show(
+ {
+ template: "{{'kSuccess' | translate}}...",
+ noBackdrop: true,
+ duration: 1000
+ });
+ if ($scope.events[ndx].Event.Archived == '0')
+ $scope.events[ndx].Event.Archived = '1';
+ else
+ $scope.events[ndx].Event.Archived = '0';
+ }
+
+
+
+ },
+ function (error) {
+ NVRDataModel.log ("Error archiving: "+ JSON.stringify(error));
+ } );
+
+
+
+ };
+
+ //--------------------------------------------------------------------------
+ // Takes care of deleting individual events
+ //--------------------------------------------------------------------------
+
+ $scope.deleteEvent = function(id, itemid)
+ {
+ //$scope.eventList.showDelete = false;
+ //curl -XDELETE http://server/zm/api/events/1.json
+ var loginData = NVRDataModel.getLogin();
+ var apiDelete = loginData.apiurl + "/events/" + id + ".json";
+ NVRDataModel.debug("DeleteEvent: ID=" + id + " item=" + itemid);
+ NVRDataModel.log("Delete event " + apiDelete);
+
+ $ionicLoading.show(
+ {
+ template: "{{'kDeletingEvent' | translate}}...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+
+
+ $http.delete(apiDelete)
+ .success(function(data)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.debug("delete output: " + JSON.stringify(data));
+
+ if (data.message == 'Error')
+ {
+ $ionicLoading.show(
+ {
+ template: "{{'kError' | translate}}...",
+ noBackdrop: true,
+ duration: 1500
+ });
+
+ }
+ else
+ {
+
+
+ $ionicLoading.show(
+ {
+ template: "{{'kSuccess' | translate}}...",
+ noBackdrop: true,
+ duration: 1000
+ });
+ $scope.events.splice(itemid, 1);
+
+ }
+
+ // NVRDataModel.displayBanner('info', [$translate.instant('kDeleteEventSuccess')], 2000, 2000);
+
+
+
+
+ //doRefresh();
+
+ })
+ .error(function(data)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.debug("delete error: " + JSON.stringify(data));
+ NVRDataModel.displayBanner('error', [$translate.instant('kDeleteEventError1'), $translate.instant('kDeleteEventError2')]);
+ });
+
+ };
+
+ //------------------------------------------------
+ // Tapping on the filter sign lets you reset it
+ //-------------------------------------------------
+
+ $scope.filterTapped = function()
+ {
+ //console.log("FILTER TAPPED");
+ var myFrom = moment($rootScope.fromString).format("MMM/DD/YYYY " + NVRDataModel.getTimeFormat()).toString();
+ var toString = moment($rootScope.toString).format("MMM/DD/YYYY " + NVRDataModel.getTimeFormat()).toString();
+
+ $rootScope.zmPopup = $ionicPopup.confirm(
+ {
+ title: $translate.instant('kFilterSettings'),
+ template: $translate.instant('kFilterEventsBetween1') + ':<br/> <b>' + myFrom + "</b> " + $translate.instant('kTo') + " <b>" + toString + '</b><br/>' + $translate.instant('kFilterEventsBetween2'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ $rootScope.zmPopup.then(function(res)
+ {
+ if (res)
+ {
+ NVRDataModel.log("Filter reset requested in popup");
+ $rootScope.isEventFilterOn = false;
+ $rootScope.fromDate = "";
+ $rootScope.fromTime = "";
+ $rootScope.toDate = "";
+ $rootScope.toTime = "";
+ $rootScope.fromString = "";
+ $rootScope.toString = "";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ });
+ return;
+ }
+ else
+ {
+ NVRDataModel.log("Filter reset cancelled in popup");
+ }
+ });
+
+ };
+
+ //--------------------------------------------------------------------------
+ // When the user pulls the pullup bar we call this to get the latest
+ // data for events ranges summaries using the consolveEvents facility of ZM
+ //--------------------------------------------------------------------------
+
+ $scope.footerExpand = function()
+ {
+ footerExpand();
+
+ };
+
+ function footerExpand()
+ {
+ //https://server/zm/api/events/consoleEvents/5%20minute.json
+ var ld = NVRDataModel.getLogin();
+
+ var af = "/AlarmFrames >=:" + (ld.enableAlarmCount ? ld.minAlarmCount : 0);
+
+ var apiurl = ld.apiurl + "/events/consoleEvents/1%20hour" + af + ".json";
+ NVRDataModel.debug("consoleEvents API:" + apiurl);
+
+ $http.get(apiurl)
+ .success(function(data)
+ {
+ NVRDataModel.debug(JSON.stringify(data));
+ $scope.hours = [];
+ var p = data.results;
+ for (var key in data.results)
+ {
+
+ if (p.hasOwnProperty(key))
+ {
+
+ var idfound = true;
+ //console.log ("PERSIST IS " + ld.persistMontageOrder);
+ if (ld.persistMontageOrder)
+ {
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == key && (NVRDataModel.isNotHidden(key) || showHiddenMonitors))
+ {
+ // console.log ("Authorizing "+$scope.monitors[ii].Monitor.Name);
+ idfound = true;
+ break;
+ }
+ }
+ }
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ if (idfound)
+ $scope.hours.push(
+ {
+ monitor: NVRDataModel.getMonitorName(key),
+ events: p[key],
+ mid: key
+ });
+
+ }
+ }
+ });
+
+ apiurl = ld.apiurl + "/events/consoleEvents/1%20day" + af + ".json";
+ NVRDataModel.debug("consoleEvents API:" + apiurl);
+ $http.get(apiurl)
+ .success(function(data)
+ {
+ NVRDataModel.debug(JSON.stringify(data));
+ $scope.days = [];
+ var p = data.results;
+ for (var key in data.results)
+ {
+ if (p.hasOwnProperty(key))
+ {
+ var idfound = true;
+ if (ld.persistMontageOrder)
+ {
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == key && (NVRDataModel.isNotHidden(key) || showHiddenMonitors))
+ {
+ idfound = true;
+ break;
+ }
+ }
+ }
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ if (idfound)
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ $scope.days.push(
+ {
+ monitor: NVRDataModel.getMonitorName(key),
+ events: p[key],
+ mid: key
+ });
+
+ }
+ }
+ });
+
+ apiurl = ld.apiurl + "/events/consoleEvents/1%20week" + af + ".json";
+ NVRDataModel.debug("consoleEvents API:" + apiurl);
+ $http.get(apiurl)
+ .success(function(data)
+ {
+ NVRDataModel.debug(JSON.stringify(data));
+ $scope.weeks = [];
+ var p = data.results;
+ for (var key in data.results)
+ {
+ if (p.hasOwnProperty(key))
+ {
+
+ var idfound = true;
+ if (ld.persistMontageOrder)
+ {
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == key && (NVRDataModel.isNotHidden(key) || showHiddenMonitors))
+ {
+ idfound = true;
+ break;
+ }
+ }
+ }
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ if (idfound)
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ $scope.weeks.push(
+ {
+ monitor: NVRDataModel.getMonitorName(key),
+ events: p[key],
+ mid: key
+ });
+
+ }
+ }
+ });
+
+ apiurl = ld.apiurl + "/events/consoleEvents/1%20month" + af + ".json";
+ NVRDataModel.debug("consoleEvents API:" + apiurl);
+ $http.get(apiurl)
+ .success(function(data)
+ {
+ NVRDataModel.debug(JSON.stringify(data));
+ $scope.months = [];
+ var p = data.results;
+ for (var key in data.results)
+ {
+ if (p.hasOwnProperty(key))
+ {
+
+ var idfound = true;
+ var ld = NVRDataModel.getLogin();
+ if (ld.persistMontageOrder)
+ {
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == key && (NVRDataModel.isNotHidden(key) || showHiddenMonitors))
+ {
+ idfound = true;
+ break;
+ }
+ }
+ }
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ if (idfound)
+ //console.log(NVRDataModel.getMonitorName(key) + " -> " + p[key]);
+ $scope.months.push(
+ {
+ monitor: NVRDataModel.getMonitorName(key),
+ events: p[key],
+ mid: key
+ });
+
+ }
+ }
+ });
+
+ }
+
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.scrollPosition = function()
+ {
+ var scrl = parseFloat($ionicScrollDelegate.$getByHandle("mainScroll").getScrollPosition().top);
+ var item = Math.round(scrl / eventsListDetailsHeight);
+ if ($scope.events == undefined || !$scope.events.length || $scope.events[item] == undefined)
+ {
+ return "";
+ }
+ else
+ {
+ //return prettifyDate($scope.events[item].Event.StartTime);
+ return ($scope.events[item].Event.humanizeTime);
+ }
+ //return Math.random();
+ };
+
+ //-------------------------------------------------------------------------
+ // called when user switches to background
+ //-------------------------------------------------------------------------
+ function onPause()
+ {
+ NVRDataModel.debug("EventCtrl:onpause called");
+ if ($scope.popover) $scope.popover.remove();
+
+ }
+ //-------------------------------------------------------------------------
+ // Pads the filename with leading 0s, depending on ZM_IMAGE_DIGITS
+ //-------------------------------------------------------------------------
+ function padToN(number, digits)
+ {
+
+ var i;
+ var stringMax = "";
+ var stringLeading = "";
+ for (i = 1; i <= digits; i++)
+ {
+ stringMax = stringMax + "9";
+ if (i != digits) stringLeading = stringLeading + "0";
+ }
+ var numMax = parseInt(stringMax);
+
+ if (number <= numMax)
+ {
+ number = (stringLeading + number).slice(-digits);
+ }
+ //console.log ("PADTON: returning " + number);
+ return number;
+ }
+
+ //-------------------------------------------------------------------------
+ // FIXME: Are we using this?
+ //-------------------------------------------------------------------------
+ $scope.disableSlide = function()
+ {
+ NVRDataModel.debug("EventCtrl:DisableSlide called");
+ $ionicSlideBoxDelegate.$getByHandle("eventSlideBox").enableSlide(false);
+ };
+
+ $scope.checkSwipe = function (ndx)
+ {
+ if ($scope.events[ndx].Event.ShowScrub)
+ {
+ $ionicListDelegate.canSwipeItems(false);
+ NVRDataModel.debug ("disabling options swipe");
+ }
+ else
+ {
+ $ionicListDelegate.canSwipeItems(true);
+ NVRDataModel.debug ("enabling options swipe");
+ }
+
+ };
+
+ //-------------------------------------------------------------------------
+ // This function is called when a user enables or disables
+ // scrub view for an event.
+ //-------------------------------------------------------------------------
+
+ $scope.toggleGroupScrub = function(event, ndx, frames)
+ {
+ $scope.groupType = "scrub";
+ toggleGroup(event, ndx, frames, $scope.groupType);
+ };
+
+ $scope.toggleGroupAlarms = function(event, ndx, frames)
+ {
+ $scope.groupType = "alarms";
+ toggleGroup(event, ndx, frames, $scope.groupType);
+ };
+
+ function toggleGroup(event, ndx, frames, groupType)
+ {
+
+
+ // If we are here and there is a record of a previous scroll
+ // then we need to scroll back to hide that view
+ if (scrollbynumber)
+ {
+ $ionicScrollDelegate.$getByHandle("mainScroll").scrollBy(0, -1 * scrollbynumber, true);
+ scrollbynumber = 0;
+ }
+
+ if (oldEvent && event != oldEvent)
+ {
+
+ NVRDataModel.debug("EventCtrl:Old event scrub will hide now");
+ oldEvent.Event.ShowScrub = false;
+ oldEvent.Event.height = eventsListDetailsHeight;
+ oldEvent = "";
+ }
+
+ event.Event.ShowScrub = !event.Event.ShowScrub;
+
+ if (event.Event.ShowScrub == false)
+ {
+ $ionicListDelegate.canSwipeItems(true);
+ NVRDataModel.debug ("enabling options swipe due to toggle");
+ }
+
+ else
+ {
+ $ionicListDelegate.canSwipeItems(false);
+ $ionicListDelegate.closeOptionButtons();
+ NVRDataModel.debug ("disabling options swipe due to toggle");
+
+ }
+
+
+
+
+ //console.log ("SCRUBBING IS "+event.Event.ShowScrub);
+ // $ionicScrollDelegate.resize();
+
+ //console.log ("GROUP TYPE IS " + groupType);
+
+ if (event.Event.ShowScrub == true) // turn on display now
+ {
+
+
+ if (groupType == 'alarms')
+ {
+ // $ionicListDelegate.canSwipeItems(false);
+ //NVRDataModel.debug ("Disabling flag swipe as alarms are swipable");
+ $scope.alarm_images = [];
+ event.Event.height = (eventsListDetailsHeight + eventsListScrubHeight);
+ $ionicScrollDelegate.resize();
+ var myurl = loginData.apiurl + '/events/' + event.Event.Id + ".json";
+ NVRDataModel.log("API for event details" + myurl);
+ $http.get(myurl)
+ .success(function(data)
+ {
+ $scope.FrameArray = data.event.Frame;
+ // $scope.slider_options.scale=[];
+
+ //$scope.slider_options.scale = [];
+
+ var i;
+ var timestamp = null;
+ for (i = 0; i < data.event.Frame.length; i++)
+ {
+ if (data.event.Frame[i].Type == "Alarm")
+ {
+
+ //console.log ("**ONLY ALARM AT " + i + "of " + data.event.Frame.length);
+ var atype;
+ if (timestamp != data.event.Frame[i].TimeStamp)
+ {
+
+ atype = $translate.instant('kShowTimeDiffFrames');
+ }
+ else
+ {
+ atype = $translate.instant('kShowAllFrames');
+ }
+ $scope.alarm_images.push(
+ {
+ type: atype,
+ id: data.event.Frame[i].Id,
+ frameid: data.event.Frame[i].FrameId,
+ score: data.event.Frame[i].Score,
+ fname: padToN(data.event.Frame[i].FrameId, eventImageDigits) + "-capture.jpg",
+ aname:padToN(data.event.Frame[i].FrameId, eventImageDigits) + "-analyse.jpg",
+ time: data.event.Frame[i].TimeStamp
+ });
+ timestamp = data.event.Frame[i].TimeStamp;
+ }
+
+ }
+ oldEvent = event;
+
+ //console.log (JSON.stringify(data));
+ })
+ .error(function(err)
+ {
+ NVRDataModel.log("Error retrieving detailed frame API " + JSON.stringify(err));
+ NVRDataModel.displayBanner('error', ['could not retrieve frame details', 'please try again']);
+ });
+
+ } // end of groupType == alarms
+ else // groupType == scrub
+ {
+
+ NVRDataModel.debug("EventCtrl: Scrubbing will turn on now");
+ $scope.currentEvent = "";
+ $scope.event = event;
+ //$ionicScrollDelegate.freezeScroll(true);
+ $ionicSideMenuDelegate.canDragContent(false);
+ $scope.slider_options = {
+ from: 1,
+ to: event.Event.Frames,
+ realtime: true,
+ step: 1,
+ className: "mySliderClass",
+ callback: function(value, released)
+ {
+ //console.log("CALLBACK"+value+released);
+ $ionicScrollDelegate.freezeScroll(!released);
+ //NVRDataModel.debug("EventCtrl: freezeScroll called with " + !released);
+
+ },
+ //modelLabels:function(val) {return "";},
+ css:
+ {
+ background:
+ {
+ "background-color": "silver"
+ },
+ before:
+ {
+ "background-color": "purple"
+ },
+ default:
+ {
+ "background-color": "white"
+ }, // default value: 1px
+ after:
+ {
+ "background-color": "green"
+ }, // zone after default value
+ pointer:
+ {
+ "background-color": "red"
+ }, // circle pointer
+ range:
+ {
+ "background-color": "red"
+ } // use it if double value
+ },
+ scale: []
+
+ };
+
+ event.Event.height = (eventsListDetailsHeight + eventsListScrubHeight);
+ $ionicScrollDelegate.resize();
+ $scope.mycarousel.index = 0;
+ $scope.ionRange.index = 1;
+ //console.log("**Resetting range");
+ $scope.slides = [];
+ var i;
+
+ if (event.Event.imageMode == 'path')
+ {
+ NVRDataModel.debug("EventCtrl: found " + frames + " frames to scrub");
+
+ for (i = 1; i <= frames; i++)
+ {
+ var fname = padToN(i, eventImageDigits) + "-capture.jpg";
+
+ $scope.slides.push(
+ {
+ id: i,
+ img: fname
+ });
+
+ }
+ }
+ else // we need fids
+ {
+ var myurl_frames = loginData.apiurl + '/events/' + event.Event.Id + ".json";
+ NVRDataModel.log("API for event details" + myurl_frames);
+ $http.get(myurl_frames)
+ .success(function(data)
+ {
+ $scope.FrameArray = data.event.Frame;
+ // $scope.slider_options.scale=[];
+
+ //$scope.slider_options.scale = [];
+
+ var i;
+ for (i = 0; i < data.event.Frame.length; i++)
+ {
+
+ //console.log ("**ONLY ALARM AT " + i + "of " + data.event.Frame.length);
+ $scope.slides.push(
+ {
+ id: data.event.Frame[i].Id,
+ frameid: data.event.Frame[i].FrameId,
+
+ });
+
+ }
+
+ //console.log (JSON.stringify(data));
+ })
+ .error(function(err)
+ {
+ NVRDataModel.log("Error retrieving detailed frame API " + JSON.stringify(err));
+ NVRDataModel.displayBanner('error', [$translate.instant('kErrorFrameBanner'), $translate.instant('kErrorPleaseTryAgain')]);
+ });
+
+ }
+
+ // now get event details to show alarm frames
+ loginData = NVRDataModel.getLogin();
+
+ if (typeof event.Event.DefaultVideo === 'undefined')
+ event.Event.DefaultVideo = "";
+ // grab video details
+ event.Event.video = {};
+ var videoURL;
+
+ //if (event.Event.imageMode == 'path')
+ if (1)
+ videoURL = event.Event.baseURL + "/events/" + event.Event.relativePath + event.Event.DefaultVideo;
+ else
+ videoURL = event.Event.baseURL + "/index.php?view=view_video&eid=" + event.Event.Id;
+
+ console.log("************** VIDEO IS " + videoURL);
+ event.Event.video.config = {
+ autoPlay: true,
+ sources: [
+ {
+ src: $sce.trustAsResourceUrl(videoURL),
+ type: "video/mp4"
+ }
+
+ ],
+
+ theme: "lib/videogular-themes-default/videogular.css",
+
+ };
+
+ var myurl2 = loginData.apiurl + '/events/' + event.Event.Id + ".json";
+ NVRDataModel.log("API for event details" + myurl2);
+ $http.get(myurl2)
+ .success(function(data)
+ {
+ $scope.FrameArray = data.event.Frame;
+ // $scope.slider_options.scale=[];
+ $scope.slider_options.scale = [];
+
+ var i;
+ for (i = 0; i < data.event.Frame.length; i++)
+ {
+ if (data.event.Frame[i].Type == "Alarm")
+ {
+
+ //console.log ("**ALARM AT " + i + "of " + data.event.Frame.length);
+ $scope.slider_options.scale.push(
+ {
+ val: data.event.Frame[i].FrameId,
+ label: ' '
+ });
+ }
+ else
+ {
+ //$scope.slider_options.scale.push(' ');
+ }
+
+ }
+
+ //console.log (JSON.stringify(data));
+ })
+ .error(function(err)
+ {
+ NVRDataModel.log("Error retrieving detailed frame API " + JSON.stringify(err));
+ NVRDataModel.displayBanner('error', [$translate.instant('kErrorFrameBanner'), $translate.instant('kErrorPleaseTryAgain')]);
+ });
+
+ oldEvent = event;
+ $rootScope.rand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+ var elem = angular.element(document.getElementById("item-" + ndx));
+ var locobject = $ionicPosition.offset(elem);
+ //console.log(JSON.stringify(locobject));
+ var toplocation = parseInt(locobject.top);
+ var objheight = parseInt(locobject.height);
+ // console.log("top location is " + toplocation);
+ var distdiff = parseInt($rootScope.devHeight) - toplocation - objheight;
+ // console.log("*****Space at bottom is " + distdiff);
+
+ if (distdiff < eventsListScrubHeight) // size of the scroller with bars
+ {
+ scrollbynumber = eventsListScrubHeight - distdiff;
+ $ionicScrollDelegate.$getByHandle("mainScroll").scrollBy(0, scrollbynumber, true);
+
+ // we need to scroll up to make space
+ }
+
+ } // end of groupType == scrub
+ } // end of ShowScrub == true
+ else
+ {
+ // $ionicScrollDelegate.freezeScroll(false);
+ //
+ // $ionicListDelegate.canSwipeItems(true);
+ // NVRDataModel.debug ("enabling options swipe");
+
+ $ionicSideMenuDelegate.canDragContent(true);
+ event.Event.height = eventsListDetailsHeight;
+ $ionicScrollDelegate.resize();
+
+ if (scrollbynumber)
+ {
+ $ionicScrollDelegate.$getByHandle("mainScroll").scrollBy(0, -1 * scrollbynumber, true);
+ scrollbynumber = 0;
+ }
+ // we are turning off, so scroll by back
+ }
+
+ }
+
+ $scope.closeIfOpen = function(event)
+ {
+ if (event != undefined)
+ {
+ if (event.Event.ShowScrub)
+ toggleGroup(event);
+
+ }
+ };
+
+ $scope.isGroupShown = function(event)
+ {
+ // console.log ("IS SHOW INDEX is " + ndx);
+ //console.log ("SHOW GROUP IS " + showGroup);
+
+ return (event == undefined) ? false : event.Event.ShowScrub;
+
+ };
+
+ //---------------------------------------------------
+ // reload view
+ //---------------------------------------------------
+ $scope.reloadView = function()
+ {
+ // All we really need to do here is change the random token
+ // in the image src and it will refresh. No need to reload the view
+ // and if you did reload the view, it would go back to events list
+ // which is the view - and when you are in the modal it will go away
+ //console.log("*** Refreshing Modal view ***");
+ //$state.go($state.current, {}, {reload: true});
+ $rootScope.rand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kRefreshedView'),
+ noBackdrop: true,
+ duration: 3000
+ });
+
+ };
+
+ //---------------------------------------------------
+ // when you tap a list entry - to break search loop
+ //---------------------------------------------------
+ $scope.tapped = function()
+ {
+ // console.log("*** TAPPED ****");
+ // if he tapped, the we are not infinite loading on ion-infinite
+ if (enableLoadMore == false)
+ {
+ moreEvents = true;
+ enableLoadMore = true;
+ // console.log("REMOVING ARTIFICAL LOAD MORE BLOCK");
+ }
+ };
+
+ $scope.$on('$ionicView.loaded', function()
+ {
+ // console.log("**VIEW ** Events Ctrl Loaded");
+ });
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ // console.log("**VIEW ** Events Ctrl Entered");
+ NVRDataModel.setAwake(false);
+
+ EventServer.sendMessage('push',
+ {
+ type: 'badge',
+ badge: 0,
+ });
+
+ $ionicPopover.fromTemplateUrl('templates/events-popover.html',
+ {
+ scope: $scope,
+ }).then(function(popover)
+ {
+ $scope.popover = popover;
+ });
+
+ //reset badge count
+ if (window.cordova && window.cordova.plugins.notification)
+ {
+ $cordovaBadge.set(0).then(function()
+ {
+ // You have permission, badge set.
+ }, function(err)
+ {
+ NVRDataModel.debug("app does not have badge permissions. Please check your phone notification settings");
+ // You do not have permission.
+ });
+
+ $cordovaLocalNotification.clearAll();
+ }
+
+ });
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ //console.log("**VIEW ** Events Ctrl Left");
+ });
+
+ $scope.$on('$ionicView.unloaded', function()
+ {
+ //console.log("**VIEW ** Events Ctrl Unloaded");
+ //console.log("*** MODAL ** Destroying modal too");
+ if ($scope.modal !== undefined)
+ {
+ $scope.modal.remove();
+ }
+
+ });
+
+ //---------------------------------------------------
+ // used to hide loading image toast
+ //---------------------------------------------------
+ $scope.finishedLoadingImage = function(ndx)
+ {
+ // console.log("*** Events image FINISHED loading index: "+ndx+"***");
+ $ionicLoading.hide();
+ };
+
+ //---------------------------------------------------
+ //
+ //---------------------------------------------------
+ $scope.clearSearch = function()
+ {
+ $scope.search.text = "";
+ };
+
+ //---------------------------------------------------
+ // Called when user toggles search
+ //---------------------------------------------------
+ $scope.searchClicked = function()
+ {
+ $scope.showSearch = !$scope.showSearch;
+ // this helps greatly in repeat scroll gets
+ if ($scope.showSearch == false)
+ $scope.search.text = "";
+
+ //console.log("**** Setting search view to " + $scope.showSearch + " ****");
+ if (enableLoadMore == false && $scope.showSearch == false)
+ {
+ moreEvents = true;
+ enableLoadMore = true;
+ //console.log("REMOVING ARTIFICAL LOAD MORE BLOCK");
+ }
+ };
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeRelativePath(event)
+ {
+ var relativePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+ relativePath = event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return relativePath;
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeBasePath(event)
+ {
+ var basePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+
+ basePath = event.Event.baseURL + "/events/" +
+ event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return basePath;
+ }
+
+ $scope.modalGraph = function()
+ {
+ $ionicModal.fromTemplateUrl('templates/events-modalgraph.html',
+ {
+ scope: $scope, // give ModalCtrl access to this scope
+ animation: 'slide-in-up',
+ id: 'modalgraph',
+
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $scope.modal.show();
+
+ });
+ };
+
+ $scope.analyzeEvent = function(ev)
+ {
+ $scope.event = ev;
+ $ionicModal.fromTemplateUrl('templates/timeline-modal.html',
+ {
+ scope: $scope, // give ModalCtrl access to this scope
+ animation: 'slide-in-up',
+ id: 'analyze',
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $scope.modal.show();
+
+ });
+ };
+
+ $scope.$on('modal.removed', function(e, m)
+ {
+
+ if (m.id != 'footage')
+ return;
+ NVRDataModel.debug("Rebinding watchers of eventCtrl");
+ setupWatchers();
+
+ //console.log ("************** FOOTAGE CLOSED");
+
+ });
+
+ //--------------------------------------------------------
+ //This is called when we first tap on an event to see
+ // the feed. It's important to instantiate ionicModal here
+ // as otherwise you'd instantiate it when the view loads
+ // and our "Please wait loading" technique I explained
+ //earlier won't work
+ //--------------------------------------------------------
+
+ $scope.openModal = function(event)
+ {
+
+ NVRDataModel.debug("unbinding eventCtrl watchers as modal has its own");
+ ionRangeWatcher();
+ mycarouselWatcher();
+ //NVRDataModel.debug("EventCtrl: Open Modal with Base path " + relativepath);
+
+ $scope.event = event;
+
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ $scope.currentEvent = event;
+ $scope.followSameMonitor = ($stateParams.id == "0") ? "0" : "1";
+
+ $ionicModal.fromTemplateUrl('templates/events-modal.html',
+ {
+ scope: $scope,
+ animation: 'slide-in-up',
+ id: 'footage',
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: 10000
+ });
+
+ $scope.modal.show();
+
+ var ld = NVRDataModel.getLogin();
+
+ });
+
+ };
+
+ //--------------------------------------------------------
+ //We need to destroy because we are instantiating
+ // it on open
+ //--------------------------------------------------------
+ $scope.closeModal = function()
+ {
+ NVRDataModel.debug(">>>EventCtrl:Close & Destroy Modal");
+ NVRDataModel.setAwake(false);
+ if ($scope.modal !== undefined)
+ {
+ $scope.modal.remove();
+ }
+
+ };
+
+ //--------------------------------------------------------
+ //Cleanup the modal when we're done with it
+ // I Don't think it ever comes here
+ //--------------------------------------------------------
+ $scope.$on('$destroy', function()
+ {
+ //console.log("Destroy Modal");
+ if ($scope.modal !== undefined)
+ {
+ $scope.modal.remove();
+ }
+ if ($scope.popover !== undefined)
+ $scope.popover.remove();
+ });
+
+ //--------------------------------------------------------
+ // used by infinite scrolling to see if we can get more
+ //--------------------------------------------------------
+
+ $scope.moreDataCanBeLoaded = function()
+ {
+ return moreEvents;
+ };
+
+ //--------------------------------------------------------
+ // stop searching for more data
+ //--------------------------------------------------------
+ $scope.cancelSearch = function()
+ {
+ $ionicLoading.hide(); //Or whatever action you want to preform
+ enableLoadMore = false;
+ //console.log("**** CANCELLED ****");
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSearchCancelled'),
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: 2000,
+ maxWidth: 200,
+ showDelay: 0
+ });
+
+ };
+
+ //--------------------------------------------------------
+ // loads next page of events
+ //--------------------------------------------------------
+
+ function loadMore()
+ {
+ // the events API does not return an error for anything
+ // except greater page limits than reported
+
+ // console.log("***** LOADING MORE INFINITE SCROLL ****");
+ eventsPage--;
+ if ((eventsPage <= 0) && (pageLoaded))
+ {
+ moreEvents = false;
+ //console.log("*** At Page " + eventsPage + ", not proceeding");
+ return;
+ }
+
+ if (!enableLoadMore)
+ {
+ moreEvents = false; // Don't ion-scroll till enableLoadMore is true;
+ $scope.$broadcast('scroll.infiniteScrollComplete');
+
+ // console.log("**** LOADMORE ARTIFICALLY DISABLED");
+ return;
+ }
+
+ var loadingStr = "";
+ if ($scope.search.text != "")
+ {
+ var toastStr = $translate.instant('kToastSearchingPage') + eventsPage;
+ $ionicLoading.show(
+ {
+ maxwidth: 100,
+ scope: $scope,
+ template: '<button class="button button-clear icon-left ion-close-circled button-text-wrap" ng-click="cancelSearch()" >' + toastStr + '</button>'
+ });
+
+ loadingStr = "none";
+ }
+
+ nolangFrom = "";
+ nolangTo = "";
+ if ($rootScope.fromString)
+ nolangFrom = moment($rootScope.fromString).locale('en').format("YYYY-MM-DD HH:mm:ss");
+ if ($rootScope.toString)
+ nolangTo = moment($rootScope.toString).locale('en').format("YYYY-MM-DD HH:mm:ss");
+
+ NVRDataModel.getEvents($scope.id, eventsPage, loadingStr, nolangFrom, nolangTo)
+ .then(function(data)
+ {
+ var loginData = NVRDataModel.getLogin();
+ // console.log("Got new page of events with Page=" + eventsPage);
+ var myevents = data;
+
+ for (var i = 0; i < myevents.length; i++)
+ {
+
+ var idfound = true;
+ var ld = NVRDataModel.getLogin();
+
+ if (ld.persistMontageOrder)
+ {
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == myevents[i].Event.MonitorId && (NVRDataModel.isNotHidden(myevents[i].Event.MonitorId) || showHiddenMonitors))
+ {
+
+ //console.log ( $scope.monitors[ii].Monitor.Id + " MATCHES " + myevents[i].Event.MonitorId);
+ idfound = true;
+
+ break;
+ }
+ }
+ }
+
+ myevents[i].Event.humanizeTime = humanizeTime(myevents[i].Event.StartTime);
+ myevents[i].Event.MonitorName = NVRDataModel.getMonitorName(myevents[i].Event.MonitorId);
+ // now construct base path
+
+ myevents[i].Event.streamingURL = NVRDataModel.getStreamingURL(myevents[i].Event.MonitorId);
+ myevents[i].Event.baseURL = NVRDataModel.getBaseURL(myevents[i].Event.MonitorId);
+ myevents[i].Event.imageMode = NVRDataModel.getImageMode(myevents[i].Event.MonitorId);
+ // console.log ("***** MULTISERVER STREAMING URL FOR EVENTS " + myevents[i].Event.streamingURL);
+
+ // console.log ("***** MULTISERVER BASE URL FOR EVENTS " + myevents[i].Event.baseURL);
+
+ myevents[i].Event.ShowScrub = false;
+ myevents[i].Event.BasePath = computeBasePath(myevents[i]);
+ myevents[i].Event.relativePath = computeRelativePath(myevents[i]);
+ myevents[i].Event.height = eventsListDetailsHeight;
+
+ if (myevents[i].Event.imageMode == 'path')
+ //if (1)
+ myevents[i].Event.videoPath = myevents[i].Event.baseURL + "/events/" + myevents[i].Event.relativePath + myevents[i].Event.DefaultVideo;
+ else
+ myevents[i].Event.videoPath = myevents[i].Event.baseURL + "/index.php?view=view_video&eid=" + myevents[i].Event.Id;
+
+ if (idfound) $scope.events.push(myevents[i]);
+ }
+
+ //console.log("Got new page of events");
+ moreEvents = true;
+ $scope.$broadcast('scroll.infiniteScrollComplete');
+ },
+
+ function(error)
+ {
+ // console.log("*** No More Events to Load, Stop Infinite Scroll ****");
+ moreEvents = false;
+ $scope.$broadcast('scroll.infiniteScrollComplete');
+
+ });
+ }
+
+ $scope.loadMore = function()
+ {
+ loadMore();
+
+ };
+
+ $scope.toggleMinAlarmFrameCount = function()
+ {
+
+ var ld = NVRDataModel.getLogin();
+
+ console.log("Toggling " + ld.enableAlarmCount);
+ ld.enableAlarmCount = !ld.enableAlarmCount;
+ NVRDataModel.setLogin(ld);
+ $scope.loginData = NVRDataModel.getLogin();
+ doRefresh();
+ };
+
+ //--------------------------------------
+ // formats events dates in a nice way
+ //---------------------------------------
+
+ function humanizeTime(str)
+ {
+ //console.log ("Time:"+str+" TO LOCAL " + moment(str).local().toString());
+ //if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).fromNow();
+ // else
+ // return moment(str).fromNow();
+
+ }
+
+ $scope.prettifyDate = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format('MMM Do');
+ else
+ return moment(str).format('MMM Do');
+ };
+
+ function prettifyDate(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format('MMM Do');
+ else
+ return moment(str).format('MMM Do');
+ }
+
+ $scope.prettifyTime = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format(NVRDataModel.getTimeFormat());
+ else
+ return moment(str).format(NVRDataModel.getTimeFormat());
+ };
+
+ $scope.prettifyTimeSec = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format(NVRDataModel.getTimeFormatSec());
+ else
+ return moment(str).format(NVRDataModel.getTimeFormatSec());
+ };
+
+ $scope.prettify = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format(NVRDataModel.getTimeFormat() + ', MMMM Do YYYY');
+ else
+ return moment(str).format(NVRDataModel.getTimeFormat() + ', MMMM Do YYYY');
+ };
+ //--------------------------------------------------------
+ // For consistency we are keeping the refresher list
+ // but its a dummy. The reason I deviated is because
+ // refresh with infinite scroll is a UX problem - its
+ // easy to pull to refresh when scrolling up with
+ // a large list
+ //--------------------------------------------------------
+
+ $scope.dummyDoRefresh = function()
+ {
+ $scope.$broadcast('scroll.refreshComplete');
+ };
+
+ $scope.doRefresh = function()
+ {
+ doRefresh();
+ }; //dorefresh
+
+ function doRefresh()
+ {
+ // console.log("***Pull to Refresh");
+
+ NVRDataModel.debug("Reloading monitors");
+ var refresh = NVRDataModel.getMonitors(1);
+ refresh.then(function(data)
+ {
+ $scope.monitors = data;
+
+ /* var ld = NVRDataModel.getLogin();
+ if (ld.persistMontageOrder) {
+ var tempMon = data;
+ $scope.monitors = NVRDataModel.applyMontageMonitorPrefs(tempMon, 2)[0];
+ } else {
+ $scope.monitors = data;
+ }*/
+
+ getInitialEvents();
+ moreEvents = true;
+
+ });
+ }
+
+}]);
diff --git a/www/js/EventDateTimeFilterCtrl.js b/www/js/EventDateTimeFilterCtrl.js
new file mode 100644
index 00000000..772b16be
--- /dev/null
+++ b/www/js/EventDateTimeFilterCtrl.js
@@ -0,0 +1,138 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,moment */
+
+angular.module('zmApp.controllers')
+ .controller('zmApp.EventDateTimeFilterCtrl', ['$scope', '$ionicSlideBoxDelegate', '$ionicSideMenuDelegate', '$rootScope', '$ionicHistory', 'NVRDataModel', '$state', function($scope, $ionicScrollDelegate, $ionicSideMenuDelegate, $rootScope, $ionicHistory, NVRDataModel, $state)
+ {
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+ $scope.today = moment().format("YYYY-MM-DD");
+ });
+
+ //--------------------------------------------------------------------------
+ // Clears filters
+ //--------------------------------------------------------------------------
+
+ $scope.removeFilters = function()
+ {
+ $rootScope.isEventFilterOn = false;
+ $rootScope.fromDate = "";
+ $rootScope.fromTime = "";
+ $rootScope.toDate = "";
+ $rootScope.toTime = "";
+ $rootScope.fromString = "";
+ $rootScope.toString = "";
+
+ // if you come here via the events pullup
+ // you are looking at a specific monitor ID
+ // going back will only retain that monitor ID
+ // so lets reload with all monitors
+ //
+ //console.log (">>> BACKVIEW="+$ionicHistory.backTitle());
+
+ if ($ionicHistory.backTitle() == 'Timeline')
+ {
+ $ionicHistory.goBack();
+ }
+ else // in events, backview is undefined?
+ {
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ });
+ return;
+ }
+
+ //$ionicHistory.goBack();
+ };
+
+ //--------------------------------------------------------------------------
+ // Saves filters in root variables so EventFilter can access it. I know:
+ // don't root.
+ //--------------------------------------------------------------------------
+ $scope.saveFilters = function()
+ {
+ if (!$rootScope.fromDate)
+ {
+ //console.log("RESET fromDate");
+ $rootScope.fromDate = new Date();
+ NVRDataModel.debug("DateTimeFilter: resetting from date");
+ }
+
+ if (!$rootScope.toDate)
+ {
+ // console.log("RESET toDate");
+ $rootScope.toDate = new Date();
+ NVRDataModel.debug("DateTimeFilter: resetting to date");
+ }
+
+ if (!$rootScope.fromTime)
+ {
+ // console.log("RESET fromTime");
+ $rootScope.fromTime = new Date(99, 5, 24, 0, 0, 0, 0); //moment().format("hh:mm:ss");
+ NVRDataModel.debug("DateTimeFilter: resetting from time");
+ }
+
+ if (!$rootScope.toTime)
+ {
+ //console.log("RESET toTime");
+ $rootScope.toTime = new Date(99, 5, 24, 23, 59, 59, 0);
+ //$rootScope.toTime = "01:01:02"; //moment().format("hh:mm:ss");
+ NVRDataModel.debug("DateTimeFilter: resetting to time");
+ }
+
+ if ($rootScope.fromDate > $rootScope.toDate)
+ {
+ NVRDataModel.log("From date > To Date, swapping");
+ var t = $rootScope.fromDate;
+ $rootScope.fromDate = $rootScope.toDate;
+ $rootScope.toDate = t;
+ }
+
+ $rootScope.isEventFilterOn = true;
+ $rootScope.fromString = moment($rootScope.fromDate).format("YYYY-MM-DD") + " " + moment($rootScope.fromTime).format("HH:mm:ss");
+
+ $rootScope.toString = moment($rootScope.toDate).format("YYYY-MM-DD") + " " + moment($rootScope.toTime).format("HH:mm:ss");
+
+ //console.log("CONCAT DATES " + temp);
+ //
+ // var startDate = moment(temp).format("YYYY-MM-DD hh:mm:ss");
+ NVRDataModel.debug("DateTimeFilter: From/To is now: " + $rootScope.fromString + " & " + $rootScope.toString);
+ $ionicHistory.goBack();
+ };
+
+ }
+
+ ]);
diff --git a/www/js/EventModalCtrl.js b/www/js/EventModalCtrl.js
new file mode 100644
index 00000000..b8a7acbc
--- /dev/null
+++ b/www/js/EventModalCtrl.js
@@ -0,0 +1,1873 @@
+// Common Controller for the montage view
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global saveAs, cordova,StatusBar,angular,console,ionic, moment, Chart */
+
+angular.module('zmApp.controllers').controller('EventModalCtrl', ['$scope', '$rootScope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$q', '$sce', 'carouselUtils', '$ionicPopup', '$translate', '$filter', 'SecuredPopups', function($scope, $rootScope, zm, NVRDataModel, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $q, $sce, carouselUtils, $ionicPopup, $translate, $filter, SecuredPopups)
+{
+
+ // from parent scope
+ var currentEvent = $scope.currentEvent;
+ var nphTimer;
+ var eventQueryHandle;
+ $scope.loginData = NVRDataModel.getLogin();
+ $scope.currentRate = '-';
+ var timeFormat = 'MM/DD/YYYY HH:mm:ss';
+ var event;
+ var gEvent;
+ var handle;
+
+ var framearray = {
+
+ labels: [],
+ datasets: [
+ {
+ //label: '# of Votes',
+ backgroundColor: 'rgba(242, 12, 12, 0.5)',
+ borderColor: 'rgba(242, 12, 12, 0.5)',
+ data: [],
+ }]
+ };
+
+ var frameoptions = [];
+
+ var eventImageDigits = 5; // failsafe
+ $scope.currentProgress = {
+ progress: 0
+ };
+ $scope.sliderProgress = {
+ progress: 0
+ };
+ NVRDataModel.getKeyConfigParams(0)
+ .then(function(data)
+ {
+ //console.log ("***GETKEY: " + JSON.stringify(data));
+ eventImageDigits = parseInt(data);
+ NVRDataModel.log("Image padding digits reported as " + eventImageDigits);
+ });
+
+ $scope.animationInProgress = false;
+ $scope.imageFit = true;
+ // FIXME: This is a hack - for some reason
+ // the custom slider view is messed up till the image loads
+ // in modal view
+ $scope.showModalRangeSider = false;
+ $scope.isModalActive = true;
+
+ $timeout(function()
+ {
+ $scope.showModalRangeSider = true;
+
+ }, 2000);
+
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ $rootScope.authSession = "undefined";
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kNegotiatingStreamAuth'),
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 300,
+ showDelay: 0
+ });
+ var ld = NVRDataModel.getLogin();
+
+ $scope.currentStreamMode = ld.gapless ? 'gapless' : 'single';
+ NVRDataModel.log("Using stream mode " + $scope.currentStreamMode);
+
+ NVRDataModel.debug("EventModalCtrl called from " + $ionicHistory.currentStateName());
+ // This is not needed for event mode
+
+ NVRDataModel.debug("Setting playback to " + $scope.streamMode);
+
+ $rootScope.validMonitorId = $scope.monitors[0].Monitor.Id;
+ NVRDataModel.getAuthKey($rootScope.validMonitorId, (Math.floor((Math.random() * 999999) + 1)).toString())
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ $rootScope.authSession = success;
+ NVRDataModel.log("Modal: Stream authentication construction: " + $rootScope.authSession);
+
+ },
+ function(error)
+ {
+
+ $ionicLoading.hide();
+ NVRDataModel.debug("ModalCtrl: Error details of stream auth:" + error);
+ //$rootScope.authSession="";
+ NVRDataModel.log("Modal: Error returned Stream authentication construction. Retaining old value of: " + $rootScope.authSession);
+ });
+
+ //--------------------------------------------------------------------------------------
+ // Handles bandwidth change, if required
+ //
+ //--------------------------------------------------------------------------------------
+
+ $rootScope.$on("bandwidth-change", function(e, data)
+ {
+ // not called for offline, I'm only interested in BW switches
+ NVRDataModel.debug("Got network change:" + data);
+ var ds;
+ if (data == 'lowbw')
+ {
+ ds = $translate.instant('kLowBWDisplay');
+ }
+ else
+ {
+ ds = $translate.instant('kHighBWDisplay');
+ }
+ NVRDataModel.displayBanner('net', [ds]);
+
+ var ld = NVRDataModel.getLogin();
+
+ $scope.singleImageQuality = (NVRDataModel.getBandwidth() == "lowbw") ? zm.eventSingleImageQualityLowBW : ld.singleImageQuality;
+ });
+
+ //-------------------------------------------------------
+ // we use this to reload the connkey if authkey changed
+ //------------------------------------------------------
+
+ $rootScope.$on("auth-success", function()
+ {
+
+ NVRDataModel.debug("EventModalCtrl: Re-login detected, resetting everything & re-generating connkey");
+ NVRDataModel.stopNetwork("Auth-Success inside EventModalCtrl");
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ //console.log ("********* OFFSET FROM AUTH SUCC");
+ $timeout(function()
+ {
+ sendCommand('14', $scope.connKey, '&offset=' + $scope.currentProgress.progress);
+ }, 500);
+ //$timeout.cancel(eventQueryHandle);
+ //eventQueryHandle = $timeout (function(){checkEvent();}, zm.eventPlaybackQuery);
+
+ });
+
+ //-------------------------------------------------------
+ // tap to pause
+ //------------------------------------------------------
+
+ $scope.togglePause = function()
+ {
+ $scope.isPaused = !$scope.isPaused;
+ NVRDataModel.debug("Paused is " + $scope.isPaused);
+ sendCommand($scope.isPaused ? '1' : '2', $scope.connKey);
+
+ };
+
+ $scope.onPlayerReady = function(api)
+ {
+
+ // we need this timeout to avoid load interrupting
+ // play -- I suppose its an angular digest foo thing
+ console.log ("*********** ON PLAY READY");
+ handle = api;
+
+ $ionicLoading.show(
+ {
+ template: "<ion-spinner icon='ripple' class='spinner-energized'></ion-spinner><br/>" + $translate.instant('kVideoLoading')+"...",
+
+ });
+ NVRDataModel.debug("Player is ready");
+ $timeout(function()
+ {
+ handle.pause();
+ handle.setPlayback(NVRDataModel.getLogin().videoPlaybackSpeed);
+ handle.play();
+ NVRDataModel.debug ("*** Invoking play");
+
+ }, 300);
+
+ // window.stop();
+ };
+
+ $scope.onPlaybackUpdate = function(rate)
+ {
+ console.log ("UPDATED RATE TO "+rate);
+ var ld = NVRDataModel.getLogin();
+ ld.videoPlaybackSpeed = rate;
+ NVRDataModel.setLogin(ld);
+ };
+
+ $scope.onCanPlay = function()
+ {
+
+ console.log ("*********** CAN PLAY");
+ $ionicLoading.hide();
+ NVRDataModel.debug("This video can be played");
+ $scope.videoObject.config.cuepoints.points = [];
+ // now set up cue points
+ NVRDataModel.debug("Setting cue points..");
+ NVRDataModel.debug ("API-Total length:"+currentEvent.Event.Length);
+ NVRDataModel.debug ("Player-Total length:"+handle.totalTime/1000);
+
+ for (var l=0; l<currentEvent.Frame.length; l++ )
+ {
+ if (currentEvent.Frame[l].Type=='Alarm')
+ {
+ // var ft = moment(currentEvent.Frame[l].TimeStamp);
+ //var s = factor*Math.abs(st.diff(ft,'seconds'));
+
+ var s = currentEvent.Frame[l].Delta;
+
+ //console.log("START="+currentEvent.Event.StartTime);
+ //console.log("END="+currentEvent.Frame[l].TimeStamp);
+ NVRDataModel.debug ("alarm cue at:"+s+"s");
+ $scope.videoObject.config.cuepoints.points.push({time:s});
+ }
+ }
+ };
+
+ $scope.onVideoError = function(event)
+ {
+ $ionicLoading.hide();
+ if (!$scope.isModalActive) return;
+ NVRDataModel.debug("player reported a video error:" + JSON.stringify(event));
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kError'),
+ template: $rootScope.platformOS == 'desktop' ? $translate.instant('kVideoError') : $translate.instant('kVideoErrorMobile'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+
+ };
+
+ //-------------------------------------------------------
+ // This is what we call every zm.EventQueryInterval
+ // it really only queries to get status to it can display
+ // zms takes care of the display
+ //------------------------------------------------------
+
+ function checkEvent()
+ {
+
+ if ($scope.modalFromTimelineIsOpen == false)
+ {
+ NVRDataModel.log("Modal was closed in timeline, cancelling timer");
+ $interval.cancel(eventQueryHandle);
+ return;
+ }
+
+ //console.log ("Event timer");
+ //console.log ("Event timer");
+ $scope.checkEventOn = true;
+ if ($scope.defaultVideo !== undefined && $scope.defaultVideo != '')
+ {
+ //console.log("playing video, not using zms, skipping event commands");
+ }
+ else
+ {
+ processEvent('99', $scope.connKey);
+ }
+ }
+
+ function sendCommand(cmd, connkey, extras, rq)
+ {
+ var d = $q.defer();
+
+ if ($scope.defaultVideo !== undefined && $scope.defaultVideo != '')
+ {
+ // console.log("playing video, not using zms, skipping event commands");
+ d.resolve(true);
+ return (d.promise);
+ }
+
+ var loginData = NVRDataModel.getLogin();
+ //console.log("Sending CGI command to " + loginData.url);
+ var rqtoken = rq ? rq : "stream";
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ $http(
+ {
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ //'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ if (extras)
+ {
+ foo = foo + extras;
+ //console.log("EXTRAS****SUB RETURNING " + foo);
+ }
+ //console.log("CGI subcommand=" + foo);
+ return foo;
+
+ },
+
+ data:
+ {
+ view: "request",
+ request: rqtoken,
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken,
+ // user: loginData.username,
+ // pass: loginData.password
+ }
+ })
+ .then(function(resp)
+ {
+ NVRDataModel.debug("sendCmd response:" + JSON.stringify(resp));
+ d.resolve(resp);
+ return (d.promise);
+
+ },
+ function(resp)
+ {
+ NVRDataModel.debug("sendCmd error:" + JSON.stringify(resp));
+ d.reject(resp);
+ return (d.promise);
+ });
+
+ return (d.promise);
+ }
+
+ function processEvent(cmd, connkey)
+ {
+
+ if ($scope.blockSlider)
+ {
+ //console.log("Not doing ZMS Command as slider is depressed...");
+ return;
+ }
+
+ var loginData = NVRDataModel.getLogin();
+ //console.log("sending process Event command to " + loginData.url);
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ var req = $http(
+ {
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ //'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ //console.log("****processEvent subcommands RETURNING " + foo);
+ return foo;
+ },
+
+ data:
+ {
+ view: "request",
+ request: "stream",
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken,
+ // user: loginData.username,
+ // pass: loginData.password
+ }
+ });
+
+ req.success(function(resp)
+ {
+ // NVRDataModel.debug ("processEvent success:"+JSON.stringify(resp));
+
+ if (resp.result == "Ok")
+ {
+
+ $scope.currentProgress.progress = resp.status.progress;
+ $scope.eventId = resp.status.event;
+ $scope.d_eventId = $scope.eventId;
+ $scope.currentRate = resp.status.rate;
+
+ if ($scope.currentProgress.progress > $scope.currentEventDuration) $scope.currentProgress.progress = $scope.currentEventDuration;
+ $scope.progressText = "At " + $scope.currentProgress.progress + "s of " + $scope.currentEventDuration + "s";
+
+ $scope.sliderProgress.progress = $scope.currentProgress.progress;
+
+ // lets not do this and use zms to move forward or back
+ // as this code conflicts with fast rev etc
+ //if (Math.floor(resp.status.progress) >=$scope.currentEventDuration)
+
+ //$timeout (checkEvent(), zm.eventPlaybackQuery);
+ //eventQueryHandle = $timeout (function(){checkEvent();}, zm.eventPlaybackQuery);
+
+ }
+ else // resp.result was messed up
+
+ {
+ NVRDataModel.debug("Hmm I found an error " + JSON.stringify(resp));
+ //window.stop();
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+
+ // console.log (JSON.stringify(resp));
+ $timeout(function()
+ {
+ sendCommand('14', $scope.connKey, '&offset=' + $scope.currentProgress.progress);
+ }, 500);
+ NVRDataModel.debug("so I'm regenerating Connkey to " + $scope.connKey);
+ //eventQueryHandle = $timeout (function(){checkEvent();}, zm.eventPlaybackQuery);
+ }
+ });
+
+ req.error(function(resp)
+ {
+ NVRDataModel.debug("processEvent error:" + JSON.stringify(resp));
+ //eventQueryHandle = $timeout (function(){checkEvent();}, zm.eventPlaybackQuery);
+
+ });
+
+ }
+
+ function onPause()
+ {
+
+ // $interval.cancel(modalIntervalHandle);
+
+ // FIXME: Do I need to setAwake(false) here?
+ $interval.cancel(eventQueryHandle);
+ NVRDataModel.log("EventModalCtrl: paused, killing timer");
+
+ }
+
+ function onResume()
+ {
+ NVRDataModel.debug("EventModalCtrl: Modal resume called");
+ $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1);
+
+ }
+
+ $scope.finishedLoadingImage = function()
+ {
+ // console.log("***Monitor image FINISHED Loading***");
+ $ionicLoading.hide();
+
+ };
+
+ $scope.enableSliderBlock = function()
+ {
+ $scope.blockSlider = true;
+ };
+
+ $scope.youChangedSlider = function()
+ {
+
+ //console.log("YOU changed " + $scope.sliderProgress.progress);
+ $scope.currentProgress.progress = $scope.sliderProgress.progress;
+ sendCommand('14', $scope.connKey, '&offset=' + $scope.currentProgress.progress)
+ .then(function(s)
+ {
+ $scope.blockSlider = false;
+ }, function(e)
+ {
+ $scope.blockSlider = false;
+ });
+
+ };
+
+ //-----------------------------------------------------------------------
+ // Sucess/Error handlers for saving a snapshot of the
+ // monitor image to phone storage
+ //-----------------------------------------------------------------------
+
+ function SaveSuccess()
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDone'),
+ noBackdrop: true,
+ duration: 1000
+ });
+ NVRDataModel.debug("ModalCtrl:Photo saved successfuly");
+ }
+
+ function SaveError(e)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kErrorSave'),
+ noBackdrop: true,
+ duration: 2000
+ });
+ NVRDataModel.log("Error saving image: " + e.message);
+ //console.log("***ERROR");
+ }
+
+ $scope.jumpToOffsetInEvent = function()
+ {
+ // streamReq.send( streamParms+"&command="+CMD_SEEK+"&offset="+offset );
+ };
+
+ //-----------------------------------------------------------------------
+ // Saves a snapshot of the monitor image to phone storage
+ //-----------------------------------------------------------------------
+
+ $scope.saveEventImageToPhoneWithPerms = function(onlyAlarms)
+ {
+
+ if ($rootScope.platformOS != 'android')
+ {
+ processSaveEventImageToPhone(onlyAlarms);
+ return;
+ }
+
+ // if we are on android do the 6.x+ hasPermissions flow
+ NVRDataModel.debug("EventModalCtrl: Permission checking for write");
+ var permissions = cordova.plugins.permissions;
+ permissions.hasPermission(permissions.WRITE_EXTERNAL_STORAGE, checkPermissionCallback, null);
+
+ function checkPermissionCallback(status)
+ {
+ if (!status.hasPermission)
+ {
+ SaveError("No permission to write to external storage");
+ }
+ permissions.requestPermission(permissions.WRITE_EXTERNAL_STORAGE, succ, err);
+ }
+
+ function succ(s)
+ {
+ processSaveEventImageToPhone(onlyAlarms);
+ }
+
+ function err(e)
+ {
+ SaveError("Error in requestPermission");
+ }
+ };
+
+ function processSaveEventImageToPhone(onlyAlarms)
+ {
+
+ if ($scope.loginData.useNphZmsForEvents)
+ {
+ NVRDataModel.log("Use ZMS stream to save to phone");
+ saveEventImageToPhoneZms(onlyAlarms);
+
+ }
+ else
+ {
+ saveEventImageToPhone(onlyAlarms);
+ }
+
+ }
+
+ function saveEventImageToPhoneZms(onlyAlarms)
+ {
+ // The strategy here is to build the array now so we can grab frames
+ // $scope.currentProgress.progress is the seconds where we are
+ // $scope.eventId is the event Id
+
+ $scope.isPaused = true;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait'),
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ sendCommand('1', $scope.connKey).
+ then(function(resp)
+ {
+
+ // console.log ("PAUSE ANSWER IS " + JSON.stringify(resp));
+ $scope.currentProgress.progress = resp.data.status.progress;
+ // console.log ("STEP 0 progress is " + $scope.currentProgress.progress);
+ $scope.slides = [];
+
+ var apiurl = $scope.loginData.apiurl + "/events/" + $scope.eventId + ".json";
+ NVRDataModel.debug("prepared to get frame details using " + apiurl);
+ $http.get(apiurl)
+ .then(function(success)
+ {
+
+ event = success.data.event;
+
+ event.Event.BasePath = computeBasePath(event);
+ event.Event.relativePath = computeRelativePath(event);
+ $scope.playbackURL = $scope.loginData.url;
+ $scope.eventBasePath = event.Event.BasePath;
+ $scope.relativePath = event.Event.relativePath;
+
+ // now lets get approx frame #
+
+ var totalTime = event.Event.Length;
+ var totalFrames = event.Event.Frames;
+
+ var myFrame = Math.round(totalFrames / totalTime * $scope.currentProgress.progress);
+
+ // console.log ("STEP 0: playback " + $scope.playbackURL + " total time " + totalTime + " frames " + totalFrames);
+
+ if (myFrame > totalFrames) myFrame = totalFrames;
+
+ // console.log ("STEP 0 myFrame is " + myFrame);
+ // console.log ("DUMPING " + JSON.stringify(event));
+ $scope.mycarousel.index = myFrame;
+ // console.log ("STEP 1 : Computed index as "+ $scope.mycarousel.index);
+ var i, p = 0;
+ for (i = 1; i <= event.Frame.length; i++)
+ {
+ var fname = padToN(event.Frame[i - 1].FrameId, eventImageDigits) + "-capture.jpg";
+ // console.log ("Building " + fname);
+
+ // console.log ("DUMPING ONE " + JSON.stringify(event.Frame[i-1]));
+ // onlyAlarms means only copy alarmed frames
+ if (onlyAlarms)
+ {
+ if (event.Frame[i - 1] && event.Frame[i - 1].Type == 'Alarm')
+ {
+ p++;
+ $scope.slides.push(
+ {
+ id: event.Frame[i - 1].FrameId,
+ img: fname,
+ });
+ //console.log ("ALARM PUSHED " + fname);
+ }
+ }
+ else // push all frames
+ {
+ //now handle bulk frames pushing before pushing this one
+ if (event.Frame[i-1].Type == 'Bulk')
+ {
+ var f1 = parseInt(event.Frame[i-2].FrameId);
+ var f2 = parseInt(event.Frame[i-1].FrameId);
+
+ //console.log ("Filling in bulk from:"+f1+" to "+(f2-1));
+ for (var bulk=f1+1; bulk < f2; bulk++)
+ {
+ //console.log ("Storing bulk:"+bulk);
+ var bfname = padToN(bulk, eventImageDigits) + "-capture.jpg";
+ p++;
+ $scope.slides.push({
+ id: bulk,
+ img: bfname
+
+ });
+
+
+ }
+ }
+ //console.log ("storing: "+event.Frame[i - 1].FrameId);
+ p++;
+ $scope.slides.push(
+ {
+ id: event.Frame[i - 1].FrameId,
+ img: fname,
+ });
+
+
+
+ }
+
+ }
+ //console.log ("I PUSHED:" + p+" BUT SLIDE LENGHT BEFORE DISPLAY:"+$scope.slides.length);
+ // console.log ("STEP 2 : calling Save Event To Phone");
+ $ionicLoading.hide();
+ saveEventImageToPhone(onlyAlarms);
+
+ },
+ function(err)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("snapshot API Error: Could not get frames " + JSON.stringify(err));
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kErrorRetrievingFrames'),
+ noBackdrop: true,
+ duration: 4000
+ });
+ });
+ },
+
+ function(err)
+ {
+ NVRDataModel.debug("Error pausing stream before snapshot " + JSON.stringify(err));
+ $ionicLoading.hide();
+ }
+
+ ); // then
+
+ }
+
+ // don't think this is used anymore
+ function saveEventImageToPhone(onlyAlarms)
+ {
+ // console.log ("________________UNUSED?_______________________");
+ var curState = carouselUtils.getStop();
+ carouselUtils.setStop(true);
+
+ //console.log("Your index is " + $scope.mycarousel.index);
+ //console.log("Associated image is " + $scope.slides[$scope.mycarousel.index].img);
+
+ NVRDataModel.debug("ModalCtrl: SaveEventImageToPhone called");
+ var canvas, context, imageDataUrl, imageData;
+ var loginData = NVRDataModel.getLogin();
+
+ // for alarms only
+ if (onlyAlarms) $scope.mycarousel.index = 0;
+ var url = $scope.playbackURL + '/index.php?view=image&rand=' + $rootScope.rand + "&path=" + $scope.relativePath + $scope.slides[$scope.mycarousel.index].img;
+
+ $scope.selectEventUrl = url;
+ $scope.slideIndex = $scope.mycarousel.index;
+ $scope.slideLastIndex = $scope.slides.length - 1;
+ console.log ("FRAMES LENGTH IS " +$scope.slideLastIndex );
+
+ // console.log ("URL TO DISPLAY " + url);
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ template: '<center>Frame: {{slideIndex+1}} / {{slideLastIndex+1}}</center><br/><img src="{{selectEventUrl}}" width="100%" />',
+ title: 'Select ' + (onlyAlarms ? 'Alarmed ' : '') + 'frame to save',
+ subTitle: 'use left and right arrows to change',
+ scope: $scope,
+ cssClass: 'popup95',
+ buttons: [
+ {
+ // left 1
+ text: '',
+ type: 'button-small button-energized ion-chevron-left',
+ onTap: function(e)
+ {
+ if ($scope.slideIndex > 0) $scope.slideIndex--;
+ $scope.selectEventUrl = $scope.playbackURL + '/index.php?view=image&rand=' + $rootScope.rand + "&path=" + $scope.relativePath + $scope.slides[$scope.slideIndex].img;
+ //NVRDataModel.log("selected frame is " + $scope.slideIndex);
+
+ console.log("URL TO DISPLAY " + $scope.slides[$scope.slideIndex].img);
+
+ e.preventDefault();
+ }
+ },
+ {
+ // right 1
+ text: '',
+ type: 'button-small button-energized ion-chevron-right',
+ onTap: function(e)
+ {
+ if ($scope.slideIndex < $scope.slideLastIndex) $scope.slideIndex++;
+ $scope.selectEventUrl = $scope.playbackURL + '/index.php?view=image&rand=' + $rootScope.rand + "&path=" + $scope.relativePath + $scope.slides[$scope.slideIndex].img;
+ //NVRDataModel.log("selected frame is " + $scope.slideIndex);
+ console.log("URL TO DISPLAY " + $scope.slides[$scope.slideIndex].img);
+ e.preventDefault();
+ }
+ },
+ {
+ // left 10
+ text: '',
+ type: 'button-small button-energized ion-skip-backward',
+ onTap: function(e)
+ {
+ var tempVar = $scope.slideIndex;
+ tempVar -= 10;
+ if (tempVar < 0) tempVar = 0;
+ $scope.slideIndex = tempVar;
+
+ $scope.selectEventUrl = $scope.playbackURL + '/index.php?view=image&rand=' + $rootScope.rand + "&path=" + $scope.relativePath + $scope.slides[$scope.slideIndex].img;
+ //NVRDataModel.log("selected frame is " + $scope.slideIndex);
+
+ e.preventDefault();
+ }
+ },
+ {
+ // right 10
+ text: '',
+ type: 'button-small button-energized ion-skip-forward',
+ onTap: function(e)
+ {
+ var tempVar = $scope.slideIndex;
+ tempVar += 10;
+ if (tempVar > $scope.slideLastIndex) tempVar = $scope.slideLastIndex;
+ $scope.slideIndex = tempVar;
+ if ($scope.slideIndex < $scope.slideLastIndex) $scope.slideIndex++;
+ $scope.selectEventUrl = $scope.playbackURL + '/index.php?view=image&rand=' + $rootScope.rand + "&path=" + $scope.relativePath + $scope.slides[$scope.slideIndex].img;
+ //NVRDataModel.log("selected frame is " + $scope.slideIndex);
+ e.preventDefault();
+ }
+ },
+
+ {
+ text: '',
+ type: 'button-assertive button-small ion-close-round'
+ },
+ {
+ text: '',
+ type: 'button-positive button-small ion-checkmark-round',
+ onTap: function(e)
+ {
+ saveNow();
+
+ }
+ }
+ ]
+ });
+
+ function saveNow()
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSavingSnapshot') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ var url = $scope.selectEventUrl;
+ NVRDataModel.log("saveNow: File path to grab is " + url);
+
+ var img = new Image();
+ img.onload = function()
+ {
+ // console.log("********* ONLOAD");
+ canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ context = canvas.getContext('2d');
+ context.drawImage(img, 0, 0);
+
+ imageDataUrl = canvas.toDataURL('image/jpeg', 1.0);
+ imageData = imageDataUrl.replace(/data:image\/jpeg;base64,/, '');
+
+ if ($rootScope.platformOS != "desktop")
+ {
+ try
+ {
+
+ cordova.exec(
+ SaveSuccess,
+ SaveError,
+ 'Canvas2ImagePlugin',
+ 'saveImageDataToLibrary', [imageData]
+ );
+ // carouselUtils.setStop(curState);
+ }
+ catch (e)
+ {
+
+ SaveError(e.message);
+ // carouselUtils.setStop(curState);
+ }
+ }
+ else
+ {
+
+ var fname = $scope.relativePath + $scope.slides[$scope.slideIndex].img + ".png";
+ fname = fname.replace(/\//, "-");
+ fname = fname.replace(/\.jpg/, '');
+
+ canvas.toBlob(function(blob)
+ {
+ saveAs(blob, fname);
+ SaveSuccess();
+ });
+ }
+ };
+ try
+ {
+ img.src = url;
+ // console.log ("SAVING IMAGE SOURCE");
+ }
+ catch (e)
+ {
+ SaveError(e.message);
+ }
+ }
+ }
+
+ $scope.reloadView = function()
+ {
+ NVRDataModel.log("Reloading view for modal view, recomputing rand");
+ $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1);
+ $scope.isModalActive = true;
+ };
+
+ $scope.scaleImage = function()
+ {
+
+ $scope.imageFit = !$scope.imageFit;
+ console.log("Switching image style to " + $scope.imageFit);
+ };
+
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log (">>>>>>>>>>>>>>>>>>>> MODAL VIEW ENTER");
+
+ });
+
+ $scope.$on('modal.shown', function(e, m)
+ {
+
+ if (m.id != 'footage')
+
+ return;
+
+ $scope.isToggleListMenu = true;
+ $scope.videoDynamicTime = "";
+ $scope.videoIsReady = false;
+ var ld = NVRDataModel.getLogin();
+ $scope.loginData = NVRDataModel.getLogin();
+
+ $scope.singleImageQuality = (NVRDataModel.getBandwidth() == "lowbw") ? zm.eventSingleImageQualityLowBW : ld.singleImageQuality;
+ $scope.blockSlider = false;
+ $scope.checkEventOn = false;
+ //$scope.singleImageQuality = 100;
+
+ //$scope.commandURL = $scope.currentEvent.Event.baseURL+"/index.php";
+ // NVRDataModel.log (">>>>>>>>>>>>>>>>>>ZMS url command is " + $scope.commandURL);
+
+ currentEvent = $scope.currentEvent;
+
+ //console.log("Current Event " + JSON.stringify(currentEvent));
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ NVRDataModel.debug("Generated Connkey:" + $scope.connKey);
+
+ $scope.currentFrame = 1;
+ $scope.isPaused = false;
+
+ gEvent = $scope.currentEvent;
+ //console.log ("CURRENT EVENT " + JSON.stringify($scope.currentEvent));
+ //
+ $scope.currentEventDuration = Math.floor($scope.currentEvent.Event.Length);
+ //console.log ($scope.event.Event.Frames);
+ if (currentEvent && currentEvent.Event)
+ {
+ //console.log ("************ CALLING PREPARE MODAL ***********");
+ prepareModalEvent(currentEvent.Event.Id);
+ if (ld.useNphZmsForEvents)
+ {
+ $timeout(function()
+ {
+
+ if ($scope.modal != undefined && $scope.modal.isShown())
+ {
+ NVRDataModel.log(">>>Starting checkAllEvents interval...");
+
+ //eventQueryHandle = $timeout (checkEvent(), zm.eventPlaybackQuery);
+
+ $interval.cancel(eventQueryHandle);
+ eventQueryHandle = $interval(function()
+ {
+ checkEvent();
+ // console.log ("Refreshing Image...");
+ }.bind(this), (NVRDataModel.getBandwidth() == "lowbw") ? zm.eventPlaybackQueryLowBW : zm.eventPlaybackQuery);
+ }
+ else
+ {
+ NVRDataModel.log(">>>Modal was exited, not starting checkAllEvents");
+ }
+
+ }, 5000);
+ }
+
+ }
+
+ });
+
+ //var current_data;
+ function drawGraph()
+ {
+
+ var cv = document.getElementById("eventchart");
+ var ctx = cv.getContext("2d");
+
+ frameoptions = {
+ responsive: true,
+ legend: false,
+ title:
+ {
+ display: false,
+ text: ""
+ },
+ scales:
+ {
+ yAxes: [
+ {
+ display: false,
+ scaleLabel:
+ {
+ display: false,
+ labelString: 'value',
+ }
+
+ }],
+ xAxes: [
+ {
+ type: 'time',
+ display: false,
+ time:
+ {
+
+ format: timeFormat,
+ tooltipFormat: 'll HH:mm',
+ min: framearray.datasets[0].data[0].x,
+ max: framearray.datasets[0].data[framearray.datasets[0].data.length - 1].x,
+ displayFormats:
+ {
+
+ }
+ },
+ scaleLabel:
+ {
+ display: false,
+ labelString: ''
+ }
+
+ }]
+ }
+ };
+
+ $timeout(function()
+ {
+
+ var myChart = new Chart(ctx,
+ {
+ type: 'line',
+ data: framearray,
+ options: frameoptions,
+ });
+
+ });
+ }
+
+ $scope.videoTime = function(s, c)
+ {
+ var a, o;
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ a = moment.tz(s, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess());
+
+ }
+ else
+ {
+ a = moment(s);
+ }
+ a.add(c);
+
+ o = a.format("MMM Do " + NVRDataModel.getTimeFormatSec());
+ $scope.videoDynamicTime = o;
+ //return a.format("MMM Do "+o);
+
+ };
+
+ $scope.$on('modal.removed', function(e, m)
+ {
+ console.log("************* REMOVE CALLED");
+ $interval.cancel(eventQueryHandle);
+ if (m.id != 'footage')
+ return;
+
+ $scope.isModalActive = false;
+
+ NVRDataModel.debug("Modal removed - killing connkey");
+ sendCommand(17, $scope.connKey);
+ //$timeout (function(){NVRDataModel.stopNetwork("Modal removed inside EventModalCtrl");},400);
+
+ // Execute action
+ });
+
+ // Playback speed adjuster
+ $scope.adjustSpeed = function(val)
+ {
+
+ if ($scope.defaultVideo !== undefined && $scope.defaultVideo != '')
+ {
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kUseVideoControls'),
+ noBackdrop: true,
+ duration: 3000
+ });
+ return;
+ }
+
+ var ld = NVRDataModel.getLogin();
+
+ if (ld.useNphZmsForEvents)
+ {
+
+ var cmd;
+ $scope.isPaused = false;
+ switch (val)
+ {
+ case 'ff':
+ cmd = 4;
+ break;
+ case 'fr':
+ cmd = 7;
+ break;
+ case 'np':
+ cmd = 2;
+ break;
+ case 'p':
+ cmd = 1;
+ $scope.isPaused = true;
+ break;
+ default:
+ cmd = 0;
+ }
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+ sendCommand(cmd, $scope.connKey)
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+
+ },
+ function(err)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.debug("Error in adjust speed: " + JSON.stringify(err));
+ }
+ );
+
+ }
+ else // not using nph
+ {
+
+ switch (val)
+ {
+
+ case "super":
+ $scope.eventSpeed = 20 / $scope.event.Event.Frames;
+ carouselUtils.setDuration($scope.eventSpeed);
+ break;
+ case "normal":
+ $scope.eventSpeed = $scope.event.Event.Length / $scope.event.Event.Frames;
+ //$scope.eventSpeed = 5;
+ carouselUtils.setDuration($scope.eventSpeed);
+
+ break;
+ case "faster":
+ $scope.eventSpeed = $scope.eventSpeed / 2;
+ if ($scope.eventSpeed < 20 / $scope.event.Event.Frames)
+ $scope.eventSpeed = 10 / $scope.event.Event.Frames;
+ carouselUtils.setDuration($scope.eventSpeed);
+ break;
+ case "slower":
+ $scope.eventSpeed = $scope.eventSpeed * 2;
+ carouselUtils.setDuration($scope.eventSpeed);
+
+ break;
+ default:
+
+ }
+ NVRDataModel.debug("Set playback speed to " + $scope.eventSpeed);
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPlaybackInterval') + ': ' + $scope.eventSpeed.toFixed(3) + "ms",
+ animation: 'fade-in',
+ showBackdrop: false,
+ duration: 1500,
+ maxWidth: 300,
+ showDelay: 0
+ });
+ }
+
+ };
+
+ $scope.toggleListMenu = function()
+ {
+
+ $scope.isToggleListMenu = !$scope.isToggleListMenu;
+ };
+
+ $scope.toggleGapless = function()
+ {
+ // console.log(">>>>>>>>>>>>>>GAPLESS TOGGLE INSIDE MODAL");
+ $scope.loginData.gapless = !$scope.loginData.gapless;
+ NVRDataModel.setLogin($scope.loginData);
+
+ NVRDataModel.debug("EventModalCtrl: gapless has changed resetting everything & re-generating connkey");
+ NVRDataModel.stopNetwork("EventModalCtrl-toggle gapless");
+ NVRDataModel.debug("Regenerating connkey as gapless has changed");
+ // console.log ("********* OFFSET FROM TOGGLE GAPLESS");
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ $timeout(function()
+ {
+ sendCommand('14', $scope.connKey, '&offset=' + $scope.currentProgress.progress);
+ }, 500);
+ //$timeout.cancel(eventQueryHandle);
+ //eventQueryHandle = $timeout (function(){checkEvent();}, zm.eventPlaybackQuery);
+
+ };
+
+ // This function returns neighbor events if applicable
+ function neighborEvents(eid)
+ {
+ var d = $q.defer();
+ // now get event details to show alarm frames
+ var loginData = NVRDataModel.getLogin();
+ var myurl = loginData.apiurl + '/events/' + eid + ".json";
+ var neighbors = {
+ prev: "",
+ next: ""
+ };
+ $http.get(myurl)
+ .success(function(data)
+ {
+
+ // In Timeline view, gapless should stick to the same monitor
+ if ($scope.followSameMonitor == "1") // we are viewing only one monitor
+ {
+ NVRDataModel.debug("Getting next event for same monitor Id ");
+ neighbors.prev = data.event.Event.PrevOfMonitor ? data.event.Event.PrevOfMonitor : "";
+ neighbors.next = data.event.Event.NextOfMonitor ? data.event.Event.NextOfMonitor : "";
+ }
+ else
+ {
+ neighbors.prev = data.event.Event.Prev ? data.event.Event.Prev : "";
+ neighbors.next = data.event.Event.Next ? data.event.Event.Next : "";
+ }
+ NVRDataModel.debug("Neighbor events of " + eid + "are Prev:" +
+ neighbors.prev + " and Next:" + neighbors.next);
+
+ d.resolve(neighbors);
+ return (d.promise);
+ })
+ .error(function(err)
+ {
+ NVRDataModel.log("Error retrieving neighbors" + JSON.stringify(err));
+ d.reject(neighbors);
+ return (d.promise);
+
+ });
+ return (d.promise);
+
+ }
+
+ $scope.zoomImage = function(val)
+ {
+ var zl = parseInt($ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom);
+ if (zl == 1 && val == -1)
+ {
+ NVRDataModel.debug("Already zoomed out max");
+ return;
+ }
+
+ zl += val;
+ NVRDataModel.debug("Zoom level is " + zl);
+ $ionicScrollDelegate.$getByHandle("imgscroll").zoomTo(zl, true);
+
+ };
+
+ //--------------------------------------------------------
+ //Navigate to next/prev event in full screen mode
+ //--------------------------------------------------------
+
+ $scope.onSwipeEvent = function(eid, dirn)
+ {
+ //console.log("HERE");
+ var ld = NVRDataModel.getLogin();
+ if (!ld.canSwipeMonitors) return;
+
+ if ($ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom != 1)
+ {
+ //console.log("Image is zoomed in - not honoring swipe");
+ return;
+ }
+
+ if (ld.useNphZmsForEvents)
+ {
+ NVRDataModel.log("using zms to move ");
+ jumpToEventZms($scope.connKey, dirn);
+ // sendCommand ( dirn==1?'13':'12',$scope.connKey);
+
+ }
+ else
+ {
+ jumpToEvent(eid, dirn);
+ }
+
+ //console.log("JUMPING");
+
+ };
+
+ $scope.jumpToEvent = function(eid, dirn)
+ {
+ // console.log("jumptoevent");
+ var ld = NVRDataModel.getLogin();
+ if (ld.useNphZmsForEvents)
+ {
+ NVRDataModel.log("using zms to move ");
+ jumpToEventZms($scope.connKey, dirn);
+ // sendCommand ( dirn==1?'13':'12',$scope.connKey);
+
+ }
+ else
+ {
+ jumpToEvent(eid, dirn);
+ }
+
+ };
+
+ function jumpToEvent(eid, dirn)
+ {
+ NVRDataModel.log("Event jump called with:" + eid);
+ if (eid == "")
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kNoMoreEvents'),
+ noBackdrop: true,
+ duration: 2000
+ });
+
+ return;
+ }
+
+ var slidein;
+ var slideout;
+ if (dirn == 1)
+ {
+ slideout = "animated slideOutLeft";
+ slidein = "animated slideInRight";
+ }
+ else
+ {
+ slideout = "animated slideOutRight";
+ slidein = "animated slideInLeft";
+ }
+ var element = angular.element(document.getElementById("full-screen-event"));
+ element.addClass(slideout).one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', outWithOld);
+
+ function outWithOld()
+ {
+
+ NVRDataModel.log("ModalCtrl:Stopping network pull...");
+ NVRDataModel.stopNetwork("EventModalCtrl-out with old");
+ $scope.animationInProgress = true;
+ // give digest time for image to swap
+ // 100 should be enough
+ $timeout(function()
+ {
+ element.removeClass(slideout);
+ element.addClass(slidein)
+ .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', inWithNew);
+ prepareModalEvent(eid);
+ }, 200);
+ }
+
+ function inWithNew()
+ {
+ element.removeClass(slidein);
+ $scope.animationInProgress = false;
+ carouselUtils.setStop(false);
+ }
+
+ }
+
+ function humanizeTime(str)
+ {
+ // if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).fromNow();
+ // else
+ // return moment(str).fromNow();
+
+ }
+
+ function jumpToEventZms(connkey, dirn)
+ {
+
+ if ($scope.defaultVideo !== undefined && $scope.defaultVideo != '')
+ {
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kEventNavVidFeeds'),
+ noBackdrop: true,
+ duration: 3000
+ });
+ return;
+
+ }
+ var cmd = dirn == 1 ? '13' : '12';
+ $scope.d_eventId = "...";
+ NVRDataModel.debug("Sending " + cmd + " to " + connkey);
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSwitchingEvents') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+ //console.log("Send command connkey: " + connkey);
+ sendCommand(cmd, connkey)
+ .then(
+ function(success)
+ {
+ //console.log ("jump success " + JSON.stringify(success));
+ $ionicLoading.hide();
+ },
+ function(error)
+ {
+
+ NVRDataModel.debug("Hmm jump error " + JSON.stringify(error));
+ NVRDataModel.stopNetwork("EventModalCtrl-jumptoEventZms error");
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ // console.log ("********* OFFSET FROM JUMPTOEVENTZMS ERROR");
+ $timeout(function()
+ {
+ sendCommand('14', $scope.connKey, '&offset=' + $scope.currentProgress.progress);
+ }, 500);
+ NVRDataModel.debug("so I'm regenerating Connkey to " + $scope.connKey);
+ //$timeout.cancel(eventQueryHandle);
+ // eventQueryHandle = $timeout (function(){checkEvent();}, zm.eventPlaybackQuery);
+ $ionicLoading.hide();
+ });
+ var slidein;
+ var slideout;
+ if (dirn == 1)
+ {
+ slideout = "animated slideOutLeft";
+ slidein = "animated slideInRight";
+ }
+ else
+ {
+ slideout = "animated slideOutRight";
+ slidein = "animated slideInLeft";
+ }
+ var element = angular.element(document.getElementById("full-screen-event"));
+ element.addClass(slideout).one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', outWithOld);
+
+ function outWithOld()
+ {
+
+ $timeout(function()
+ {
+ element.removeClass(slideout);
+ element.addClass(slidein)
+ .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', inWithNew);
+
+ }, 200);
+ }
+
+ function inWithNew()
+ {
+ element.removeClass(slidein);
+
+ }
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeRelativePath(event)
+ {
+ var relativePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+ relativePath = event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return relativePath;
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeBasePath(event)
+ {
+ var basePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+
+ basePath = loginData.url + "/events/" +
+ event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return basePath;
+ }
+
+ //-------------------------------------------------------------------------
+ // Called when rncarousel or video player finished playing event
+ //-------------------------------------------------------------------------
+
+ $scope.playbackFinished = function()
+ {
+ playbackFinished();
+ };
+
+ function playbackFinished()
+ {
+ // currentEvent is updated with the currently playing event in prepareModalEvent()
+ NVRDataModel.log("Playback of event " + currentEvent.Event.Id + " is finished");
+
+ if ($scope.loginData.gapless)
+ {
+
+ neighborEvents(currentEvent.Event.Id)
+ .then(function(success)
+ {
+
+ // lets give a second before gapless transition to the next event
+ $timeout(function()
+ {
+ $scope.nextId = success.next;
+ $scope.prevId = success.prev;
+ NVRDataModel.debug("Gapless move to event " + $scope.nextId);
+ jumpToEvent($scope.nextId, 1);
+ }, 1000);
+ },
+ function(error)
+ {
+ NVRDataModel.debug("Error in neighbor call " +
+ JSON.stringify(error));
+ });
+ }
+ else
+ {
+ NVRDataModel.debug("not going to next event, gapless is off");
+ }
+ }
+
+ //--------------------------------------------------------
+ // Called by openModal as well as jump to event
+ // what it basically does is get a detailed event API
+ // for an event ID and constructs required playback
+ // parameters
+ // Note that openModal is called with the top level event
+ // API. Some parameters are repeated across both
+ //--------------------------------------------------------
+
+ function prepareModalEvent(eid)
+ {
+
+ // Lets get the detailed event API
+ var loginData = NVRDataModel.getLogin();
+ var myurl = loginData.apiurl + '/events/' + eid + ".json";
+ NVRDataModel.log("*** Constructed API for detailed events: " + myurl);
+ $scope.humanizeTime = "...";
+ $scope.mName = "...";
+ $http.get(myurl)
+ .then(function(success)
+ {
+
+ // console.log ("DUCCESS::"+JSON.stringify(success));
+ var event = success.data.event;
+ currentEvent = event;
+
+ event.Event.BasePath = computeBasePath(event);
+ event.Event.relativePath = computeRelativePath(event);
+
+ event.Event.streamingURL = NVRDataModel.getStreamingURL(event.Event.MonitorId);
+ // event.Event.baseURL = NVRDataModel.getBaseURL (event.Event.MonitorId);
+ event.Event.baseURL = loginData.url;
+ event.Event.imageMode = NVRDataModel.getImageMode(event.Event.MonitorId);
+
+ //console.log (JSON.stringify( success));
+ $scope.eventName = event.Event.Name;
+ $scope.eventId = event.Event.Id;
+ $scope.d_eventId = $scope.eventId;
+ $scope.eFramesNum = event.Event.Frames;
+ $scope.eventDur = Math.round(event.Event.Length);
+ $scope.loginData = NVRDataModel.getLogin();
+ $scope.humanizeTime = humanizeTime(event.Event.StartTime);
+ $scope.mName = NVRDataModel.getMonitorName(event.Event.MonitorId);
+ //console.log (">>>>>>>>HUMANIZE " + $scope.humanizeTime);
+
+ //console.log("**** VIDEO STATE IS " + event.Event.DefaultVideo);
+ if (typeof event.Event.DefaultVideo === 'undefined')
+ event.Event.DefaultVideo = "";
+
+ $scope.defaultVideo = event.Event.DefaultVideo;
+
+ //console.log("loginData is " + JSON.stringify($scope.loginData));
+ //console.log("Event ID is " + $scope.eventId);
+ //console.log("video is " + $scope.defaultVideo);
+
+ neighborEvents(event.Event.Id)
+ .then(function(success)
+ {
+ $scope.nextId = success.next;
+ $scope.prevId = success.prev;
+ },
+ function(error)
+ {
+ //console.log(JSON.stringify(error));
+ });
+
+ $scope.nextId = "...";
+ $scope.prevId = "...";
+
+ event.Event.video = {};
+ var videoURL;
+
+ if ((event.Event.imageMode == 'path') || NVRDataModel.getLogin().forceImageModePath)
+ videoURL = event.Event.baseURL + "/events/" + event.Event.relativePath + event.Event.DefaultVideo;
+ else
+ videoURL = event.Event.baseURL + "/index.php?view=view_video&eid=" + event.Event.Id;
+
+ // hack
+ //videoURL = "http://static.videogular.com/assets/videos/videogular.mp4";
+ //videoURL = "http://arjunrc.ddns.net:8888/foo2.mp4";
+ $scope.video_url = videoURL;
+
+ console.log("************** VIDEO IS " + videoURL);
+
+ NVRDataModel.debug("Video url passed to player is: " + videoURL);
+
+ // console.log (">>>>>>>>>>>>>"+loginData.url+"-VS-"+event.Event.baseURL);
+
+ //console.log("************** VIDEO IS " + videoURL);
+
+ $scope.videoObject = {
+ config:
+ {
+ autoPlay: true,
+ responsive: false,
+ nativeControls: false,
+ nativeFullScreen:false,
+
+ playsInline: true,
+ sources: [
+ {
+ src: $sce.trustAsResourceUrl(videoURL),
+ type: "video/mp4"
+ }
+
+ ],
+
+ theme: "lib/videogular-themes-default/videogular.css",
+ cuepoints: {
+ theme: {
+ url:"lib/videogular-cuepoints/cuepoints.css"
+ },
+ points: [],
+ }
+ }
+ };
+
+ // $scope.videoObject = angular.copy(event.Event.video);
+
+ $scope.playbackURL = $scope.loginData.url;
+
+ $scope.videoIsReady = true;
+
+ /* we don't need this for electron
+ if ($rootScope.platformOS == "desktop") {
+ $scope.playbackURL = zm.desktopUrl;
+ } */
+
+ $scope.eventBasePath = event.Event.BasePath;
+ $scope.relativePath = event.Event.relativePath;
+ $rootScope.rand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+
+ $scope.slider_modal_options = {
+ from: 1,
+ to: event.Event.Frames,
+ realtime: true,
+ step: 1,
+ className: "mySliderClass",
+ callback: function(value, released)
+ {
+ //console.log("CALLBACK"+value+released);
+ $ionicScrollDelegate.freezeScroll(!released);
+
+ },
+ //modelLabels:function(val) {return "";},
+ smooth: false,
+ css:
+ {
+ background:
+ {
+ "background-color": "silver"
+ },
+ before:
+ {
+ "background-color": "purple"
+ },
+ default:
+ {
+ "background-color": "white"
+ }, // default value: 1px
+ after:
+ {
+ "background-color": "green"
+ }, // zone after default value
+ pointer:
+ {
+ "background-color": "red"
+ }, // circle pointer
+ range:
+ {
+ "background-color": "red"
+ } // use it if double value
+ },
+ scale: []
+
+ };
+
+ $scope.mycarousel.index = 0;
+ $scope.ionRange.index = 1;
+ $scope.eventSpeed = $scope.event.Event.Length / $scope.event.Event.Frames;
+
+ //console.log("**Resetting range");
+ $scope.slides = [];
+ var i;
+ for (i = 1; i <= event.Event.Frames; i++)
+ {
+ var fname = padToN(i, eventImageDigits) + "-capture.jpg";
+ // console.log ("Building " + fname);
+ $scope.slides.push(
+ {
+ id: i,
+ img: fname
+ });
+ }
+
+ // now get event details to show alarm frames
+
+ //$scope.FrameArray = event.Frame;
+ // $scope.slider_options.scale=[];
+ // $scope.slider_modal_options.scale = [];
+
+ // lets
+ framearray.datasets[0].data = [];
+ for (i = 0; i < event.Frame.length; i++)
+ {
+
+ var ts = moment(event.Frame[i].TimeStamp).format(timeFormat);
+
+ //console.log ("pushing s:" + event.Frame[i].Score+" t:"+ts);
+
+ framearray.datasets[0].data.push(
+ {
+ x: ts,
+ y: event.Frame[i].Score
+ });
+ framearray.labels.push("");
+
+ }
+ $scope.totalEventTime = Math.round(parseFloat(event.Event.Length)) - 1;
+ $scope.currentEventTime = 0;
+
+ // video mode doesn't need this graph - it won't really work
+ if ($scope.defaultVideo == undefined || $scope.defaultVideo == '')
+ {
+ $timeout(function()
+ {
+ drawGraph();
+ }, 500);
+ }
+
+ },
+ function(err)
+ {
+ NVRDataModel.log("Error retrieving detailed frame API " + JSON.stringify(err));
+ NVRDataModel.displayBanner('error', ['could not retrieve frame details', 'please try again']);
+ });
+
+ }
+
+ if (typeof $scope.ionRange !== 'undefined')
+ {
+ $scope.$watch('ionRange.index', function()
+ {
+ //
+ $scope.mycarousel.index = parseInt($scope.ionRange.index) - 1;
+
+ if (carouselUtils.getStop() == true)
+ return;
+
+ //console.log ("***ION RANGE CHANGED TO " + $scope.mycarousel.index);
+ });
+ }
+
+ if (typeof $scope.mycarousel !== 'undefined')
+ {
+ $scope.$watch('mycarousel.index', function()
+ {
+
+ if (currentEvent && $scope.ionRange.index == parseInt(currentEvent.Event.Frames - 1))
+ {
+ playbackFinished();
+ }
+ // end of playback from quick scrub
+ // so ignore gapless
+
+ if ($scope.event && $scope.ionRange.index == parseInt($scope.event.Event.Frames) - 1)
+ {
+ if (!$scope.modal || $scope.modal.isShown() == false)
+ {
+ // console.log("quick scrub playback over");
+ carouselUtils.setStop(true);
+ $scope.ionRange.index = 0;
+ $scope.mycarousel.index = 1;
+ }
+
+ }
+ if (carouselUtils.getStop() == true)
+ return;
+ $scope.ionRange.index = ($scope.mycarousel.index + 1).toString();
+ // console.log ("***IONRANGE RANGE CHANGED TO " + $scope.ionRange.index);
+
+ });
+ }
+
+ function padToN(number, digits)
+ {
+
+ var i;
+ var stringMax = "";
+ var stringLeading = "";
+ for (i = 1; i <= digits; i++)
+ {
+ stringMax = stringMax + "9";
+ if (i != digits) stringLeading = stringLeading + "0";
+ }
+ var numMax = parseInt(stringMax);
+
+ if (number <= numMax)
+ {
+ number = (stringLeading + number).slice(-digits);
+ }
+ //console.log ("PADTON: returning " + number);
+ return number;
+ }
+
+}]);
diff --git a/www/js/EventServer.js b/www/js/EventServer.js
new file mode 100644
index 00000000..b0d0c83f
--- /dev/null
+++ b/www/js/EventServer.js
@@ -0,0 +1,584 @@
+/* jshint -W041 */
+
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console ,PushNotification*/
+
+//--------------------------------------------------------------------------
+// This factory interacts with the ZM Event Server
+// over websockets and is responsible for rendering real time notifications
+//--------------------------------------------------------------------------
+
+angular.module('zmApp.controllers')
+ .factory('EventServer', ['NVRDataModel', '$rootScope', '$websocket', '$ionicPopup', '$timeout', '$q', 'zm', '$ionicPlatform', '$cordovaMedia', '$translate', function(NVRDataModel, $rootScope, $websocket, $ionicPopup, $timeout, $q, zm, $ionicPlatform, $cordovaMedia, $translate)
+ {
+
+ var lastEventServerCheck = Date.now();
+ var ws;
+
+ var localNotificationId = 0;
+ var firstError = true;
+
+ //--------------------------------------------------------------------------
+ // called when the websocket is opened
+ //--------------------------------------------------------------------------
+ function openHandshake()
+ {
+ var loginData = NVRDataModel.getLogin();
+ if (loginData.isUseEventServer == false || loginData.eventServer == "")
+ {
+ NVRDataModel.log("openHandShake: no event server");
+ return;
+ }
+
+ NVRDataModel.log("openHandshake: Websocket open");
+ ws.$emit('auth',
+ {
+ user: loginData.username,
+ password: loginData.password
+ });
+
+ if ($rootScope.apnsToken != '')
+ {
+ var plat = $ionicPlatform.is('ios') ? 'ios' : 'android';
+ var ld = NVRDataModel.getLogin();
+ var pushstate = "enabled";
+ if (ld.disablePush == true)
+ pushstate = "disabled";
+
+ NVRDataModel.debug("openHandShake: state of push is " + pushstate);
+ // let's do this only if disabled. If enabled, I suppose registration
+ // will be called?
+ //if (ld.disablePush)
+ if (1)
+ {
+ //console.log ("HANDSHAKE MESSAGE WITH "+$rootScope.monstring);
+ ws.$emit('push',
+ {
+ type: 'token',
+ platform: plat,
+ token: $rootScope.apnsToken,
+ monlist:$rootScope.monstring,
+ intlist:$rootScope.intstring,
+ state: pushstate
+ });
+ }
+ }
+
+ }
+
+ //--------------------------------------------------------------------------
+ // Called once at app start. Does a lazy definition of websockets open
+ //--------------------------------------------------------------------------
+ function init()
+ {
+
+ $rootScope.isAlarm = 0;
+ $rootScope.alarmCount = "0";
+
+ var d = $q.defer();
+
+ var loginData = NVRDataModel.getLogin();
+
+ //console.log ("INIT GOT " + JSON.stringify(loginData));
+
+ if (loginData.isUseEventServer == false || !loginData.eventServer)
+ {
+ NVRDataModel.log("No Event Server present. Not initializing");
+ d.reject("false");
+ return d.promise;
+ }
+
+ //if (!$rootScope.apnsToken)
+ pushInit();
+
+ if (typeof ws !== 'undefined')
+ {
+ NVRDataModel.debug("Event server already initialized");
+ d.resolve("true");
+ return d.promise;
+ }
+
+ NVRDataModel.log("Initializing Websocket with URL " +
+ loginData.eventServer + " , will connect later...");
+ ws = $websocket.$new(
+ {
+ url: loginData.eventServer,
+ reconnect: true,
+ reconnectInterval: 60000,
+ lazy: true
+ });
+
+ // Transmit auth information to server
+ ws.$on('$open', openHandshake);
+
+ NVRDataModel.debug("Setting up websocket error handler");
+ ws.$on('$error', function(e)
+ {
+
+ // we don't need this check as I changed reconnect interval to 60s
+ //if ((Date.now() - lastEventServerCheck > 30000.0) || firstError)
+ if (1)
+ {
+ NVRDataModel.debug("Websocket Errorhandler called");
+ $timeout(function()
+ {
+ NVRDataModel.displayBanner('error', ['Event Server connection error']);
+ }, 3000); // leave 3 seconds for transitions
+ firstError = false;
+ lastEventServerCheck = Date.now();
+ }
+ //console.log ("VALUE TIME " + lastEventServerCheck);
+ //console.log ("NOW TIME " + Date.now());
+ });
+
+ ws.$on('$close', function()
+ {
+ NVRDataModel.log("Websocket closed");
+
+ });
+
+ // Handles responses back from ZM ES
+
+ ws.$on('$message', function(str)
+ {
+ NVRDataModel.log("Real-time event: " + JSON.stringify(str));
+
+ // Error messages
+ if (str.status != 'Success')
+ {
+ NVRDataModel.log("Event Error: " + JSON.stringify(str));
+
+ if (str.reason == 'APNSDISABLED')
+ {
+ ws.$close();
+ NVRDataModel.displayBanner('error', ['Event Server: APNS disabled'], 2000, 6000);
+ $rootScope.apnsToken = "";
+ }
+
+ }
+
+ if (str.status == 'Success' && (str.event == 'auth'))
+ {
+ if (str.version == undefined)
+ str.version = "0.1";
+ if (NVRDataModel.versionCompare(str.version, zm.minEventServerVersion) == -1)
+ {
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kEventServerVersionTitle'),
+ template: $translate.instant('kEventServerVersionBody1') + " " + str.version + ". " + $translate.instant('kEventServerVersionBody2') +
+ zm.minEventServerVersion,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ }
+
+ }
+
+ if (str.status == 'Success' && str.event == 'alarm') // new events
+ {
+
+ var localNotText;
+ // ZMN specific hack for Event Server
+ if (str.supplementary != 'true')
+ {
+ new Audio('sounds/blop.mp3').play();
+ localNotText = "Latest Alarms: ";
+ $rootScope.isAlarm = 1;
+
+ // Show upto a max of 99 when it comes to display
+ // so aesthetics are maintained
+ if ($rootScope.alarmCount == "99")
+ {
+ $rootScope.alarmCount = "99+";
+ }
+ if ($rootScope.alarmCount != "99+")
+ {
+ $rootScope.alarmCount = (parseInt($rootScope.alarmCount) + 1).toString();
+ }
+
+ }
+ else
+ {
+ NVRDataModel.debug("received supplementary event information over websockets");
+ }
+ var eventsToDisplay = [];
+ var listOfMonitors = [];
+ for (var iter = 0; iter < str.events.length; iter++)
+ {
+ // lets stack the display so they don't overwrite
+ eventsToDisplay.push(str.events[iter].Name + ": latest new alarm (" + str.events[iter].EventId + ")");
+ localNotText = localNotText + str.events[iter].Name + ",";
+ listOfMonitors.push(str.events[iter].MonitorId);
+
+ }
+ localNotText = localNotText.substring(0, localNotText.length - 1);
+
+ // if we are in background, do a local notification, else do an in app display
+ if (!NVRDataModel.isBackground())
+ {
+
+ //emit alarm details - this is when received over websockets
+ $rootScope.$emit('alarm',
+ {
+ message: listOfMonitors
+ });
+
+ if (str.supplementary != 'true')
+ {
+
+ NVRDataModel.debug("App is in foreground, displaying banner");
+ if (eventsToDisplay.length > 0)
+ {
+
+ if (eventsToDisplay.length == 1)
+ {
+ //console.log("Single Display: " + eventsToDisplay[0]);
+ NVRDataModel.displayBanner('alarm', [eventsToDisplay[0]], 5000, 5000);
+ }
+ else
+ {
+ NVRDataModel.displayBanner('alarm', eventsToDisplay,
+ 5000, 5000 * eventsToDisplay.length);
+ }
+
+ }
+ }
+ }
+
+ } //end of success handler
+
+ });
+ d.resolve("true");
+ return (d.promise);
+
+ }
+
+ function disconnect()
+ {
+ NVRDataModel.log("Disconnecting and deleting Event Server socket...");
+
+ if (typeof ws === 'undefined')
+ return;
+
+ ws.$close();
+ ws.$un('open');
+ ws.$un('close');
+ ws.$un('message');
+ ws = undefined;
+
+ }
+
+ //--------------------------------------------------------------------------
+ // Send an arbitrary object to the Event Serve
+ // currently planned to use it for device token
+ // isForce =1 when you need to send the message even
+ // if config says ES is off. This may happen when
+ // you turn off ES and then we need sendMessage to
+ // let ZMES know not to send us messages
+ //--------------------------------------------------------------------------
+ function sendMessage(type, obj, isForce)
+ {
+ var ld = NVRDataModel.getLogin();
+ if (ld.isUseEventServer == false && isForce != 1)
+ {
+ NVRDataModel.debug("Not sending WSS message as event server is off");
+ return;
+ }
+
+ if (typeof ws === 'undefined')
+ {
+ NVRDataModel.debug("Event server not initalized, not sending message");
+ return;
+ }
+
+ if (ws.$status() == ws.$CLOSED)
+ {
+ NVRDataModel.log("Websocket was closed, trying to re-open");
+ ws.$un('$open');
+ //ws.$on ('$open', openHandshake);
+ ws.$open();
+
+ ws.$on('$open', openHandshake, function()
+ {
+
+ //console.log(" sending " + type + " " +
+ // JSON.stringify(obj));
+ //console.log("sending " + type + " " + JSON.stringify(obj));
+ ws.$emit(type, obj);
+
+ ws.$un('$open');
+ ws.$on('$open', openHandshake);
+
+ });
+
+ }
+ else
+ {
+ ws.$emit(type, obj);
+ // console.log("sending " + type + " " + JSON.stringify(obj));
+ }
+
+ }
+
+ //--------------------------------------------------------------------------
+ // Called each time we resume
+ //--------------------------------------------------------------------------
+ function refresh()
+ {
+ var loginData = NVRDataModel.getLogin();
+
+ if ((!loginData.eventServer) || (loginData.isUseEventServer == false))
+ {
+ NVRDataModel.log("No Event Server configured, skipping refresh");
+
+ // Let's also make sure that if the socket was open
+ // we close it - this may happen if you disable it after using it
+
+ if (typeof ws !== 'undefined')
+ {
+ if (ws.$status() != ws.$CLOSED)
+ {
+ NVRDataModel.debug("Closing open websocket as event server was disabled");
+ ws.$close();
+ }
+ }
+
+ return;
+ }
+
+ if (typeof ws === 'undefined')
+ {
+ NVRDataModel.debug("Calling websocket init");
+ init();
+ }
+
+ // refresh is called when
+ // The following situations will close the socket
+ // a) In iOS the client went to background -- we should reconnect
+ // b) The Event Server died
+ // c) The network died
+ // Seems to me in all cases we should give re-open a shot
+
+ if (ws.$status() == ws.$CLOSED)
+ {
+ NVRDataModel.log("Websocket was closed, trying to re-open");
+ ws.$open();
+ }
+
+ }
+
+ function pushInit()
+ {
+ NVRDataModel.log(">>>Setting up push registration");
+ var push;
+ var mediasrc;
+ var media;
+ var ld = NVRDataModel.getLogin();
+
+ var plat = $ionicPlatform.is('ios') ? 'ios' : 'android';
+
+ if ($rootScope.platformOS == 'desktop')
+ {
+ NVRDataModel.log("Desktop instance, not setting up push. Websockets only, I hope");
+ return;
+ }
+
+ if (plat == 'ios')
+ {
+ mediasrc = "sounds/blop.mp3";
+ push = PushNotification.init(
+
+ {
+ "ios":
+ {
+ "alert": true,
+ "badge": true,
+ "sound": ld.soundOnPush,
+ "clearBadge": true
+ }
+ }
+
+ );
+
+ }
+ else
+ {
+ mediasrc = "/android_asset/www/sounds/blop.mp3";
+ var android_media_file = "blop";
+
+ push = PushNotification.init(
+
+ {
+ "android":
+ {
+ "senderID": zm.gcmSenderId,
+ "icon": "ic_stat_notification",
+ sound: ld.soundOnPush,
+ vibrate: ld.vibrateOnPush
+ //"sound": android_media_file
+ }
+ }
+
+ );
+
+ }
+
+ // console.log("*********** MEDIA BLOG IS " + mediasrc);
+ media = $cordovaMedia.newMedia(mediasrc);
+
+ push.on('registration', function(data)
+ {
+ NVRDataModel.debug("Push Notification registration ID received: " + JSON.stringify(data));
+ $rootScope.apnsToken = data.registrationId;
+
+ var plat = $ionicPlatform.is('ios') ? 'ios' : 'android';
+ var ld = NVRDataModel.getLogin();
+ var pushstate = "enabled";
+ if (ld.disablePush == true)
+ pushstate = "disabled";
+
+ // now at this stage, if this is a first registration
+ // zmeventserver will have no record of this token
+ // so we need to make sure we send it a legit list of
+ // monitors otherwise users will get notifications for monitors
+ // their login is not supposed to see. Refer #391
+
+ var monstring='';
+ var intstring='';
+ NVRDataModel.getMonitors()
+ .then (function(succ) {
+ var mon = succ;
+ for (var i = 0; i < mon.length; i++) {
+ monstring = monstring + mon[i].Monitor.Id + ",";
+ intstring = intstring + '0,';
+ }
+ if (monstring.charAt(monstring.length - 1) == ',')
+ monstring = monstring.substr(0, monstring.length - 1);
+
+ if (intstring.charAt(intstring.length - 1) == ',')
+ intstring = intstring.substr(0, intstring.length - 1);
+
+ //console.log ("WUTPUT SENDING REG WITH "+monstring);
+
+ $rootScope.monstring = monstring;
+ $rootScope.intstring = intstring;
+
+ sendMessage('push',
+ {
+ type: 'token',
+ platform: plat,
+ token: $rootScope.apnsToken,
+ monlist: monstring,
+ intlist: intstring,
+ state: pushstate
+ }, 1);
+
+ },
+ function (err)
+ {
+ NVRDataModel.log ("Could not get monitors, can't send push reg");
+ });
+
+ });
+
+ push.on('notification', function(data)
+ {
+
+ NVRDataModel.debug("received push notification");
+
+ var ld = NVRDataModel.getLogin();
+ if (ld.isUseEventServer == false)
+ {
+ NVRDataModel.debug("received push notification, but event server disabled. Not acting on it");
+ return;
+ }
+
+ if (data.additionalData.foreground == false)
+ {
+ // This means push notification tap in background
+
+ NVRDataModel.debug("*** PUSH NOTFN.>>>>" + JSON.stringify(data));
+
+ // set tappedMid to monitor
+ //*** PUSH DATA>>>>{"sound":"blop","message":"Alarms: Basement (2854) ","additionalData":{"mid":"2","coldstart":false,"collapse_key":"do_not_collapse","foreground":false}}
+
+ NVRDataModel.debug("Notification Tapped");
+ $rootScope.alarmCount = "0";
+ $rootScope.isAlarm = 0;
+ $rootScope.tappedNotification = 1;
+ var mid = data.additionalData.mid;
+
+ // if Multiple mids, take the first one
+ var mi = mid.indexOf(',');
+ if (mi > 0)
+ {
+ mid = mid.slice(0, mi);
+ }
+ mid = parseInt(mid);
+
+ $rootScope.tappedMid = mid;
+ NVRDataModel.log("Push notification: Tapped Monitor taken as:" + $rootScope.tappedMid);
+
+ if ($rootScope.platformOS == 'ios')
+ {
+
+ NVRDataModel.debug("iOS only: clearing background push");
+ push.finish(function()
+ {
+ NVRDataModel.debug("processing of push data is finished");
+ });
+ }
+
+ }
+ else
+ {
+
+ // this flag honors the HW mute button. Go figure
+ // http://ilee.co.uk/phonegap-plays-sound-on-mute/
+ if (ld.soundOnPush)
+ {
+ media.play(
+ {
+ playAudioWhenScreenIsLocked: false
+ });
+ }
+
+ var str = data.message;
+ // console.log ("***STRING: " + str + " " +str.status);
+ var eventsToDisplay = [];
+
+ NVRDataModel.displayBanner('alarm', [str], 0, 5000 * eventsToDisplay.length);
+
+ $rootScope.isAlarm = 1;
+
+ // Show upto a max of 99 when it comes to display
+ // so aesthetics are maintained
+ if ($rootScope.alarmCount == "99")
+ {
+ $rootScope.alarmCount = "99+";
+ }
+ if ($rootScope.alarmCount != "99+")
+ {
+ $rootScope.alarmCount = (parseInt($rootScope.alarmCount) + 1).toString();
+ }
+ }
+ });
+
+ push.on('error', function(e)
+ {
+ NVRDataModel.debug("Push error: " + JSON.stringify(e));
+ // console.log("************* PUSH ERROR ******************");
+ });
+ }
+
+ return {
+ refresh: refresh,
+ init: init,
+ sendMessage: sendMessage,
+ pushInit: pushInit,
+ disconnect: disconnect
+
+ };
+
+ }]);
diff --git a/www/js/EventServerSettingsCtrl.js b/www/js/EventServerSettingsCtrl.js
new file mode 100644
index 00000000..efa868ea
--- /dev/null
+++ b/www/js/EventServerSettingsCtrl.js
@@ -0,0 +1,360 @@
+ /* jshint -W041 */
+ /* jslint browser: true*/
+ /* global cordova,StatusBar,angular,console */
+
+ angular.module('zmApp.controllers').controller('zmApp.EventServerSettingsCtrl', ['$scope', '$ionicSideMenuDelegate', 'zm', '$stateParams', 'EventServer', '$ionicHistory', '$rootScope', '$state', 'message', 'NVRDataModel', '$ionicPlatform', '$ionicPopup', '$timeout', '$translate', function($scope, $ionicSideMenuDelegate, zm, $stateParams, EventServer, $ionicHistory, $rootScope, $state, message, NVRDataModel, $ionicPlatform, $ionicPopup, $timeout, $translate)
+ {
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ // we need this to dynamically get title
+ // name as ion-view is set in stone and
+ // we don't get title till beforeEnter
+ // which is odd - I'd expect beforeEnter to load
+ // before View is loaded
+ $scope.getTitle = function()
+ {
+ return $scope.loginData.serverName;
+ };
+
+ //----------------------------------------------------------------
+ // Save anyway when you exit
+ //----------------------------------------------------------------
+
+ $scope.$on('$ionicView.beforeLeave', function()
+ {
+ saveItems();
+
+ });
+
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+
+ $scope.loginData = NVRDataModel.getLogin();
+ //console.log ("Event server - before Enter, loginData is " + JSON.stringify($scope.loginData));
+ $scope.defScreen = $scope.loginData.onTapScreen;
+
+ if ($scope.loginData.eventServer == "")
+ {
+ $scope.loginData.eventServer = "wss://" + extractDomain($scope.loginData.url) + ":9000";
+ }
+
+ res = $scope.loginData.eventServerMonitors.split(",");
+ minterval = $scope.loginData.eventServerInterval.split(",");
+
+ var monchecked = false;
+ for (var i = 0; i < $scope.monitors.length; i++)
+ {
+
+ if (!isEnabled($scope.monitors[i].Monitor.Id))
+ {
+ // if the filter list has IDs and this is not part of it, uncheck it
+ $scope.monitors[i].Monitor.isChecked = false;
+ //console.log("Marking false");
+ $scope.monitors[i].Monitor.reportingInterval = 0;
+ }
+ else
+ {
+ // console.log("Marking true");
+ $scope.monitors[i].Monitor.isChecked = true;
+ $scope.monitors[i].Monitor.reportingInterval = getInterval($scope.monitors[i].Monitor.Id);
+ monchecked = true;
+ }
+
+ }
+
+ // now if none are checked, assume it means all checked. This is related to the
+ // fact that ES will start sending all monitors, even ones you don't have access to
+ if (!monchecked)
+ {
+ NVRDataModel.debug ("Enabling all monitors for event server");
+ for (var j = 0; j < $scope.monitors.length; j++)
+ {
+ $scope.monitors[i].Monitor.isChecked = true;
+ $scope.monitors[i].Monitor.reportingInterval = 0;
+ }
+
+ }
+ });
+
+ //--------------------------------------------------
+ // notification tap action
+ //--------------------------------------------------
+
+ $scope.selectScreen = function()
+ {
+
+ var ld = NVRDataModel.getLogin();
+
+ $scope.myopt = {
+ selectedState: ld.onTapScreen
+ };
+
+ var options = '<ion-radio-fix ng-model="myopt.selectedState" ng-value="\'' + $translate.instant('kTapEvents') + '\'">' + $translate.instant('kTapEvents') + '</ion-radio-fix>';
+
+ options += '<ion-radio-fix ng-model="myopt.selectedState" ng-value="\'' + $translate.instant('kTapMontage') + '\'">' + $translate.instant('kTapMontage') + '</ion-radio-fix>';
+ options += '<ion-radio-fix ng-model="myopt.selectedState" ng-value="\'' + $translate.instant('kTapLiveMonitor') + '\'">' + $translate.instant('kTapLiveMonitor') + '</ion-radio-fix>';
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ scope: $scope,
+ template: options,
+
+ title: 'View to navigate to:',
+ subTitle: 'currently set to: ' + ld.onTapScreen,
+ buttons: [
+ {
+ text: $translate.instant('kButtonCancel'),
+
+ },
+ {
+ text: $translate.instant('kButtonOk'),
+ onTap: function(e)
+ {
+
+ ld.onTapScreen = $scope.myopt.selectedState;
+ NVRDataModel.log("Setting new onTap State:" + ld.onTapScreen);
+ NVRDataModel.setLogin(ld);
+ $scope.defScreen = $scope.myopt.selectedState;
+ $scope.loginData = ld;
+
+ }
+ }]
+ });
+
+ };
+
+ //----------------------------------------------------------------
+ // Accordion list show/hide
+ //----------------------------------------------------------------
+
+ $scope.toggleGroup = function(group)
+ {
+ if ($scope.isGroupShown(group))
+ {
+ $scope.shownGroup = null;
+ }
+ else
+ {
+ $scope.shownGroup = group;
+ }
+ };
+ $scope.isGroupShown = function(group)
+ {
+ return $scope.shownGroup === group;
+ };
+
+ $scope.saveItems = function()
+ {
+ saveItems();
+ };
+
+ //----------------------------------------------------------------
+ // Saves ES data
+ //----------------------------------------------------------------
+
+ function saveItems()
+ {
+ NVRDataModel.debug("Saving Event Server data");
+ var monstring = "";
+ var intervalstring = "";
+ var plat = $ionicPlatform.is('ios') ? 'ios' : 'android';
+ for (var i = 0; i < $scope.monitors.length; i++)
+ {
+ if (isNaN($scope.monitors[i].Monitor.reportingInterval))
+ {
+ $scope.monitors[i].Monitor.reportingInterval = 0;
+ }
+ if ($scope.monitors[i].Monitor.isChecked)
+ {
+ monstring = monstring + $scope.monitors[i].Monitor.Id + ",";
+ var tint = parseInt($scope.monitors[i].Monitor.reportingInterval);
+ if (isNaN(tint)) tint = 0;
+ intervalstring = intervalstring + tint + ",";
+ }
+
+ }
+
+ if (monstring.charAt(monstring.length - 1) == ',')
+ monstring = monstring.substr(0, monstring.length - 1);
+
+ if (intervalstring.charAt(intervalstring.length - 1) == ',')
+ intervalstring = intervalstring.substr(0, intervalstring.length - 1);
+
+ $scope.loginData.eventServerMonitors = monstring;
+ $scope.loginData.eventServerInterval = intervalstring;
+
+ //console.log ("SAVED: " + JSON.stringify($scope.loginData));
+ NVRDataModel.setLogin($scope.loginData);
+
+ var pushstate = "enabled";
+ if ($scope.loginData.disablePush == true || $scope.loginData.isUseEventServer == false)
+ pushstate = "disabled";
+
+ if ($scope.loginData.isUseEventServer == true)
+ {
+ EventServer.init()
+ .then(function(data)
+ {
+ // console.log("Sending control filter");
+ NVRDataModel.debug("Sending Control message 'filter' with monlist=" + monstring + " and interval=" + intervalstring);
+ EventServer.sendMessage("control",
+ {
+ type: 'filter',
+ monlist: monstring,
+ intlist: intervalstring
+ }, 1);
+
+ if ($rootScope.apnsToken != "")
+ // if its defined then this is post init work
+ // so lets transmit state here
+
+ {
+ // we need to disable the token
+ NVRDataModel.debug("Sending token state " + pushstate);
+ EventServer.sendMessage('push',
+ {
+ type: 'token',
+ platform: plat,
+ token: $rootScope.apnsToken,
+ state: pushstate
+ }, 1);
+
+ }
+
+ });
+
+ }
+ else
+ {
+ if ($rootScope.apnsToken != "")
+ // if its defined then this is post init work
+ // so lets transmit state here
+
+ {
+ // we need to disable the token
+ NVRDataModel.debug("Sending token state " + pushstate);
+ EventServer.sendMessage('push',
+ {
+ type: 'token',
+ platform: plat,
+ token: $rootScope.apnsToken,
+ state: pushstate
+ }, 1);
+
+ }
+ // Give the above some time to transmit
+
+ EventServer.disconnect();
+
+ }
+
+ NVRDataModel.displayBanner('info', ['settings saved']);
+ }
+
+ //----------------------------------------------------------------
+ // returns domain name in string -
+ // http://stackoverflow.com/questions/8498592/extract-root-domain-name-from-string
+ //----------------------------------------------------------------
+ function extractDomain(url)
+ {
+ var domain;
+ //find & remove protocol (http, ftp, etc.) and get domain
+ if (url.indexOf("://") > -1)
+ {
+ domain = url.split('/')[2];
+ }
+ else
+ {
+ domain = url.split('/')[0];
+ }
+
+ //find & remove port number
+ domain = domain.split(':')[0];
+
+ return domain;
+ }
+
+ //----------------------------------------------------------------
+ // returns reporting interval for monitor ID
+ //----------------------------------------------------------------
+ function getInterval(id)
+ {
+ // means no interval, should only happen one time
+ // till we save
+ if ($scope.loginData.eventServerInterval == "")
+ return 0;
+ var retval = 0;
+ for (var i = 0; i < res.length; i++)
+ {
+ if (res[i] == id)
+ {
+ retval = parseInt(minterval[i]);
+ break;
+ }
+ }
+ return retval;
+ }
+
+ //----------------------------------------------------------------
+ // Returns true/false if monitor ID is in event monitor list
+ //----------------------------------------------------------------
+ function isEnabled(id)
+ {
+ if ($scope.loginData.eventServerMonitors == "")
+ return true;
+
+ var isThere = false;
+ for (var i = 0; i < res.length; i++)
+ {
+ if (res[i] == id)
+ {
+ isThere = true;
+ //console.log("isRes found: " + id);
+ break;
+ }
+ }
+ return isThere;
+ }
+
+ //-------------------------------------------------------------------------
+ // Controller Main
+ //------------------------------------------------------------------------
+ $scope.monitors = [];
+ $scope.monitors = message;
+ var res, minterval;
+
+ }]);
diff --git a/www/js/EventsGraphsCtrl.js b/www/js/EventsGraphsCtrl.js
new file mode 100644
index 00000000..5f21b09b
--- /dev/null
+++ b/www/js/EventsGraphsCtrl.js
@@ -0,0 +1,250 @@
+/* jshint -W041 */
+/* jshint -W083 */
+/*This is for the loop closure I am using in line 143 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,moment */
+
+// This controller generates a graph for events
+// the main function is generateChart. I call generate chart with required parameters
+// from the template file
+
+angular.module('zmApp.controllers').controller('zmApp.EventsGraphsCtrl', ['$ionicPlatform', '$scope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$rootScope', '$http', '$ionicHistory', '$state', function($ionicPlatform, $scope, zm, NVRDataModel, $ionicSideMenuDelegate, $rootScope, $http, $ionicHistory, $state)
+{
+ //console.log("Inside Graphs controller");
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.$on('$ionicView.loaded', function()
+ {
+ // console.log("**VIEW ** Graph Ctrl Loaded");
+ });
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ // console.log("**VIEW ** EventsGraphs Ctrl Entered");
+ NVRDataModel.setAwake(false);
+ });
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ // console.log("**VIEW ** Graph Ctrl Left");
+ });
+
+ $scope.$on('$ionicView.unloaded', function()
+ {
+ // console.log("**VIEW ** Graph Ctrl Unloaded");
+ });
+
+ //-------------------------------------------------
+ // Controller main
+ //-------------------------------------------------
+
+ // $scope.chart = "";
+ $scope.navTitle = 'Tab Page';
+ // $scope.chart="";
+ $scope.leftButtons = [
+ {
+ type: 'button-icon icon ion-navicon',
+ tap: function(e)
+ {
+ $scope.toggleMenu();
+ }
+ }];
+
+ var container = angular.element(document.getElementById('visualization'));
+ //console.log(JSON.stringify(container));
+ var data = [
+ {
+ id: 1,
+ content: 'item 1',
+ start: '2013-04-20'
+ },
+ {
+ id: 2,
+ content: 'item 2',
+ start: '2013-04-14'
+ },
+ {
+ id: 3,
+ content: 'item 3',
+ start: '2013-04-18'
+ },
+ {
+ id: 4,
+ content: 'item 4',
+ start: '2013-04-16',
+ end: '2013-04-19'
+ },
+ {
+ id: 5,
+ content: 'item 5',
+ start: '2013-04-25'
+ },
+ {
+ id: 6,
+ content: 'item 6',
+ start: '2013-04-27'
+ }];
+ var options = {};
+ //var timeline = new vis.Timeline(container[0], data, options);
+
+ // -------------------------------------------------
+ // Called when user taps on a bar
+ //---------------------------------------------------
+ $scope.handleChartClick = function(event)
+ {
+
+ //console.log(JSON.stringify($scope.chartwithbars.getBarsAtEvent(event)));
+ //console.log(angular.element[0].getContext('2d'));
+ //console.log (JSON.stringify( $scope.chart));
+
+ };
+
+ //-------------------------------------------------
+ // Generates a bar graph with data provided
+ //-------------------------------------------------
+ $scope.generateTCChart = function(id, chartTitle, hrs)
+ {
+ var monitors = [];
+ var dateRange = "";
+ var startDate = "";
+ var endDate = "";
+
+ $scope.chart = {
+ barHeight: "",
+ data: "",
+ options: ""
+
+ };
+
+ $scope.chart.barHeight = $rootScope.devHeight;
+
+ if (hrs)
+ {
+ // Apply a time based filter if I am not watching all events
+ var cur = moment();
+ endDate = cur.format("YYYY-MM-DD " + NVRDataModel.getTimeFormat());
+ startDate = cur.subtract(hrs, 'hours').format("YYYY-MM-DD " + NVRDataModel.getTimeFormat());
+ //console.log("Start and End " + startDate + "==" + endDate);
+ NVRDataModel.log("Generating graph for " + startDate + " to " + endDate);
+
+ }
+
+ var loginData = NVRDataModel.getLogin();
+ //$scope.chart.data = {};
+ $scope.chart.data = {
+ labels: [],
+ datasets: [
+ {
+ label: '',
+ fillColor: zm.graphFillColor,
+ strokeColor: zm.graphStrokeColor,
+ highlightFill: zm.graphHighlightFill,
+ data: []
+ }, ]
+ };
+
+ NVRDataModel.getMonitors(0).then(function(data)
+ {
+ monitors = data;
+ var adjustedHeight = monitors.length * 30;
+ if (adjustedHeight > $rootScope.devHeight)
+ {
+
+ $scope.chart.barHeight = adjustedHeight;
+ //console.log("********* BAR HEIGHT TO " + $scope.chart.barHeight);
+ }
+
+ for (var i = 0; i < monitors.length; i++)
+ {
+ (function(j)
+ { // loop closure - http is async, so success returns after i goes out of scope
+ // so we need to bind j to i when http returns so its not out of scope. Gak.
+ // I much prefer the old days of passing context data from request to response
+
+ $scope.chart.data.labels.push(monitors[j].Monitor.Name);
+
+ //$scope.chartObject[id].data.push([monitors[j].Monitor.Name,'0','color:#76A7FA','0']);
+ // $scope.chartObject.data[j+1]=([monitors[j].Monitor.Name,'100','color:#76A7FA','0']);
+
+ var dateString = "";
+ if (hrs)
+ {
+ dateString = "/StartTime >=:" + startDate + "/EndTime <=:" + endDate;
+ }
+ var url = loginData.apiurl +
+ "/events/index/MonitorId:" + monitors[j].Monitor.Id + dateString +
+ ".json?page=1";
+ // console.log("Monitor event URL:" + url);
+ NVRDataModel.log("EventGraph: composed url is " + url);
+ $http.get(url /*,{timeout:15000}*/ )
+ .success(function(data)
+ {
+ NVRDataModel.debug("Event count for monitor" +
+ monitors[j].Monitor.Id + " is " + data.pagination.count);
+ $scope.chart.data.datasets[0].data[j] = data.pagination.count;
+ })
+ .error(function(data)
+ {
+ // ideally I should be treating it as an error
+ // but what I am really doing now is treating it like no events
+ // works but TBD: make this into a proper error handler
+ $scope.chart.data.datasets[0].data[j] = 0;
+ NVRDataModel.log("Error retrieving events for graph " + JSON.stringify(data), "error");
+ });
+ })(i); // j
+ } //for
+ });
+
+ $scope.chart.options = {
+
+ responsive: true,
+ scaleBeginAtZero: true,
+ scaleShowGridLines: false,
+ scaleGridLineColor: "rgba(0,0,0,.05)",
+ scaleGridLineWidth: 1,
+ barShowStroke: true,
+ barStrokeWidth: 2,
+ barValueSpacing: 5,
+ barDatasetSpacing: 1,
+ showTooltip: true,
+
+ //String - A legend template
+ // legendTemplate : '<ul class="tc-chart-js-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'
+ };
+ }; //generateTCChart
+}]);
diff --git a/www/js/EventsModalGraphCtrl.js b/www/js/EventsModalGraphCtrl.js
new file mode 100644
index 00000000..fc9a9c42
--- /dev/null
+++ b/www/js/EventsModalGraphCtrl.js
@@ -0,0 +1,409 @@
+// Common Controller for the montage view
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global saveAs, cordova,StatusBar,angular,console,ionic, moment, vis , Chart, DJS*/
+
+angular.module('zmApp.controllers').controller('EventsModalGraphCtrl', ['$scope', '$rootScope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$q', '$sce', 'carouselUtils', '$ionicPopup', '$translate', function($scope, $rootScope, zm, NVRDataModel, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $q, $sce, carouselUtils, $ionicPopup, $translate)
+{
+
+ var Graph2d;
+ var tcGraph;
+ var items;
+ var groups;
+ var eventImageDigits = 5;
+ var cv;
+ var ctx;
+ //var options;
+ //var data;
+ var onlyalarm_data;
+ var current_data;
+ var current_options;
+ var btype;
+ var data, options;
+
+ $scope.$on('modal.shown', function(e, m)
+ {
+
+ if (m.id != 'modalgraph')
+ return;
+
+ //console.log ("INSIDE MODAL GRAPH>>>>>>>>>>>>>>>>>");
+ data = {
+ labels: ["January", "February", "March", "April", "May", "June", "July"],
+ datasets: [
+ {
+ label: "My First dataset",
+ fillColor: "rgba(220,220,220,0.5)",
+ strokeColor: "rgba(220,220,220,0.8)",
+ highlightFill: "rgba(220,220,220,0.75)",
+ highlightStroke: "rgba(220,220,220,1)",
+ data: [65, 59, 80, 81, 56, 55, 40]
+ },
+ {
+ label: "My Second dataset",
+ fillColor: "rgba(151,187,205,0.5)",
+ strokeColor: "rgba(151,187,205,0.8)",
+ highlightFill: "rgba(151,187,205,0.75)",
+ highlightStroke: "rgba(151,187,205,1)",
+ data: [28, 48, 40, 19, 86, 27, 90]
+ }]
+ };
+
+ options = {
+
+ scales:
+ {
+ yAxes: [
+ {
+ ticks:
+ {
+ // beginAtZero:true,
+ min: -1,
+ },
+ }],
+ xAxes: [
+ {
+ display: false
+ }]
+ },
+
+ responsive: true,
+ scaleBeginAtZero: true,
+ scaleShowGridLines: true,
+ scaleGridLineColor: "rgba(0,0,0,.05)",
+ scaleGridLineWidth: 1,
+
+ hover:
+ {
+ mode: 'single',
+ onHover: function(obj)
+ {
+ if (obj.length > 0)
+ tapOrHover(obj[0]._index);
+ }
+ },
+
+ //String - A legend template
+ legendTemplate: '<ul class="tc-chart-js-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'
+ };
+
+ cv = document.getElementById("eventchart");
+ ctx = cv.getContext("2d");
+ $timeout(function()
+ {
+ var tcGraph2 = new Chart(ctx,
+ {
+ type: 'bar',
+ data: data,
+ options: options
+ });
+ });
+ });
+
+ //-------------------------------------------------------
+ // we use this to reload the connkey if authkey changed
+ //------------------------------------------------------
+
+ $rootScope.$on("auth-success", function()
+ {
+
+ NVRDataModel.debug("EventModalCtrl: Re-login detected, resetting everything & re-generating connkey");
+
+ });
+
+ //-------------------------------------------------------
+ // I was kidding, this is where it really is drawn
+ // scout's promise
+ //------------------------------------------------------
+
+ function drawGraphTC(event)
+ {
+
+ $scope.eid = event.event.Event.Id;
+
+ $scope.alarm_images = [];
+
+ /*data = {
+ labels: [],
+ datasets: [
+ {
+ label: 'Score',
+ fill:true,
+ borderJoinStyle: 'miter',
+ pointBorderColor: "rgba(220,220,220,1)",
+ pointBackgroundColor: "#e74c3c",
+ backgroundColor: 'rgba(129, 207, 224, 1.0)',
+ pointHoverRadius: 5,
+ pointHoverBackgroundColor: "#40d47e",
+ pointHoverBorderWidth: 2,
+ tension: 0.1,
+ borderColor: 'rgba(129, 207, 224, 1.0)',
+ hoverBackgroundColor: 'rgba(248, 148, 6,1.0)',
+ hoverBorderColor: 'rgba(248, 148, 6,1.0)',
+ data: [],
+ frames: []
+ },
+
+ ]
+ };*/
+
+ data = {
+ labels: [],
+ datasets: [
+ {
+ label: $translate.instant('kScore'),
+ fill: true,
+ backgroundColor: 'rgba(129, 207, 224, 1.0)',
+ borderColor: 'rgb(92, 147, 159)',
+ borderCapStyle: 'butt',
+ borderJoinStyle: 'miter',
+ pointBorderColor: "rgba(220,220,220,1)",
+ pointBackgroundColor: "#e74c3c",
+
+ pointHoverRadius: 10,
+ pointHoverBackgroundColor: "#f39c12",
+ pointHoverBorderWidth: 1,
+ tension: 0.1,
+
+ data: [],
+ frames: []
+ },
+
+ ]
+ };
+
+ onlyalarm_data = {
+ labels: [],
+ datasets: [
+ {
+ label: $translate.instant('kScore'),
+ backgroundColor: 'rgba(129, 207, 224, 1.0)',
+ borderColor: 'rgba(129, 207, 224, 1.0)',
+ hoverBackgroundColor: 'rgba(248, 148, 6,1.0)',
+ hoverBorderColor: 'rgba(248, 148, 6,1.0)',
+ data: [],
+ frames: []
+ },
+
+ ]
+ };
+
+ // Chart.js Options
+ options = {
+
+ scales:
+ {
+ yAxes: [
+ {
+ ticks:
+ {
+ // beginAtZero:true,
+ min: -1,
+ },
+ }],
+ xAxes: [
+ {
+ display: false
+ }]
+ },
+
+ responsive: true,
+ scaleBeginAtZero: true,
+ scaleShowGridLines: true,
+ scaleGridLineColor: "rgba(0,0,0,.05)",
+ scaleGridLineWidth: 1,
+
+ hover:
+ {
+ mode: 'single',
+ onHover: function(obj)
+ {
+ if (obj.length > 0)
+ tapOrHover(obj[0]._index);
+ }
+ },
+
+ //String - A legend template
+ legendTemplate: '<ul class="tc-chart-js-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'
+ };
+
+ $scope.graphWidth = event.event.Frame.length * 10;
+ if ($scope.graphWidth < $rootScope.devWidth)
+ $scope.graphWidth = $rootScope.devWidth;
+
+ // NVRDataModel.log ("Changing graph width to " + $scope.graphWidth);
+
+ for (var i = 0; i < event.event.Frame.length; i++)
+ {
+
+ data.labels.push(event.event.Frame[i].TimeStamp);
+ //data.labels.push(' ');
+ data.datasets[0].data.push(event.event.Frame[i].Score);
+ data.datasets[0].frames.push(
+ {
+ x: event.event.Frame[i].TimeStamp,
+ y: event.event.Frame[i].Score,
+ eid: event.event.Event.Id,
+ fid: event.event.Frame[i].FrameId,
+ //group:i,
+ relativePath: computeRelativePath(event.event),
+ score: event.event.Frame[i].Score,
+ fname: padToN(event.event.Frame[i].FrameId, eventImageDigits) + "-capture.jpg",
+
+ });
+
+ if (event.event.Frame[i].Type == "Alarm")
+ {
+
+ onlyalarm_data.labels.push(event.event.Frame[i].TimeStamp);
+ //data.labels.push(' ');
+ onlyalarm_data.datasets[0].data.push(event.event.Frame[i].Score);
+ onlyalarm_data.datasets[0].frames.push(
+ {
+ x: event.event.Frame[i].TimeStamp,
+ y: event.event.Frame[i].Score,
+ eid: event.event.Event.Id,
+ fid: event.event.Frame[i].FrameId,
+ //group:i,
+ relativePath: computeRelativePath(event.event),
+ score: event.event.Frame[i].Score,
+ fname: padToN(event.event.Frame[i].FrameId, eventImageDigits) + "-capture.jpg",
+
+ });
+ }
+
+ }
+
+ $scope.dataReady = true;
+
+ cv = document.getElementById("tcchart");
+ ctx = cv.getContext("2d");
+
+ if (NVRDataModel.getLogin().timelineModalGraphType == 'all')
+ {
+ btype = 'line';
+ current_data = data;
+ }
+ else
+ {
+ btype = 'bar';
+ current_data = onlyalarm_data;
+ }
+ $timeout(function()
+ {
+ tcGraph = new Chart(ctx,
+ {
+ type: btype,
+ data: current_data,
+ options: options
+ });
+ });
+
+ cv.onclick = function(e)
+ {
+ var b = tcGraph.getElementAtEvent(e);
+ if (b.length > 0)
+ {
+ tapOrHover(b[0]._index);
+ }
+ };
+ }
+
+ function tapOrHover(ndx)
+ {
+
+ $timeout(function()
+ {
+
+ //console.log ("You tapped " + ndx);
+ $scope.alarm_images = [];
+ $scope.playbackURL = NVRDataModel.getLogin().url;
+ var items = current_data.datasets[0].frames[ndx];
+ $scope.alarm_images.push(
+ {
+ relativePath: items.relativePath,
+ fid: items.fid,
+ fname: items.fname,
+ score: items.score,
+ time: moment(items.x).format("MMM D," + NVRDataModel.getTimeFormatSec()),
+ eid: items.eid
+ });
+ });
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeRelativePath(event)
+ {
+ var relativePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+ relativePath = event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return relativePath;
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeBasePath(event)
+ {
+ var basePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+
+ basePath = loginData.url + "/events/" +
+ event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return basePath;
+ }
+
+ function padToN(number, digits)
+ {
+
+ var i;
+ var stringMax = "";
+ var stringLeading = "";
+ for (i = 1; i <= digits; i++)
+ {
+ stringMax = stringMax + "9";
+ if (i != digits) stringLeading = stringLeading + "0";
+ }
+ var numMax = parseInt(stringMax);
+
+ if (number <= numMax)
+ {
+ number = (stringLeading + number).slice(-digits);
+ }
+ //console.log ("PADTON: returning " + number);
+ return number;
+ }
+
+}]);
diff --git a/www/js/FirstUseCtrl.js b/www/js/FirstUseCtrl.js
new file mode 100644
index 00000000..1dc6c514
--- /dev/null
+++ b/www/js/FirstUseCtrl.js
@@ -0,0 +1,95 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('zmApp.FirstUseCtrl', ['$scope', '$ionicSideMenuDelegate', 'zm', '$stateParams', '$ionicHistory', '$state', 'NVRDataModel', '$rootScope', '$ionicPopup', '$translate', function($scope, $ionicSideMenuDelegate, zm, $stateParams, $ionicHistory, $state, NVRDataModel, $rootScope, $ionicPopup, $translate)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //-------------------------------------------------------------------------
+ // Controller Main
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log("**VIEW ** FirstUse Ctrl Entered");
+ $ionicSideMenuDelegate.canDragContent(true);
+ // right up here lets set certs to true, we will disable it later
+ // this is for first starts
+
+ //
+ if (window.cordova)
+ {
+ cordova.plugins.certificates.trustUnsecureCerts(true);
+ NVRDataModel.log (">>>>>Accepting all certificates, since its first use");
+ }
+
+
+ });
+
+ $scope.switchLang = function()
+ {
+ $scope.lang = NVRDataModel.getLanguages();
+ $scope.myopt = {
+ lang: ""
+ };
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ scope: $scope,
+ template: '<ion-radio-fix ng-repeat="item in lang" ng-value="item.value" ng-model="myopt.lang"> {{item.text}} </ion-radio-fix>',
+
+ title: $translate.instant('kSelectLanguage'),
+
+ buttons: [
+ {
+ text: $translate.instant('kButtonCancel'),
+ onTap: function(e)
+ {
+ //return "CANCEL";
+ }
+
+ },
+ {
+ text: $translate.instant('kButtonOk'),
+ onTap: function(e)
+ {
+ NVRDataModel.log("Language selected:" + $scope.myopt.lang);
+ NVRDataModel.setDefaultLanguage($scope.myopt.lang, true);
+
+ //return "OK";
+
+ }
+ }]
+ });
+
+ };
+
+ $scope.goToLogin = function()
+ {
+ $ionicHistory.nextViewOptions(
+ {
+ disableAnimate: false,
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ };
+
+ $scope.goToWizard = function()
+ {
+ $ionicHistory.nextViewOptions(
+ {
+ disableAnimate: false,
+ disableBack: true
+ });
+ $state.go("wizard");
+ return;
+ };
+
+}]);
diff --git a/www/js/HelpCtrl.js b/www/js/HelpCtrl.js
new file mode 100644
index 00000000..51da877b
--- /dev/null
+++ b/www/js/HelpCtrl.js
@@ -0,0 +1,92 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console, Masonry */
+
+angular.module('zmApp.controllers').controller('zmApp.HelpCtrl', ['$scope', '$rootScope', '$ionicModal', 'NVRDataModel', '$ionicSideMenuDelegate', '$ionicHistory', '$state', '$translate', '$q', '$templateRequest', '$sce', '$compile', function($scope, $rootScope, $ionicModal, NVRDataModel, $ionicSideMenuDelegate, $ionicHistory, $state, $translate, $q, $templateRequest, $sce, $compile)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //----------------------------------------------------------------
+ // This function dynamically inserts the relevant help text file
+ // based on selected language
+ //----------------------------------------------------------------
+
+ function insertHelp()
+ {
+
+ var l = NVRDataModel.getDefaultLanguage() || 'en';
+ var lang = "lang/help/help-" + l + ".html";
+ //console.log ("LANG IS " + lang);
+ var templateUrl = $sce.getTrustedResourceUrl(lang);
+ var lang_fb = "lang/help/help-" + "en" + ".html";
+ var templateUrlFB = $sce.getTrustedResourceUrl(lang_fb);
+
+ $templateRequest(lang)
+ .then(function(template)
+ {
+ var elem = angular.element(document.getElementById('insertHelp'));
+ $compile(elem.html(template).contents())($scope);
+ },
+ function(error)
+ {
+ NVRDataModel.log("Language file " + lang + " not found, falling back");
+ $templateRequest(templateUrlFB)
+ .then(function(template)
+ {
+ var elem = angular.element(document.getElementById('insertHelp'));
+ $compile(elem.html(template).contents())($scope);
+ },
+ function(error)
+ {
+ NVRDataModel.log("fallback help not found");
+ });
+ }
+ );
+
+ }
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log("**VIEW ** Help Ctrl Entered");
+ NVRDataModel.setAwake(false);
+ $scope.zmAppVersion = NVRDataModel.getAppVersion();
+ insertHelp();
+
+ });
+
+}]);
diff --git a/www/js/ImportantMessageCtrl.js b/www/js/ImportantMessageCtrl.js
new file mode 100644
index 00000000..0f948d7f
--- /dev/null
+++ b/www/js/ImportantMessageCtrl.js
@@ -0,0 +1,35 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('zmApp.ImportantMessageCtrl', ['$scope', '$ionicSideMenuDelegate', 'zm', '$stateParams', '$timeout', '$rootScope', function($scope, $ionicSideMenuDelegate, zm, $stateParams, $timeout, $rootScope)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //-------------------------------------------------------------------------
+ // Controller Main
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ console.log("**VIEW ** LowVersion Ctrl Entered");
+ $ionicSideMenuDelegate.canDragContent(true);
+ $scope.requiredVersion = zm.minAppVersion;
+ $scope.currentVersion = $stateParams.ver;
+ $scope.recommendedVersion = zm.recommendedAppVersion;
+
+ });
+
+ $scope.openMenu = function()
+ {
+ $timeout(function()
+ {
+ $rootScope.stateofSlide = $ionicSideMenuDelegate.isOpen();
+ }, 500);
+
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+}]);
diff --git a/www/js/InvalidApiCtrl.js b/www/js/InvalidApiCtrl.js
new file mode 100644
index 00000000..1c65b9fd
--- /dev/null
+++ b/www/js/InvalidApiCtrl.js
@@ -0,0 +1,37 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('zmApp.InvalidApiCtrl', ['$scope', '$ionicSideMenuDelegate', 'zm', '$stateParams', '$timeout', '$rootScope', function($scope, $ionicSideMenuDelegate, zm, $stateParams, $timeout, $rootScope)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //-------------------------------------------------------------------------
+ // Controller Main
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ console.log("**VIEW ** InvalidAPI Ctrl Entered");
+ $ionicSideMenuDelegate.canDragContent(true);
+ });
+
+ $scope.openMenu = function()
+ {
+ $timeout(function()
+ {
+ $rootScope.stateofSlide = $ionicSideMenuDelegate.isOpen();
+ }, 500);
+
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.readFAQ = function()
+ {
+ window.open('https://github.com/pliablepixels/zmNinja/wiki/Validating-if-APIs-work-on-ZM', '_blank', 'location=yes');
+ return false;
+ };
+
+}]);
diff --git a/www/js/LogCtrl.js b/www/js/LogCtrl.js
new file mode 100644
index 00000000..808dcbb5
--- /dev/null
+++ b/www/js/LogCtrl.js
@@ -0,0 +1,315 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global saveAs, cordova,StatusBar,angular,console,moment */
+
+angular.module('zmApp.controllers').controller('zmApp.LogCtrl', ['$scope', '$rootScope', 'zm', '$ionicModal', 'NVRDataModel', '$ionicSideMenuDelegate', '$fileLogger', '$cordovaEmailComposer', '$ionicPopup', '$timeout', '$ionicHistory', '$state', '$interval', '$ionicLoading', '$translate', '$http',function($scope, $rootScope, zm, $ionicModal, NVRDataModel, $ionicSideMenuDelegate, $fileLogger, $cordovaEmailComposer, $ionicPopup, $timeout, $ionicHistory, $state, $interval, $ionicLoading, $translate, $http)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //---------------------------------------------------------------
+ // Controller main
+ //---------------------------------------------------------------
+
+ var intervalLogUpdateHandle;
+
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ function onPause()
+ {
+ NVRDataModel.debug("LogCtrl: pause called, killing log timer");
+ // $interval.cancel(intervalLogUpdateHandle);
+ }
+
+ function onResume()
+ {
+ NVRDataModel.debug("LogCtrl: resume called, starting log timer");
+ loadLogs();
+ }
+
+ $scope.flipLogs = function()
+ {
+ if ($scope.logEntity == 'ZoneMinder')
+ $scope.logEntity = $rootScope.appName;
+ else
+ $scope.logEntity = 'ZoneMinder';
+ console.log ("Flipped");
+ loadLogs();
+
+ };
+
+ $scope.deleteLogs = function()
+ {
+
+ $rootScope.zmPopup = $ionicPopup.confirm(
+ {
+ title: $translate.instant('kPleaseConfirm'),
+ template: $translate.instant('kDeleteLogsConfirm'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+
+ $rootScope.zmPopup.then(function(res)
+ {
+ if (res)
+ {
+ $fileLogger.deleteLogfile().then(function()
+ {
+ //console.log('Logfile deleted');
+ $fileLogger.setStorageFilename(zm.logFile);
+ $scope.log.logString = "";
+ });
+ }
+ });
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //--------------------------------------------------------------------------
+ // Make sure user knows information masking is best effort
+ //--------------------------------------------------------------------------
+
+ $scope.sendEmail = function(logstring)
+ {
+ $ionicPopup.confirm(
+ {
+ title: $translate.instant('kSensitiveTitle'),
+ template: $rootScope.appName + ' ' + $translate.instant('kSensitiveBody'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ })
+ .then(function(res)
+ {
+ if (res)
+ {
+ logstring = "Logs for version:" + $scope.zmAppVersion + " ("+$rootScope.platformOS+")\n" + logstring;
+ sendEmailReally(logstring);
+ }
+
+ });
+ };
+
+ //--------------------------------------------------------------------------
+ // Convenience function to send logs via email
+ //--------------------------------------------------------------------------
+ function sendEmailReally(logstring)
+ {
+ if (window.cordova)
+ {
+
+ // do my best to replace sensitive information
+ var loginData = NVRDataModel.getLogin();
+
+ // We don't need this anymore as log and debug now strip passwords
+ /*if (loginData.password !="")
+ {
+ var re1 = new RegExp(loginData.password, "g");
+ logstring = logstring.replace(re1, "<deleted>");
+ }*/
+ // keep the protocol, helps to debug
+ var urlNoProtocol = loginData.url.replace(/.*?:\/\//, "");
+ if (urlNoProtocol != "")
+ {
+ var re2 = new RegExp(urlNoProtocol, "g");
+ // just replacing baseurl - that will take care of
+ // masking api but may not be cgi
+ logstring = logstring.replace(re2, "<server>");
+ }
+ urlNoProtocol = loginData.streamingurl.replace(/.*?:\/\//, "");
+ if (urlNoProtocol != "")
+ {
+ var re3 = new RegExp(urlNoProtocol, "g");
+ logstring = logstring.replace(re3, "<server>");
+ }
+
+ urlNoProtocol = loginData.eventServer.replace(/.*?:\/\//, "");
+ if (urlNoProtocol != "")
+ {
+ var re4 = new RegExp(urlNoProtocol, "g");
+ logstring = logstring.replace(re4, "<server>");
+ }
+
+ cordova.plugins.email.isAvailable(
+ function (isAvailable) {
+
+ if (isAvailable) {
+ cordova.plugins.email.open({
+ to: zm.authoremail,
+ subject: $rootScope.appName + ' logs',
+ body: logstring
+ });
+ }
+ else {
+ // kEmailNotConfigured
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kEmailNotConfigured'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+
+ }
+
+ });
+
+
+
+
+ // window.plugins.emailComposer.showEmailComposerWithCallback(callback, $rootScope.appName + ' logs', logstring, [zm.authoremail]);
+
+ }
+ else
+ {
+ // console.log("Using default email client to send data");
+
+ var fname = $rootScope.appName + "-logs-" +
+ moment().format('MMM-DD-YY_HH-mm-ss') + ".txt";
+
+ var blob = new Blob([logstring],
+ {
+ type: "text/plain;charset=utf-8"
+ });
+ saveAs(blob, fname);
+ }
+
+ }
+
+ function callback()
+ {
+ // console.log ("EMAIL SENT");
+ NVRDataModel.debug("Email sent callback called");
+ }
+
+ function loadZMlogs()
+ {
+ var ld = NVRDataModel.getLogin();
+ var lapi = ld.apiurl + "/logs.json?sort=TimeKey&direction=desc&page="+$scope.zmPage;
+ $http.get (lapi)
+ .then (function (success) {
+ $ionicLoading.hide();
+ $scope.zmMaxPage = success.data.pagination.pageCount;
+ console.log ("PAGES="+$scope.zmMaxPage);
+ var tLogs = "";
+ console.log (JSON.stringify(success));
+ for (var i=0; i< success.data.logs.length; i++)
+ {
+ tLogs = tLogs + moment.unix(success.data.logs[i].Log.TimeKey).format ("MM/DD/YY hh:mm:ss") +" "+
+ success.data.logs[i].Log.Code+" " +
+ success.data.logs[i].Log.Message+"\n";
+ }
+ $scope.log.logString = tLogs;
+ },
+ function (error) {
+ NVRDataModel.log ("Error getting ZM logs:"+JSON.stringify(error));
+ $scope.log.logString = "Error getting log: " + JSON.stringify(error);
+
+
+ } );
+
+ }
+
+ $scope.changePage = function(p)
+ {
+ $scope.zmPage = $scope.zmPage + p;
+ if ($scope.zmPage < 1) $scope.zmPage = 1;
+ if ($scope.zmPage > $scope.zmMaxPage) $scope.zmPage = $scope.zmMaxPage;
+ loadLogs();
+ };
+
+ function loadLogs()
+ {
+ //console.log ("GETTING LOGS");
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kLoading'),
+ noBackdrop: true,
+ duration: zm.loadingTimeout
+
+ });
+
+ if ($scope.logEntity == $rootScope.appName)
+ {
+ $fileLogger.getLogfile().then(function(l)
+ {
+
+ $scope.log.logString = l.split('\n').reverse().join('\n');
+
+ $ionicLoading.hide();
+ },
+ function(error)
+ {
+ $scope.log.logString = "Error getting log: " + JSON.stringify(error);
+ $ionicLoading.hide();
+ });
+ }
+ else
+ loadZMlogs();
+
+ }
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log("**VIEW ** Log Ctrl Entered");
+ NVRDataModel.setAwake(false);
+ $scope.logEntity = $rootScope.appName;
+ $scope.zmPage = 1;
+ $scope.zmMaxPage = 1;
+
+ $scope.log = {
+ logString: ""
+ };
+
+ $scope.zmAppVersion = NVRDataModel.getAppVersion();
+
+ /* intervalLogUpdateHandle = $interval(function ()
+ {
+ loadLogs();
+
+ }.bind(this), 3000);*/
+
+ loadLogs();
+
+ });
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ //console.log ("Deleting Log interval...");
+ // $interval.cancel(intervalLogUpdateHandle);
+ });
+
+}]);
diff --git a/www/js/LoginCtrl.js b/www/js/LoginCtrl.js
new file mode 100644
index 00000000..300e4fbf
--- /dev/null
+++ b/www/js/LoginCtrl.js
@@ -0,0 +1,860 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,alert,URI, localforage */
+
+angular.module('zmApp.controllers').controller('zmApp.LoginCtrl', ['$scope', '$rootScope', 'zm', '$ionicModal', 'NVRDataModel', '$ionicSideMenuDelegate', '$ionicPopup', '$http', '$q', '$ionicLoading', 'zmAutoLogin', '$cordovaPinDialog', 'EventServer', '$ionicHistory', '$state', '$ionicActionSheet', 'SecuredPopups', '$stateParams', '$translate', function($scope, $rootScope, zm, $ionicModal, NVRDataModel, $ionicSideMenuDelegate, $ionicPopup, $http, $q, $ionicLoading, zmAutoLogin, $cordovaPinDialog, EventServer, $ionicHistory, $state, $ionicActionSheet, SecuredPopups, $stateParams, $translate)
+{
+ $scope.openMenu = function()
+ {
+
+ if ($scope.loginData.serverName)
+ saveItems(false);
+ $ionicSideMenuDelegate.toggleLeft();
+
+ };
+
+ var oldName;
+ var serverbuttons = [];
+ var availableServers;
+ $scope.loginData = NVRDataModel.getLogin();
+
+ $scope.check = {
+ isUseAuth: false,
+ isUseEventServer: false
+ };
+
+ $scope.check.isUseAuth = ($scope.loginData.isUseAuth) ? true : false;
+ $scope.check.isUseEventServer = ($scope.loginData.isUseEventServer == true) ? true : false;
+
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ function onResume()
+ {
+ NVRDataModel.log("Login screen resumed");
+
+ }
+
+ function onPause()
+ {
+ NVRDataModel.log("Login screen going to background, saving data");
+ localforage.setItem("settings-temp-data", $scope.loginData);
+
+ }
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+
+ };
+
+ //----------------------------------------------------------------
+ // Specifies a linked profile to try if this profile fails
+ //----------------------------------------------------------------
+
+ $scope.selectFallback = function()
+ {
+ var as = Object.keys(NVRDataModel.getServerGroups());
+ if (as.length < 2)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kFallback2Configs'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ return;
+
+ }
+ var ab = [
+ {
+ text: $translate.instant('kClear')
+ }];
+ var ld = NVRDataModel.getLogin();
+ as.forEach(function(item)
+ {
+ if (item != ld.serverName) ab.push(
+ {
+ text: item
+ });
+ });
+ var sheet = $ionicActionSheet.show(
+ {
+ buttons: ab,
+ titleText: $translate.instant('kSelectFallback'),
+ cancelText: $translate.instant('kButtonCancel'),
+ cancel: function() {},
+ buttonClicked: function(index)
+ {
+ //console.log ("YOU WANT " + ab[index].text + index);
+ if (index == 0)
+ $scope.loginData.fallbackConfiguration = "";
+ else
+ $scope.loginData.fallbackConfiguration = ab[index].text;
+ NVRDataModel.setLogin($scope.loginData);
+ return true;
+ }
+ });
+
+ };
+
+ //----------------------------------------------------------------
+ // This is called when the user changes profiles
+ //----------------------------------------------------------------
+
+ $scope.serverActionSheet = function()
+ {
+ var hideSheet = $ionicActionSheet.show(
+ {
+ buttons: serverbuttons,
+ destructiveText: $translate.instant('kDelete'),
+ titleText: $translate.instant('kManageServerGroups'),
+ cancelText: $translate.instant('kButtonCancel'),
+ cancel: function()
+ {
+ // add cancel code..
+ },
+ buttonClicked: function(index)
+ {
+ //console.log ("YOU WANT " + serverbuttons[index].text + " INDEX " + index);
+
+ if (serverbuttons[index].text == $translate.instant('kServerAdd') + "...")
+ {
+
+ $scope.loginData = angular.copy(NVRDataModel.getDefaultLoginObject());
+ return true;
+ }
+
+ var zmServers = NVRDataModel.getServerGroups();
+ $scope.loginData = zmServers[serverbuttons[index].text];
+
+ //console.log ("NEW LOGIN OBJECT IS " + JSON.stringify($scope.loginData));
+
+ $scope.check.isUseAuth = ($scope.loginData.isUseAuth) ? true : false;
+ $scope.check.isUseEventServer = ($scope.loginData.isUseEventServer == true) ? true : false;
+
+ NVRDataModel.debug("Retrieved state for this profile:" + JSON.stringify($scope.loginData));
+
+ // lets make sure Event Server is loaded
+ // correctly
+
+ // FIXME: But what happens if you don't save?
+ // loginData gets written but auth is not done
+ NVRDataModel.setLogin($scope.loginData);
+
+ return true;
+ },
+
+ destructiveButtonClicked: function()
+ {
+
+ if (!$scope.loginData.serverName)
+ {
+ NVRDataModel.debug("cannot delete empty entry");
+ return true;
+
+ }
+ $rootScope.zmPopup = SecuredPopups.show('confirm',
+ {
+ title: $translate.instant('kDelete'),
+ template: $translate.instant('kDeleteProfile')+" "+$scope.loginData.serverName,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ }).then(function(res)
+ {
+
+ if (res)
+ actuallyDelete();
+
+ });
+
+
+
+ function actuallyDelete()
+ {
+
+ var zmServers = NVRDataModel.getServerGroups();
+ //console.log ("YOU WANT TO DELETE " + $scope.loginData.serverName);
+ //console.log ("LENGTH OF SERVERS IS " + Object.keys(zmServers).length);
+ if (Object.keys(zmServers).length > 1)
+ {
+
+ NVRDataModel.log("Deleting " + $scope.loginData.serverName);
+ delete zmServers[$scope.loginData.serverName];
+ NVRDataModel.setServerGroups(zmServers);
+ // point to first element
+ // better than nothing
+ // note this is actually unordered
+ $scope.loginData = zmServers[Object.keys(zmServers)[0]];
+ NVRDataModel.setLogin($scope.loginData);
+
+ availableServers = Object.keys(NVRDataModel.getServerGroups());
+ serverbuttons = [
+ {
+ text: $translate.instant('kServerAdd') + "..."
+ }];
+ for (var servIter = 0; servIter < availableServers.length; servIter++)
+ {
+ serverbuttons.push(
+ {
+ text: availableServers[servIter]
+ });
+ //console.log("ADDING : " + availableServers[servIter]);
+ }
+ //console.log (">>>>>>>delete: server buttons " + JSON.stringify(serverbuttons));
+ }
+ else
+ {
+ NVRDataModel.displayBanner('error', [$translate.instant('kBannerCannotDeleteNeedOne')]);
+ }
+
+
+ }
+
+ return true;
+ }
+
+ });
+ };
+
+ //----------------------------------------------------------------
+ // This is when you tap on event server settings
+ //----------------------------------------------------------------
+
+ $scope.eventServerSettings = function()
+ {
+ NVRDataModel.debug("Saving settings before going to Event Server settings");
+ //console.log ( "My loginData saved " + JSON.stringify($scope.loginData));
+ NVRDataModel.setLogin($scope.loginData);
+ $state.go("eventserversettings");
+ return;
+ };
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log("**VIEW ** LoginCtrl Entered");
+ NVRDataModel.setAwake(false);
+ var ld = NVRDataModel.getLogin();
+ oldName = ld.serverName;
+
+ availableServers = Object.keys(NVRDataModel.getServerGroups());
+ serverbuttons = [
+ {
+ text: $translate.instant('kServerAdd') + "..."
+ }];
+ for (var servIter = 0; servIter < availableServers.length; servIter++)
+ {
+ serverbuttons.push(
+ {
+ text: availableServers[servIter]
+ });
+
+ //console.log (">>>>>>>ionicview enter: server buttons " + JSON.stringify(serverbuttons));
+ }
+
+ NVRDataModel.debug("Does login need to hear the wizard? " + $stateParams.wizard);
+
+ if ($stateParams.wizard == "true")
+ {
+ NVRDataModel.log("Creating new login entry for wizard");
+ $scope.loginData = angular.copy(NVRDataModel.getDefaultLoginObject());
+ $scope.loginData.serverName = $rootScope.wizard.serverName;
+ $scope.loginData.url = $rootScope.wizard.loginURL;
+ $scope.loginData.apiurl = $rootScope.wizard.apiURL;
+ $scope.loginData.streamingurl = $rootScope.wizard.streamingURL;
+ if ($rootScope.wizard.useauth && $rootScope.wizard.usezmauth)
+ {
+ $scope.loginData.username = $rootScope.wizard.zmuser;
+ $scope.loginData.password = $rootScope.wizard.zmpassword;
+ }
+ else
+ {
+ $scope.loginData.isUseAuth = false;
+ }
+
+ if ((/^https:\/\//i.test($scope.loginData.url)))
+ {
+ $scope.loginData.useSSL = true;
+ }
+
+ }
+ else
+ {
+ var savedData;
+ localforage.getItem("settings-temp-data").then(function(value)
+ {
+ savedData = value;
+ //= zmStorageService.getObject ("settings-temp-data");
+ if (!NVRDataModel.isEmpty(savedData))
+ {
+ $scope.loginData = savedData;
+ NVRDataModel.log("retrieved pre-stored loginData on past pause: " + JSON.stringify($scope.loginData));
+ localforage.removeItem("settings-temp-data");
+ //zmStorageService.setObject("settings-temp-data", {});
+ }
+ else
+ {
+ NVRDataModel.log("Not recovering login data as its empty");
+ }
+ });
+ }
+
+ });
+
+ $scope.$on('$ionicView.beforeLeave', function()
+ {
+ //console.log("**VIEW ** LoginCtrl Entered");
+
+ });
+
+ //----------------------------------------------------------------
+ // We need to make sure that if the user changes a profile, that
+ // its saved, which involves re-auth. Not doing this will mess
+ // up monitors. We can't automatically do it, because we really
+ // don't want re-auth delays each time a user taps on a new profile
+ // especially if they switch back
+ //
+ // So instead, if check if the profile name has changed - if it has
+ // we block state change and ask the user to save
+ //----------------------------------------------------------------
+
+ // credit: http://stackoverflow.com/questions/33385610/ionic-prevent-navigation-on-leave
+ /* Disabled - seems to crash with native transitions
+
+ $scope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
+ NVRDataModel.setAwake(false);
+ var ld = NVRDataModel.getLogin();
+
+ if (ld.serverName != oldName) {
+ event.preventDefault();
+ $rootScope.zmPopup = SecuredPopups.show('alert', {
+ title: $translate.instant('kPleaseSave'),
+ template: $translate.instant('kProfileChangeNotification', {
+ oldName: oldName,
+ newName: ld.serverName
+ })
+
+ });
+
+ }
+ });*/
+
+ $rootScope.$on('$stateChangeSuccess', function()
+ {
+ $scope.ignoreDirty = false;
+ });
+
+ // Make a noble attempt at deciphering
+
+ //--------------------------------------------------------------------------
+ // When PIN is enabled, this is called to specify a PIN
+ // FIXME: Get rid of cordovaPinDialog. It's really not needed
+ //--------------------------------------------------------------------------
+ $scope.pinPrompt = function(evt)
+ {
+ NVRDataModel.log("Password prompt");
+ if ($scope.loginData.usePin)
+ {
+ $scope.loginData.pinCode = "";
+ $cordovaPinDialog.prompt($translate.instant('kEnterPin'), $translate.instant('kPinProtect')).then(
+ function(result1)
+ {
+
+ // console.log (JSON.stringify(result1));
+ if (result1.input1 && result1.buttonIndex == 1)
+ {
+ $cordovaPinDialog.prompt($translate.instant('kReconfirmPin'), $translate.instant('kPinProtect'))
+ .then(function(result2)
+ {
+ if (result1.input1 == result2.input1)
+ {
+ NVRDataModel.log("Pin code match");
+ $scope.loginData.pinCode = result1.input1;
+ }
+ else
+ {
+ NVRDataModel.log("Pin code mismatch");
+ $scope.loginData.usePin = false;
+ NVRDataModel.displayBanner('error', [$translate.instant('kBannerPinMismatch')]);
+ }
+ },
+ function(error)
+ {
+ //console.log("Error inside");
+ $scope.loginData.usePin = false;
+ });
+ }
+ else
+ {
+ $scope.loginData.usePin = false;
+ }
+ },
+ function(error)
+ {
+ //console.log("Error outside");
+ $scope.loginData.usePin = false;
+ });
+
+ }
+ else
+ {
+ NVRDataModel.debug("Password disabled");
+ }
+ };
+
+ //-------------------------------------------------------------------------------
+ // Makes input easier
+ //-------------------------------------------------------------------------------
+
+ $scope.portalKeypress = function(evt)
+ {
+
+ if (/^https:\/\//i.test($scope.loginData.url))
+ {
+ $scope.loginData.useSSL = true;
+ }
+ else
+ {
+ $scope.loginData.useSSL = false;
+ }
+
+ if ($scope.loginData.url.slice(-1) == '/')
+ {
+ $scope.loginData.apiurl = $scope.loginData.url + "api";
+ $scope.loginData.streamingurl = $scope.loginData.url + "cgi-bin";
+ }
+ else
+ {
+ $scope.loginData.apiurl = $scope.loginData.url + "/api";
+ $scope.loginData.streamingurl = $scope.loginData.url + "/cgi-bin";
+ }
+
+ };
+ //-------------------------------------------------------------------------------
+ // Adds http to url if not present
+ // http://stackoverflow.com/questions/11300906/check-if-a-string-starts-with-http-using-javascript
+ //-------------------------------------------------------------------------------
+ function addhttp(url)
+ {
+
+ if ((!/^(f|ht)tps?:\/\//i.test(url)) && (url != ""))
+ {
+ url = "http://" + url;
+ }
+ return url;
+ }
+
+ function addWsOrWss(url)
+ {
+
+ if ((!/^wss?:\/\//i.test(url)) && (url != ""))
+ {
+ url = "ws://" + url;
+ }
+ return url;
+ }
+
+ function endsWith(str, suffix)
+ {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ }
+
+ //-----------------------------------------------------------------------------
+ // Perform the login action when the user submits the login form
+ //-----------------------------------------------------------------------------
+
+ function saveItems(showalert)
+ {
+
+ //console.log ("*********** SAVE ITEMS CALLED ");
+ //console.log('Saving login');
+ NVRDataModel.setFirstUse(false);
+
+ // used for menu display
+
+ // lets so some basic sanitization of the data
+ // I am already adding "/" so lets remove spurious ones
+ // though webkit has no problems. Even so, this is to avoid
+ // a deluge of folks who look at the error logs and say
+ // the reason the login data is not working is because
+ // the app is adding multiple "/" characters
+
+ $scope.loginData.url = $scope.loginData.url.replace(/\s/g, "");
+ $scope.loginData.apiurl = $scope.loginData.apiurl.replace(/\s/g, "");
+ $scope.loginData.streamingurl = $scope.loginData.streamingurl.replace(/\s/g, "");
+ $scope.loginData.eventServer = $scope.loginData.eventServer.replace(/\s/g, "");
+
+ $scope.loginData.username = $scope.loginData.username.trim();
+
+ $scope.loginData.isUseAuth = ($scope.check.isUseAuth) ? true : false;
+ $scope.loginData.isUseEventServer = ($scope.check.isUseEventServer) ? true : false;
+
+ if ($scope.loginData.url.slice(-1) == '/')
+ {
+ $scope.loginData.url = $scope.loginData.url.slice(0, -1);
+
+ }
+
+ if ($scope.loginData.apiurl.slice(-1) == '/')
+ {
+ $scope.loginData.apiurl = $scope.loginData.apiurl.slice(0, -1);
+
+ }
+
+ if ($scope.loginData.streamingurl.slice(-1) == '/')
+ {
+ $scope.loginData.streamingurl = $scope.loginData.streamingurl.slice(0, -1);
+
+ }
+
+ if ($scope.loginData.eventServer.slice(-1) == '/')
+ {
+ $scope.loginData.eventServer = $scope.loginData.eventServer.slice(0, -1);
+
+ }
+ // strip cgi-bin if it is there but only at the end
+ // Nov 17 Don't mess with this path. centos uses zm-cgi-bin of all things
+
+ /*if ($scope.loginData.streamingurl.slice(-7).toLowerCase() == 'cgi-bin') {
+ $scope.loginData.streamingurl = $scope.loginData.streamingurl.slice(0, -7);
+ }*/
+
+ // check for protocol and if not put it in
+
+ $scope.loginData.url = addhttp($scope.loginData.url);
+ $scope.loginData.apiurl = addhttp($scope.loginData.apiurl);
+ $scope.loginData.streamingurl = addhttp($scope.loginData.streamingurl);
+ $scope.loginData.eventServer = addWsOrWss($scope.loginData.eventServer);
+
+ if ($scope.loginData.useSSL)
+ {
+ // replace all http with https
+ $scope.loginData.url = $scope.loginData.url.replace("http:", "https:");
+ $scope.loginData.apiurl = $scope.loginData.apiurl.replace("http:", "https:");
+ $scope.loginData.streamingurl = $scope.loginData.streamingurl.replace("http:", "https:");
+ $scope.loginData.eventServer = $scope.loginData.eventServer.replace("ws:", "wss:");
+
+ }
+ else
+ {
+ // replace all https with http
+ $scope.loginData.url = $scope.loginData.url.replace("https:", "http:");
+ $scope.loginData.apiurl = $scope.loginData.apiurl.replace("https:", "http:");
+ $scope.loginData.streamingurl = $scope.loginData.streamingurl.replace("https:", "http:");
+ // don't do it for WSS - lets mandate that
+ }
+
+ var apiurl = $scope.loginData.apiurl + '/host/getVersion.json';
+ var portalurl = $scope.loginData.url + '/index.php';
+
+ // Check if isUseAuth is set make sure u/p have a dummy value
+ if ($scope.check.isUseAuth)
+ {
+ if (!$scope.loginData.username) $scope.loginData.username = "x";
+ if (!$scope.loginData.password) $scope.loginData.password = "x";
+ //NVRDataModel.log("Authentication is disabled, setting dummy user & pass");
+ }
+
+ if (parseInt($scope.loginData.maxMontage) <= 0)
+ {
+ $scope.loginData.maxMontage = "100";
+ }
+
+ // do this before setLogin so message is sent
+
+ if (!$scope.check.isUseEventServer)
+ {
+ $rootScope.isAlarm = 0;
+ if ($rootScope.apnsToken)
+ {
+ NVRDataModel.log("Making sure we don't get push notifications");
+ EventServer.sendMessage('push',
+ {
+ type: 'token',
+ platform: $rootScope.platformOS,
+ token: $rootScope.apnsToken,
+ state: "disabled"
+ }, 1);
+ }
+ }
+
+ NVRDataModel.setLogin($scope.loginData);
+
+ $rootScope.runMode = NVRDataModel.getBandwidth();
+
+ oldName = $scope.loginData.serverName;
+
+ if ($scope.check.isUseEventServer)
+ {
+ EventServer.init();
+ if ($rootScope.apnsToken && $scope.loginData.disablePush != true)
+ {
+ NVRDataModel.log("Making sure we get push notifications");
+ EventServer.sendMessage('push',
+ {
+ type: 'token',
+ platform: $rootScope.platformOS,
+ token: $rootScope.apnsToken,
+ state: "enabled"
+ }, 1);
+ }
+ EventServer.sendMessage("control",
+ {
+ type: 'filter',
+ monlist: $scope.loginData.eventServerMonitors,
+ intlist: $scope.loginData.eventServerInterval
+ });
+
+ }
+
+ // lets logout
+ NVRDataModel.debug("Logging out of current session...");
+ $rootScope.authSession = "undefined";
+
+
+ $http(
+ {
+ method: 'POST',
+ timeout:10000,
+ //withCredentials: true,
+ url: $scope.loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': 'application/json',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var params = str.join("&");
+ return params;
+ },
+
+ data:
+ {
+ action: "logout",
+ view: "login"
+ }
+ })
+ .finally(function(ans)
+ {
+
+ zmAutoLogin.doLogin("<button class='button button-clear' style='line-height: normal; min-height: 0; min-width: 0; color:#fff;' ng-click='$root.cancelAuth()'><i class='ion-close-circled'></i>&nbsp;" + $translate.instant('kAuthenticating') + "...</button>")
+ // Do the happy menu only if authentication works
+ // if it does not work, there is an emitter for auth
+ // fail in app.js that will be called to show an error
+ // box
+
+ .then(function(data)
+ {
+
+ // Now let's validate if the API works
+
+ // note that due to reachability, it might have switched to another server
+
+ if ($scope.loginData.serverName != NVRDataModel.getLogin().serverName)
+ {
+ NVRDataModel.debug(">>> Server information has changed, likely a fallback took over!");
+ $scope.loginData = NVRDataModel.getLogin();
+ apiurl = $scope.loginData.apiurl + '/host/getVersion.json';
+ portalurl = $scope.loginData.url + '/index.php';
+ }
+
+ // possible image digits changed between servers
+ NVRDataModel.getKeyConfigParams(0);
+
+ NVRDataModel.log("Validating APIs at " + apiurl);
+ $http.get(apiurl)
+ .success(function(data)
+ {
+
+ NVRDataModel.getTimeZone(true);
+ var loginStatus = $translate.instant('kExploreEnjoy') + " " + $rootScope.appName + "!";
+ EventServer.refresh();
+
+ // now grab and report PATH_ZMS
+ NVRDataModel.getPathZms()
+ .then(function(data)
+ {
+ var ld = NVRDataModel.getLogin();
+ var zm_cgi = data.toLowerCase();
+
+ var user_cgi = (ld.streamingurl).toLowerCase();
+ NVRDataModel.log("ZM relative cgi-path: " + zm_cgi + ", you entered: " + user_cgi);
+
+ $http.get(ld.streamingurl + "/zms")
+ .success(function(data)
+ {
+ NVRDataModel.debug("Urk! cgi-path returned success, but it should not have come here");
+ loginStatus = $translate.instant('kLoginStatusNoCgi');
+
+ NVRDataModel.debug("refreshing API version...");
+ NVRDataModel.getAPIversion()
+ .then(function(data)
+ {
+ var refresh = NVRDataModel.getMonitors(1);
+ $rootScope.apiVersion = data;
+ },
+ function(error)
+ {
+ var refresh = NVRDataModel.getMonitors(1);
+ $rootScope.apiVersion = "0.0.0";
+ NVRDataModel.debug("Error, failed API version, setting to " + $rootScope.apiVersion);
+ });
+
+ if (showalert)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kLoginValidatedTitle'),
+ template: loginStatus,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ }).then(function(res)
+ {
+
+ $ionicSideMenuDelegate.toggleLeft();
+ NVRDataModel.debug("Force reloading monitors...");
+
+ });
+ }
+ })
+ .error(function(error, status)
+ {
+ // If its 5xx, then the cgi-bin path is valid
+ // if its 4xx then the cgi-bin path is not valid
+
+ if (status < 500)
+ {
+ loginStatus = $translate.instant('kLoginStatusNoCgiAlt');
+ }
+
+ if (showalert)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kLoginValidatedTitle'),
+ template: loginStatus,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ }).then(function(res)
+ {
+
+ $ionicSideMenuDelegate.toggleLeft();
+ NVRDataModel.debug("Force reloading monitors...");
+
+ });
+ }
+ else // make sure CGI error is always shown
+ {
+ NVRDataModel.displayBanner((status < 500)? 'error':'info', [loginStatus]);
+ }
+ NVRDataModel.debug("refreshing API version...");
+ NVRDataModel.getAPIversion()
+ .then(function(data)
+ {
+ var refresh = NVRDataModel.getMonitors(1);
+ $rootScope.apiVersion = data;
+ },
+ function(error)
+ {
+ var refresh = NVRDataModel.getMonitors(1);
+ $rootScope.apiVersion = "0.0.0";
+ NVRDataModel.debug("Error, failed API version, setting to " + $rootScope.apiVersion);
+ });
+
+ });
+ });
+
+ })
+ .error(function(error)
+ {
+ NVRDataModel.displayBanner('error', [$translate.instant('kBannerAPICheckFailed'), $translate.instant('kBannerPleaseCheck')]);
+ NVRDataModel.log("API login error " + JSON.stringify(error));
+
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kLoginValidAPIFailedTitle'),
+ template: $translate.instant('kBannerPleaseCheck'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ });
+ });
+
+ });
+ }
+
+ // ----------------------------------------------
+ // Saves the current profile. Note that
+ // calling saveItems also updates the defaultServer
+ //-----------------------------------------------
+
+ $scope.saveItems = function()
+ {
+
+ if (!$scope.loginData.serverName)
+ {
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kServerEmptyError'),
+ })
+ .then(function(res)
+ {
+ return;
+ });
+ }
+ else
+ {
+ saveItems(true);
+ availableServers = Object.keys(NVRDataModel.getServerGroups());
+ serverbuttons = [
+ {
+ text: $translate.instant('kServerAdd') + "..."
+ }];
+ for (var servIter = 0; servIter < availableServers.length; servIter++)
+ {
+ serverbuttons.push(
+ {
+ text: availableServers[servIter]
+ });
+ }
+ //console.log (">>>>>>>ionicview save: server buttons " + JSON.stringify(serverbuttons));
+
+ }
+
+ };
+
+}]);
diff --git a/www/js/LowVersionCtrl.js b/www/js/LowVersionCtrl.js
new file mode 100644
index 00000000..a7fbb7ff
--- /dev/null
+++ b/www/js/LowVersionCtrl.js
@@ -0,0 +1,24 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('zmApp.LowVersionCtrl', ['$scope', '$ionicSideMenuDelegate', 'zm', '$stateParams', function($scope, $ionicSideMenuDelegate, zm, $stateParams)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //-------------------------------------------------------------------------
+ // Controller Main
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ //console.log("**VIEW ** LowVersion Ctrl Entered");
+ $ionicSideMenuDelegate.canDragContent(true);
+ $scope.requiredVersion = zm.minAppVersion;
+ $scope.currentVersion = $stateParams.ver;
+
+ });
+
+}]);
diff --git a/www/js/MenuController.js b/www/js/MenuController.js
new file mode 100644
index 00000000..071db4df
--- /dev/null
+++ b/www/js/MenuController.js
@@ -0,0 +1,55 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('MenuController', ['$scope', '$ionicSideMenuDelegate', 'zm', '$stateParams', '$ionicHistory', '$state', 'NVRDataModel', '$rootScope', '$ionicPopup', '$translate', function($scope, $ionicSideMenuDelegate, zm, $stateParams, $ionicHistory, $state, NVRDataModel, $rootScope, $ionicPopup, $translate)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //----------------------------------------------------------------
+ // This controller sits along with the main app to bring up
+ // the language menu from the main menu
+ //----------------------------------------------------------------
+ $scope.switchLang = function()
+ {
+ $scope.lang = NVRDataModel.getLanguages();
+ $scope.myopt = {
+ lang: ""
+ };
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ scope: $scope,
+ template: '<ion-radio-fix ng-repeat="item in lang" ng-value="item.value" ng-model="myopt.lang"> {{item.text}} </ion-radio-fix>',
+
+ title: $translate.instant('kSelectLanguage'),
+
+ buttons: [
+ {
+ text: $translate.instant('kButtonCancel'),
+ onTap: function(e)
+ {
+ //return "CANCEL";
+ }
+
+ },
+ {
+ text: $translate.instant('kButtonOk'),
+ onTap: function(e)
+ {
+ NVRDataModel.log("Language selected:" + $scope.myopt.lang);
+ NVRDataModel.setDefaultLanguage($scope.myopt.lang, true);
+ $rootScope.$emit('language-changed');
+
+ //return "OK";
+
+ }
+ }]
+ });
+
+ };
+
+}]);
diff --git a/www/js/MonitorCtrl.js b/www/js/MonitorCtrl.js
new file mode 100644
index 00000000..6a91a52e
--- /dev/null
+++ b/www/js/MonitorCtrl.js
@@ -0,0 +1,550 @@
+/* jshint -W041, -W083 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+// controller for Monitor View
+// refer to comments in EventCtrl for the modal stuff. They are almost the same
+
+angular.module('zmApp.controllers')
+ .controller('zmApp.MonitorCtrl', ['$ionicPopup', 'zm', '$scope', 'NVRDataModel', 'message', '$ionicSideMenuDelegate', '$ionicLoading', '$ionicModal', '$state', '$http', '$rootScope', '$timeout', '$ionicHistory', '$ionicPlatform', '$translate', '$q',
+ function($ionicPopup, zm, $scope, NVRDataModel, message, $ionicSideMenuDelegate, $ionicLoading, $ionicModal, $state, $http, $rootScope, $timeout, $ionicHistory, $ionicPlatform, $translate, $q)
+ {
+
+ //-----------------------------------------------------------------------
+ // Controller Main
+ //-----------------------------------------------------------------------
+
+ // var isModalOpen = false;
+
+ // console.log("***EVENTS: Waiting for Monitors to load before I proceed");
+
+ var loginData;
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+
+ $ionicPlatform.registerBackButtonAction(function(e)
+ {
+ e.preventDefault();
+ if ($scope.modal != undefined && $scope.modal.isShown())
+ {
+ // switch off awake, as liveview is finished
+ NVRDataModel.debug("Modal is open, closing it");
+ NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+ }
+ else
+ {
+ NVRDataModel.debug("Modal is closed, so toggling or exiting");
+ if (!$ionicSideMenuDelegate.isOpenLeft())
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+
+ }
+ else
+ {
+ navigator.app.exitApp();
+ }
+
+ }
+
+ }, 1000);
+
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+ //-----------------------------------------------------------------------
+ // This function takes care of changing monitor parameters
+ // For now, I've only limited it to enable/disable and change monitor mode
+ // and changing monitor function
+ //-----------------------------------------------------------------------
+ $scope.changeConfig = function(monitorName, monitorId, enabled, func)
+ {
+ var checked = false;
+
+ if (monitorName == 'All')
+ {
+ monitorName = $translate.instant('kAll');
+ }
+
+ //console.log("called with " + monitorId + ":" + enabled + ":" + func);
+ if (enabled == '1') checked = true;
+
+ //if monitorId is not specified, all monitors will be changed
+ var monitorsIds = [];
+ if (monitorId == '')
+ {
+ for (var i = 0; i < $scope.monitors.length; i++)
+ {
+ monitorsIds[i] = $scope.monitors[i].Monitor.Id;
+ }
+ }
+ else
+ {
+ monitorsIds[0] = monitorId;
+ }
+
+ $scope.monFunctions = [
+ {
+ text: $translate.instant('kMonModect'),
+ value: "Modect"
+ },
+ {
+ text: $translate.instant('kMonMocord'),
+ value: "Mocord"
+ },
+ {
+ text: $translate.instant('kMonRecord'),
+ value: "Record"
+ },
+ {
+ text: $translate.instant('kMonNodect'),
+ value: "Nodect"
+ },
+ {
+ text: $translate.instant('kMonMonitor'),
+ value: "Monitor"
+ },
+ {
+ text: $translate.instant('kMonNone'),
+ value: "None"
+ }];
+
+ $scope.monfunc = {
+ mymonitorsIds: monitorsIds,
+ myfunc: func,
+ myenabled: checked,
+ myfailedIds: [],
+ mypromises: []
+ };
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ scope: $scope,
+ template: '<ion-toggle ng-model="monfunc.myenabled" ng-checked="monfunc.myenabled" toggle-class="toggle-calm">Enabled</ion-toggle><ion-radio-fix ng-repeat="item in monFunctions" ng-value="item.value" ng-model="monfunc.myfunc"> {{item.text}} </ion-radio-fix>',
+
+ title: $translate.instant('kChangeSettingsFor') + ' ' + monitorName,
+
+ buttons: [
+ {
+ text: $translate.instant('kButtonCancel'),
+
+ },
+ {
+ text: $translate.instant('kButtonSave'),
+ onTap: function(e)
+ {
+ $scope.monfunc.mymonitorsIds.forEach(function(item, index)
+ {
+ NVRDataModel.debug("MonitorCtrl:changeConfig selection:" + $scope.monfunc.myenabled +
+ $scope.monfunc.myfunc);
+ var loginData = NVRDataModel.getLogin();
+ var apiRestart = loginData.apiurl + "/states/change/restart.json";
+ var apiMon = loginData.apiurl + "/monitors/" + item + ".json";
+
+ NVRDataModel.debug("MonitorCtrl: URLs for changeConfig save:" + apiMon);
+
+ var isEnabled = "";
+ isEnabled = ($scope.monfunc.myenabled == true) ? '1' : '0';
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kApplyingChanges') + "...",
+ noBackdrop: true,
+ duration: zm.largeHttpTimeout,
+ });
+
+ var httpPromise = $http(
+ {
+ url: apiMon,
+ method: 'post',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ // console.log("****RETURNING " + foo);
+ NVRDataModel.debug("MonitorCtrl: parmeters constructed: " + foo);
+ return foo;
+ },
+ data:
+ {
+ 'Monitor[Function]': $scope.monfunc.myfunc,
+ 'Monitor[Enabled]': isEnabled,
+ }
+
+ })
+ .success(function()
+ {
+ NVRDataModel.debug("MonitorCtrl: Not restarting ZM - Make sure you have the patch installed in MonitorsController.php or this won't work");
+ })
+ .error(function(data, status, headers, config)
+ {
+ NVRDataModel.debug("MonitorCtrl: Error changing monitor " + JSON.stringify(data));
+ $scope.monfunc.myfailedIds.push(item);
+ });
+
+ $scope.monfunc.mypromises.push(httpPromise);
+ });
+
+ $q.all($scope.monfunc.mypromises).then(function(e)
+ {
+ $ionicLoading.hide();
+ // if there's a failed ID, an error has occurred
+ if ($scope.monfunc.myfailedIds.length != 0)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kErrorChangingMonitors') + ". Monitor IDs : " + $scope.monfunc.myfailedIds.toString(),
+ noBackdrop: true,
+ duration: 3000,
+ });
+ }
+ else
+ {
+ // I am not restarting ZM after monitor change
+ /* NVRDataModel.debug ("MonitorCtrl: Restarting ZM");
+ $ionicLoading.show({
+ template: "Successfully changed Monitor. Please wait, restarting ZoneMinder...",
+ noBackdrop: true,
+ duration: zm.largeHttpTimeout,
+ });
+ $http.post(apiRestart)
+ .then(function (success) {
+ $ionicLoading.hide();
+ var refresh = NVRDataModel.getMonitors(1);
+ refresh.then(function (data) {
+ $scope.monitors = data;
+ $scope.$broadcast('scroll.refreshComplete');
+ });
+
+ },
+ function (error) {
+ $ionicLoading.hide();
+
+ });*/
+ doRefresh();
+ }
+ });
+ }
+
+ }, ]
+ });
+
+ };
+
+ // same logic as EventCtrl.js
+ $scope.finishedLoadingImage = function()
+ {
+ // console.log("***Monitor image FINISHED Loading***");
+ $ionicLoading.hide();
+ };
+
+ $scope.$on('$ionicView.loaded', function()
+ {
+ // console.log("**VIEW ** Monitor Ctrl Loaded");
+ });
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ // console.log("**VIEW ** Monitor Ctrl Entered");
+ NVRDataModel.setAwake(false);
+ $ionicSideMenuDelegate.canDragContent(true);
+ $scope.areImagesLoading = true;
+ });
+
+ $scope.$on('$ionicView.afterEnter', function()
+ {
+ // console.log("**VIEW ** Monitor Ctrl Entered");
+ $scope.monitors = [];
+ $scope.monitors = message;
+
+ //console.log (">>>>>>>>>>>> MONITOR CTRL " + JSON.stringify($scope.monitors));
+
+ if ($scope.monitors.length == 0)
+ {
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kNoMonitors'),
+ template: $translate.instant('kPleaseCheckCredentials')
+ });
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+
+ loginData = NVRDataModel.getLogin();
+ monitorStateCheck();
+ //console.log("Setting Awake to " + NVRDataModel.getKeepAwake());
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+ // Now lets see if we need to load live screen
+
+ // $rootScope.tappedMid = 1;
+ if ($rootScope.tappedMid != 0)
+ {
+ NVRDataModel.log("Notification tapped, we need to go to monitor " + $rootScope.tappedMid);
+
+ var tm = $rootScope.tappedMid;
+ $rootScope.tappedMid = 0;
+ var monitem;
+ for (var m = 0; m < $scope.monitors.length; m++)
+ {
+ if ($scope.monitors[m].Monitor.Id == tm)
+ {
+ monitem = $scope.monitors[m];
+ break;
+ }
+
+ }
+
+ openModal(monitem.Monitor.Id, monitem.Monitor.Controllable, monitem.Monitor.ControlId, monitem.Monitor.connKey, monitem);
+ }
+
+ });
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ // console.log("**VIEW ** Monitor Ctrl Left, force removing modal");
+ if ($scope.modal) $scope.modal.remove();
+ });
+
+ $scope.$on('$ionicView.unloaded', function()
+ {
+ // console.log("**VIEW ** Monitor Ctrl Unloaded");
+ });
+
+ $scope.openModal = function(mid, controllable, controlid, connKey, monitor)
+ {
+
+ openModal(mid, controllable, controlid, connKey, monitor);
+
+ };
+
+ function openModal(mid, controllable, controlid, connKey, monitor)
+ {
+ NVRDataModel.debug("MonitorCtrl:Open Monitor Modal with monitor Id=" + mid +
+ " and Controllable:" + controllable + " with control ID:" + controlid);
+
+ $scope.monitor = monitor;
+ //console.log (">>>>>>>>>>>> MONITOR CRL " + $scope.monitor.
+ $scope.monitorId = mid;
+ $scope.monitorName = NVRDataModel.getMonitorName(mid);
+ $scope.LoginData = NVRDataModel.getLogin();
+ $scope.rand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+ $scope.refMonitor = monitor;
+ NVRDataModel.log("Monitor Orientation is: " + $scope.orientation);
+ $rootScope.rand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+
+ $scope.showPTZ = false;
+ $scope.monitorId = mid;
+ $scope.monitorName = NVRDataModel.getMonitorName(mid);
+ $scope.controlid = controlid;
+
+ $scope.LoginData = NVRDataModel.getLogin();
+ $rootScope.modalRand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+
+ $scope.ptzMoveCommand = "";
+ $scope.ptzStopCommand = "";
+
+ $scope.zoomInCommand = "";
+ $scope.zoomOutCommand = "";
+ $scope.zoomStopCommand = "zoomStop";
+ $scope.canZoom = false;
+
+ $scope.presetOn = false;
+
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ $scope.isControllable = controllable;
+
+ $rootScope.modalRand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+
+ // This is a modal to show the monitor footage
+ // We need to switch to always awake if set so the feed doesn't get interrupted
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ $ionicModal.fromTemplateUrl('templates/monitors-modal.html',
+ {
+ scope: $scope,
+ animation: 'slide-in-up'
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: zm.loadingTimeout
+ });
+ $scope.isModalActive = true;
+ $scope.modal.show();
+ });
+
+ }
+
+ $scope.closeModal = function()
+ {
+ // console.log("Close & Destroy Monitor Modal");
+
+ // stop networking -nph-zms keeps sucking data
+
+ // switch off awake, as liveview is finished
+ NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+ $timeout(function()
+ {
+ NVRDataModel.log("MonitorCtrl:Stopping network pull...");
+ if (NVRDataModel.isForceNetworkStop()) NVRDataModel.stopNetwork("MonitorCtrl-closeModal");
+ }, 300);
+
+ };
+ //Cleanup the modal when we're done with it!
+ $scope.$on('$destroy', function()
+ {
+ //console.log("Destroy Monitor Modal");
+ if ($scope.modal) $scope.modal.remove();
+ });
+
+ //-----------------------------------------------------------------------
+ // Controller Main
+ //-----------------------------------------------------------------------
+
+ function monitorStateCheck()
+ {
+ var apiMonCheck;
+
+ // The status is provided by zmdc.pl
+ // "not running", "pending", "running since", "Unable to connect"
+ var i;
+ for (i = 0; i < $scope.monitors.length; i++)
+ {
+ (function(j)
+ {
+ $scope.monitors[j].Monitor.isRunningText = "...";
+ $scope.monitors[j].Monitor.isRunning = "...";
+ $scope.monitors[j].Monitor.color = zm.monitorCheckingColor;
+ $scope.monitors[j].Monitor.char = "ion-checkmark-circled";
+ apiMonCheck = loginData.apiurl + "/monitors/daemonStatus/id:" + $scope.monitors[j].Monitor.Id + "/daemon:zmc.json";
+
+ //apiMonCheck = apiMonCheck.replace(loginData.url, $scope.monitors[j].Monitor.baseURL);
+
+ // in multiserver replace apiurl with baseurl
+
+ NVRDataModel.debug("MonitorCtrl:monitorStateCheck: " + apiMonCheck);
+ //console.log("**** ZMC CHECK " + apiMonCheck);
+ $http.get(apiMonCheck)
+ .success(function(data)
+ {
+ NVRDataModel.debug("MonitorCtrl: monitor check state returned: " + JSON.stringify(data));
+ if (data.statustext.indexOf("not running") > -1)
+ {
+ $scope.monitors[j].Monitor.isRunning = "false";
+ $scope.monitors[j].Monitor.color = zm.monitorNotRunningColor;
+ $scope.monitors[j].Monitor.char = "ion-close-circled";
+ }
+ else if (data.statustext.indexOf("pending") > -1)
+ {
+ $scope.monitors[j].Monitor.isRunning = "pending";
+ $scope.monitors[j].Monitor.color = zm.monitorPendingColor;
+ }
+ else if (data.statustext.indexOf("running since") > -1)
+ {
+ $scope.monitors[j].Monitor.isRunning = "true";
+ $scope.monitors[j].Monitor.color = zm.monitorRunningColor;
+ }
+ else if (data.statustext.indexOf("Unable to connect") > -1)
+ {
+ $scope.monitors[j].Monitor.isRunning = "false";
+ $scope.monitors[j].Monitor.color = zm.monitorNotRunningColor;
+ $scope.monitors[j].Monitor.char = "ion-close-circled";
+ }
+
+ $scope.monitors[j].Monitor.isRunningText = data.statustext;
+ })
+ .error(function(data)
+ {
+ NVRDataModel.debug("MonitorCtrl: Error->monitor check state returned: " +
+ JSON.stringify(data));
+ NVRDataModel.displayBanner('error', [$translate.instant('kErrorRetrievingState'), $translate.instant('kPleaseTryAgain')]);
+ $scope.monitors[j].Monitor.isRunning = "error";
+ $scope.monitors[j].Monitor.color = zm.monitorErrorColor;
+ $scope.monitors[j].Monitor.char = "ion-help-circled";
+ });
+
+ })(i);
+ }
+ }
+
+ function doRefresh()
+ {
+ $scope.monitors = [];
+
+ var refresh = NVRDataModel.getMonitors(1);
+
+ refresh.then(function(data)
+ {
+ $scope.monitors = data;
+ monitorStateCheck();
+ $scope.$broadcast('scroll.refreshComplete');
+ });
+ }
+
+ $scope.doRefresh = function()
+ {
+ //console.log("***Pull to Refresh");
+ doRefresh();
+
+ };
+
+ }
+ ]);
diff --git a/www/js/MonitorModalCtrl.js b/www/js/MonitorModalCtrl.js
new file mode 100644
index 00000000..01169130
--- /dev/null
+++ b/www/js/MonitorModalCtrl.js
@@ -0,0 +1,1768 @@
+// Common Controller for the montage view
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global saveAs, cordova,StatusBar,angular,console,ionic, moment, imagesLoaded, chrome */
+
+angular.module('zmApp.controllers').controller('MonitorModalCtrl', ['$scope', '$rootScope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$q', '$sce', 'carouselUtils', '$ionicPopup', 'SecuredPopups', '$translate', function($scope, $rootScope, zm, NVRDataModel, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $q, $sce, carouselUtils, $ionicPopup, SecuredPopups, $translate)
+{
+
+ $scope.animationInProgress = false;
+ $scope.imageFit = true;
+ $scope.isModalActive = true;
+ var intervalModalHandle;
+ var cycleHandle;
+ var nphTimer;
+ var ld = NVRDataModel.getLogin();
+ $scope.svgReady = false;
+ $scope.zoneArray = [];
+ var originalZones = [];
+ $scope.isZoneEdit = false;
+ var _moveStart = false;
+ var targetID = "";
+ $scope.imageZoomable = true;
+
+
+ $scope.csize = ($rootScope.platformOS == 'desktop') ? 10:20;
+
+
+ window.addEventListener("resize", function(){imageLoaded();}, false);
+
+
+ $rootScope.authSession = "undefined";
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kNegotiatingStreamAuth') + '...',
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 300,
+ showDelay: 0
+ });
+
+ $scope.currentStreamMode = 'single';
+ NVRDataModel.log("Using stream mode " + $scope.currentStreamMode);
+
+ NVRDataModel.debug("MonitorModalCtrl called from " + $ionicHistory.currentStateName());
+
+ $rootScope.validMonitorId = $scope.monitors[0].Monitor.Id;
+ NVRDataModel.getAuthKey($rootScope.validMonitorId, $scope.monitors[0].Monitor.connKey)
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ $rootScope.authSession = success;
+ NVRDataModel.log("Modal: Stream authentication construction: " + $rootScope.authSession);
+
+ },
+ function(error)
+ {
+
+ $ionicLoading.hide();
+ NVRDataModel.debug("ModalCtrl: Error details of stream auth:" + error);
+ //$rootScope.authSession="";
+ NVRDataModel.log("Modal: Error returned Stream authentication construction. Retaining old value of: " + $rootScope.authSession);
+ });
+
+ $interval.cancel(intervalModalHandle);
+ $interval.cancel(cycleHandle);
+
+ intervalModalHandle = $interval(function()
+ {
+ loadModalNotifications();
+ // console.log ("Refreshing Image...");
+ }.bind(this), 5000);
+
+ $timeout.cancel(nphTimer);
+ nphTimer = $timeout(function()
+ {
+ $scope.currentStreamMode = 'jpeg';
+ NVRDataModel.log("Switching playback via nphzms");
+ }, zm.nphSwitchTimer);
+
+ // This is the PTZ menu
+
+ $scope.ptzRadialMenuOptions = {
+ content: '',
+
+ background: '#2F4F4F',
+ isOpen: true,
+ toggleOnClick: false,
+ button:
+ {
+ cssClass: "fa fa-arrows-alt",
+ },
+ items: [
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'Down');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'DownLeft');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'Left');
+ }
+ },
+ {
+ content: 'D',
+ empty: true,
+
+ onclick: function()
+ {
+ // console.log('About');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'UpLeft');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'Up');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'UpRight');
+ }
+ },
+
+ {
+ content: 'H',
+ empty: true,
+ onclick: function()
+ {
+ //console.log('About');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'Right');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ controlPTZ($scope.monitorId, $scope.ptzMoveCommand + 'DownRight');
+ }
+ },
+
+ {
+ content: 'K',
+ empty: true,
+ onclick: function()
+ {
+ //console.log('About');
+ }
+ },
+ ]
+ };
+
+ //-------------------------------------------------------------
+ // On re-auth, we need a new zms
+ //-------------------------------------------------------------
+
+ $rootScope.$on("auth-success", function()
+ {
+
+ NVRDataModel.debug("MonitorModalCtrl: Re-login detected, resetting everything & re-generating connkey");
+ NVRDataModel.stopNetwork("MonitorModal-auth success");
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+
+ });
+
+ $scope.cast = function(mid, mon) {
+
+ };
+
+ //----------------------------------
+ // toggles monitor cycling
+ //----------------------------------
+ $scope.toggleCycle = function()
+ {
+ //console.log ("HERE");
+ $scope.isCycle = !$scope.isCycle;
+ var ld = NVRDataModel.getLogin();
+ ld.cycleMonitors = $scope.isCycle;
+ NVRDataModel.setLogin(ld);
+ $scope.cycleText = $scope.isCycle ? $translate.instant('kOn') : $translate.instant('kOff');
+
+ if ($scope.isCycle)
+ {
+ NVRDataModel.log("re-starting cycle timer");
+ $interval.cancel(cycleHandle);
+
+ cycleHandle = $interval(function()
+ {
+ moveToMonitor($scope.monitorId, 1);
+ // console.log ("Refreshing Image...");
+ }.bind(this), ld.cycleMonitorsInterval * 1000);
+ }
+ else
+ {
+ NVRDataModel.log("cancelling cycle timer");
+ $interval.cancel(cycleHandle);
+ }
+
+ };
+
+ //-------------------------------------------------------------
+ // PTZ enable/disable
+ //-------------------------------------------------------------
+
+ $scope.togglePTZ = function()
+ {
+
+ //console.log("PTZ");
+
+ if ($scope.isControllable == '1')
+ {
+ //console.log ("iscontrollable is true");
+ $scope.showPTZ = !$scope.showPTZ;
+
+ }
+ else
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPTZnotConfigured'),
+ noBackdrop: true,
+ duration: 3000,
+ });
+ }
+
+ };
+
+ //-------------------------------------------------------------
+ // Pause and resume handlers
+ //-------------------------------------------------------------
+
+ function onPause()
+ {
+ NVRDataModel.debug("ModalCtrl: onpause called");
+ $interval.cancel(intervalModalHandle);
+ $interval.cancel(cycleHandle);
+ // $interval.cancel(modalIntervalHandle);
+
+ // FIXME: Do I need to setAwake(false) here?
+ }
+
+ function onResume()
+ {
+ NVRDataModel.debug("ModalCtrl: Modal resume called");
+ if ($scope.isModalActive)
+ {
+ NVRDataModel.log("ModalCtrl: Restarting Modal timer on resume");
+
+ $interval.cancel(intervalModalHandle);
+ $interval.cancel(cycleHandle);
+
+ var ld = NVRDataModel.getLogin();
+
+ intervalModalHandle = $interval(function()
+ {
+ loadModalNotifications();
+ }.bind(this), 5000);
+
+ if (ld.cycleMonitors)
+ {
+ NVRDataModel.debug("Cycling enabled at " + ld.cycleMonitorsInterval);
+
+ $interval.cancel(cycleHandle);
+
+ cycleHandle = $interval(function()
+ {
+ moveToMonitor($scope.monitorId, 1);
+ // console.log ("Refreshing Image...");
+ }.bind(this), ld.cycleMonitorsInterval * 1000);
+
+ }
+
+ $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1);
+
+ }
+
+ }
+
+ //-------------------------------------------------------------
+ // Queries the 1.30 API for recording state of current monitor
+ //-------------------------------------------------------------
+ function loadModalNotifications()
+ {
+
+ if (NVRDataModel.versionCompare($rootScope.apiVersion, "1.30") == -1)
+ {
+
+ return;
+ }
+
+ if (NVRDataModel.getLogin().enableLowBandwidth)
+ return;
+
+ var status = [$translate.instant('kMonIdle'),
+ $translate.instant('kMonPreAlarm'),
+ $translate.instant('kMonAlarmed'),
+ $translate.instant('kMonAlert'),
+ $translate.instant('kMonRecord')
+ ];
+ //console.log ("Inside Modal timer...");
+ var apiurl = NVRDataModel.getLogin().apiurl;
+ var alarmurl = apiurl + "/monitors/alarm/id:" + $scope.monitorId + "/command:status.json";
+ NVRDataModel.log("Invoking " + alarmurl);
+
+ $http.get(alarmurl)
+ .then(function(data)
+ {
+ // NVRDataModel.debug ("Success in monitor alarmed status " + JSON.stringify(data));
+
+ $scope.monStatus = status[parseInt(data.data.status)];
+
+ },
+ function(error)
+ {
+
+ $scope.monStatus = "";
+ NVRDataModel.debug("Error in monitor alarmed status ");
+ });
+
+ }
+
+ //-------------------------------------------------------------
+ // Enable/Disable preset list
+ //-------------------------------------------------------------
+
+ $scope.togglePresets = function()
+ {
+ $scope.presetOn = !$scope.presetOn;
+
+ if ($scope.presetOn)
+ {
+ $scope.controlToggle = "hide buttons";
+ }
+ else
+ {
+ $scope.controlToggle = "show buttons";
+ }
+ //console.log("Changing preset to " + $scope.presetOn);
+
+ var element = angular.element(document.getElementById("presetlist"));
+ // bring it in
+ if ($scope.presetOn)
+ {
+ element.removeClass("animated fadeOutUp");
+
+ }
+ else
+ {
+ element.removeClass("animated fadeInDown");
+ element.addClass("animated fadeOutUp");
+ }
+
+ };
+
+
+ $scope.saveZones = function()
+ {
+ var str="";
+ for (var i=0; i < originalZones.length; i++)
+ {
+ str = str + "o:"+originalZones[i].coords+"<br/>n:"+$scope.zoneArray[i].coords+"--------------------------------------------------<br/>";
+
+ }
+
+ $rootScope.zmPopup = SecuredPopups.show('confirm',
+ {
+ title: 'Sure',
+ template: str,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+
+ };
+
+ $scope.changeCircleSize = function()
+ {
+ $scope.csize = Math.max (($scope.csize + 5) % 31, 10);
+
+ };
+
+ $scope.toggleZoneEdit = function()
+ {
+ $scope.isZoneEdit = !$scope.isZoneEdit;
+
+
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+
+
+ if ($scope.isZoneEdit)
+ {
+ $ionicScrollDelegate.$getByHandle("imgscroll").zoomTo(1, true);
+ $scope.imageZoomable = false;
+ //document.getElementById("imgscroll").zooming="false";
+
+ for (var i=0; i < $scope.circlePoints.length; i++)
+ {
+ var t = document.getElementById("circle-"+i);
+ if (t)
+ {
+ t.removeEventListener("touchstart",moveStart);
+ t.removeEventListener("mousedown",moveStart);
+ //t.removeEventListener("mousemove",moveContinue);
+ //t.removeEventListener("mouseup",moveStop);
+
+
+ t.addEventListener("touchstart",moveStart);
+ t.addEventListener("mousedown",moveStart);
+ //t.addEventListener("mousemove",moveContinue);
+ //t.addEventListener("mouseup",moveStop);
+
+
+ console.log ("Found circle-"+i);
+ }
+ else
+ {
+ console.log ("did not find circle-"+i);
+ }
+
+ }
+ }
+ else // get out of edit
+ {
+
+ $scope.imageZoomable = true;
+ }
+
+ };
+
+ $scope.toggleZone = function()
+ {
+ $scope.showZones = !$scope.showZones;
+ if (!$scope.showZones)
+ $scope.isZoneEdit = false;
+ };
+
+ $scope.imageLoaded = function()
+ {
+ imageLoaded();
+ };
+
+ $scope.checkZoom = function()
+ {
+ //var z = $ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom;
+ //imageLoaded();
+
+ };
+
+ $scope.circleTouch = function (evt)
+ {
+ console.log ("TOUCH");
+ };
+
+ //$scope.circleOnDrag = function (evt, ndx)
+ function recomputePolygons (ax, ay, ndx,z)
+ {
+
+
+ // we get screen X/Y - need to translate
+ // to SVG points
+ console.log ("recompute with",ax,"&",ay);
+ var svg=document.getElementById('zsvg');
+ var pt = svg.createSVGPoint();
+ pt.x = ax;
+ pt.y = ay;
+ var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
+
+ $scope.circlePoints[ndx].x = Math.round(svgP.x);
+ $scope.circlePoints[ndx].y = Math.round(svgP.y);
+
+ // get related polygon set
+ var zi = $scope.circlePoints[ndx].zoneIndex;
+ var newPoints="";
+ for ( var i=0; i < $scope.circlePoints.length; i++)
+ {
+ if ($scope.circlePoints[i].zoneIndex == zi)
+ {
+ newPoints = newPoints + " " +$scope.circlePoints[i].x+","+$scope.circlePoints[i].y;
+ }
+ console.log ("recomputed polygon:", newPoints);
+ }
+ // console.log ("OLD ZONE FOR:"+zi+" is "+$scope.zoneArray[zi].coords );
+ //console.log ("NEW ZONE FOR:"+zi+" is "+newPoints);
+ $scope.zoneArray[zi].coords = newPoints;
+
+ //console.log ("INDEX="+ndx+" DRAG="+svgP.x+":"+svgP.y);
+
+ }
+
+ // credit: http://stackoverflow.com/questions/41411891/most-elegant-way-to-parse-scale-and-re-string-a-string-of-number-co-ordinates?noredirect=1#41411927
+ // This function scales coords of zones based on current image size
+ function scaleCoords(string, sx, sy) {
+ var f = [sx, sy];
+ return string.split(' ').map(function (a) {
+ return a.split(',').map(function (b, i) {
+ return Math.round(b * f[i]);
+ }).join(',');
+ }).join(' ');
+ }
+
+ function moveContinue(event)
+ {
+ if (!_moveStart) {return;}
+
+ console.log ("CONTINUE: target id="+targetID);
+
+
+ /*if(event.preventDefault) event.preventDefault();
+ if (event.gesture) event.gesture.preventDefault() ;
+ if (event.gesture) event.gesture.stopPropagation();*/
+
+ var x,y;
+
+ var z = $ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom;
+ console.log ("zoom is:"+z);
+
+ //console.log(event, this, "t");
+ if (event.touches)
+ {
+ //console.log ("TOUCH");
+ x = event.targetTouches[0].pageX;
+ y = event.targetTouches[0].pageY;
+
+ }
+ else
+ {
+ //console.log ("MOUSE");
+ x = event.clientX ;
+ y = event.clientY ;
+
+
+ }
+
+
+ console.log ("X="+x+" Y="+y + " sl="+document.body.scrollLeft+ " sy="+document.body.scrollTop);
+ $timeout (function() {recomputePolygons (x,y,targetID,1);});
+
+
+ }
+
+ function moveStop (event)
+ {
+ _moveStart = false;
+ console.log ("STOP");
+ }
+
+ function moveStart(event)
+ {
+
+ _moveStart=true;
+ targetID = event.target.id.substring(7);
+ console.log ("START: target id="+targetID);
+
+ if(event.preventDefault) event.preventDefault();
+ if (event.gesture) event.gesture.preventDefault() ;
+ if (event.gesture) event.gesture.stopPropagation();
+
+ var z = $ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom;
+ console.log ("zoom is:"+z);
+
+ var x,y;
+ // perhaps event.targetTouches[0]?
+ if (event.touches)
+ {
+ //console.log(event.changedTouches[0], this, "t");
+ x = event.touches[0].pageX;
+ y = event.touches[0].pageY;
+
+ }
+ else
+ {
+ //console.log(event, this, "t");
+ x = event.clientX ;
+ y = event.clientY ;
+
+ }
+ console.log ("X="+x+" Y="+y + " sl="+document.body.scrollLeft+ " sy="+document.body.scrollTop);
+
+ }
+
+
+ // called when the live monitor image loads
+ // this is a good time to calculate scaled zone points
+ function imageLoaded()
+ {
+
+ var img =document.getElementById("singlemonitor");
+
+ //$scope.cw = img.naturalWidth;
+ //$scope.ch = img.naturalHeight;
+
+ $scope.cw = img.naturalWidth;
+ $scope.ch = img.naturalHeight;
+
+ //console.log ("REPORTED DIM:" + $scope.cw+ "x"+$scope.ch );
+ //console.log ("ORIGINAL DIM:" + img.naturalWidth+ "x"+img.naturalHeight);
+ //https://server/zm/api/zones/forMonitor/7.json
+ //
+ $scope.zoneArray = [];
+ $scope.circlePoints = [];
+
+ var ow = $scope.monitor.Monitor.Width;
+ var oh = $scope.monitor.Monitor.Height;
+
+ // console.log ("MONITOR IS: "+JSON.stringify($scope.monitor));
+
+ // console.log ("ORIGINAL WH="+ow+"x"+oh);
+
+ for (var i=0; i < originalZones.length; i++)
+ {
+ var sx = $scope.cw/ow;
+ var sy = $scope.ch/oh;
+ //$scope.zoneArray.push({
+ // coords:scaleCoords(originalZones[i].coords,sx,sy),
+ // type:originalZones[i].type});
+ $scope.zoneArray.push({
+ coords:originalZones[i].coords,
+ type:originalZones[i].type});
+
+
+ }
+
+ // now create a points array for circle handles
+ for (i=0; i < $scope.zoneArray.length; i++)
+ {
+ /*jshint loopfunc: true */
+ console.log ("ZONE ARRAY="+$scope.zoneArray[i].coords);
+ $scope.zoneArray[i].coords.split(' ')
+ .forEach( function(itm)
+ {
+ var o=itm.split(',');
+ $scope.circlePoints.push({x:o[0],y:o[1], zoneIndex:i});
+
+ // console.log ("CIRCLE X="+o[0]+"Y="+o[1]);
+ });
+
+ }
+
+
+
+
+
+
+ }
+
+ //-------------------------------------------------------------
+ // this is checked to make sure we are not pulling images
+ // when app is in background. This is a problem with Android,
+ // for example
+ //-------------------------------------------------------------
+
+ $scope.isBackground = function()
+ {
+ // console.log ("Is background called from ModalCtrl and returned " +
+ // NVRDataModel.isBackground());
+ return NVRDataModel.isBackground();
+ };
+
+ //-------------------------------------------------------------
+ // Send PTZ command to ZM
+ // Note: PTZ fails on desktop, don't bother about it
+ //-------------------------------------------------------------
+
+ $scope.controlPTZ = function(monitorId, cmd)
+ {
+ console.log ("PTZ command is"+cmd);
+ controlPTZ(monitorId, cmd);
+ };
+
+ function controlPTZ(monitorId, cmd)
+ {
+
+ //presetGotoX
+ //presetHome
+ //curl -X POST "http://server.com/zm/index.php?view=request" -d
+ //"request=control&user=admin&passwd=xx&id=4&control=moveConLeft"
+
+ if ($scope.ptzMoveCommand=="undefined")
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPTZNotReady'),
+ noBackdrop: true,
+ duration: 2000,
+ });
+ return;
+ }
+
+ var ptzData = "";
+ if (cmd.lastIndexOf("preset", 0) === 0)
+ {
+ NVRDataModel.debug("PTZ command is a preset, so skipping xge/lge");
+ ptzData = {
+ view: "request",
+ request: "control",
+ id: monitorId,
+ control: cmd,
+ // xge: "30", //wtf
+ // yge: "30", //wtf
+ };
+
+ }
+ else
+ {
+
+ ptzData = {
+ view: "request",
+ request: "control",
+ id: monitorId,
+ control: cmd,
+ xge: "30", //wtf
+ yge: "30", //wtf
+ };
+ }
+
+ //console.log("Command value " + cmd + " with MID=" + monitorId);
+ //console.log("PTZDATA is " + JSON.stringify(ptzData));
+ $ionicLoading.hide();
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: zm.loadingTimeout,
+ });
+
+ var loginData = NVRDataModel.getLogin();
+ $ionicLoading.hide();
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSendingPTZ') + "...",
+ noBackdrop: true,
+ duration: zm.loadingTimeout,
+ });
+
+ var req = $http(
+ {
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': 'application/json',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ //console.log("****RETURNING " + foo);
+ return foo;
+ },
+
+ data: ptzData
+
+ });
+
+ req.success(function(resp)
+ {
+ $ionicLoading.hide();
+
+ });
+
+ req.error(function(resp)
+ {
+ $ionicLoading.hide();
+ //console.log("ERROR: " + JSON.stringify(resp));
+ NVRDataModel.log("Error sending PTZ:" + JSON.stringify(resp), "error");
+ });
+ }
+
+ $scope.getZoomLevel = function()
+ {
+ //console.log("ON RELEASE");
+ var zl = $ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition();
+ //console.log(JSON.stringify(zl));
+ };
+
+ $scope.onTap = function(m, d)
+ {
+
+ moveToMonitor(m, d);
+ };
+
+ $scope.onSwipe = function(m, d)
+ {
+ if ($scope.isZoneEdit)
+ {
+ NVRDataModel.log ("swipe disabled as you are in edit mode");
+ return;
+ }
+ var ld = NVRDataModel.getLogin();
+ if (!ld.canSwipeMonitors) return;
+
+ if ($ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom != 1)
+ {
+ //console.log("Image is zoomed in - not honoring swipe");
+ return;
+ }
+ $scope.monStatus = "";
+ moveToMonitor(m, d);
+
+ };
+
+ function moveToMonitor(m, d)
+ {
+
+ if ($scope.isZoneEdit)
+ {
+ NVRDataModel.log ("Not cycling, as you are editing zones");
+ }
+ var curstate = $ionicHistory.currentStateName();
+ var found = 0;
+ var mid;
+ mid = NVRDataModel.getNextMonitor(m, d);
+
+ $scope.showPTZ = false;
+
+ // FIXME: clean this up - in a situation where
+ // no monitors are enabled, will it loop for ever?
+ do {
+ mid = NVRDataModel.getNextMonitor(m, d);
+ m = mid;
+ //console.log("Next Monitor is " + m);
+
+ found = 0;
+ for (var i = 0; i < $scope.monitors.length; i++)
+ {
+ if ($scope.monitors[i].Monitor.Id == mid &&
+ // if you came from monitors, then ignore noshow
+ ($scope.monitors[i].Monitor.listDisplay != 'noshow' || curstate == "monitors") &&
+ $scope.monitors[i].Monitor.Function != 'None' &&
+ $scope.monitors[i].Monitor.Enabled != '0')
+ {
+ found = 1;
+ //console.log(mid + "is part of the monitor list");
+ NVRDataModel.debug("ModalCtrl: swipe detected, moving to " + mid);
+ break;
+ }
+ else
+ {
+ NVRDataModel.debug("skipping " + $scope.monitors[i].Monitor.Id +
+ " listDisplay=" + $scope.monitors[i].Monitor.listDisplay +
+ " Function=" + $scope.monitors[i].Monitor.Function +
+ " Enabled=" + $scope.monitors[i].Monitor.Enabled);
+ }
+ }
+
+ }
+ while (found != 1);
+
+ var slidein;
+ var slideout;
+ var dirn = d;
+ if (dirn == 1)
+ {
+ slideout = "animated slideOutLeft";
+ slidein = "animated slideInRight";
+ }
+ else
+ {
+ slideout = "animated slideOutRight";
+ slidein = "animated slideInLeft";
+ }
+
+ var element = angular.element(document.getElementById("monitorimage"));
+ element.addClass(slideout)
+ .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', outWithOld);
+
+ function outWithOld()
+ {
+
+ NVRDataModel.log("ModalCtrl:Stopping network pull...");
+ NVRDataModel.stopNetwork("MonitorModal-outwithOld");
+ $scope.rand = Math.floor((Math.random() * 100000) + 1);
+ $scope.animationInProgress = true;
+
+ $timeout(function()
+ {
+ element.removeClass(slideout);
+ element.addClass(slidein)
+ .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', inWithNew);
+ $scope.monitorId = mid;
+ $scope.monitorName = NVRDataModel.getMonitorName(mid);
+ $scope.monitor = NVRDataModel.getMonitorObject(mid);
+ $scope.zoneArray=[];
+ $scope.circlePoints=[];
+ getZones();
+ configurePTZ($scope.monitorId);
+ }, 200);
+ }
+
+ function inWithNew()
+ {
+
+ element.removeClass(slidein);
+ $scope.animationInProgress = false;
+
+ NVRDataModel.log("New image loaded in");
+ var ld = NVRDataModel.getLogin();
+ carouselUtils.setStop(false);
+ if (ld.useNphZms == true)
+ {
+ $scope.currentStreamMode = 'single';
+ NVRDataModel.log("Setting timer to play nph-zms mode");
+ // first 5 seconds, load a snapshot, then switch to real FPS display
+ // this is to avoid initial image load delay
+ // FIXME: 5 seconds fair?
+ $timeout.cancel(nphTimer);
+ nphTimer = $timeout(function()
+ {
+ $scope.currentStreamMode = 'jpeg';
+ NVRDataModel.log("Switching playback via nphzms");
+ }, zm.nphSwitchTimer);
+ }
+
+ }
+
+ $ionicLoading.hide();
+
+ }
+
+ //-----------------------------------------------------------------------
+ // Sucess/Error handlers for saving a snapshot of the
+ // monitor image to phone storage
+ //-----------------------------------------------------------------------
+
+ function SaveSuccess()
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDone'),
+ noBackdrop: true,
+ duration: 1000
+ });
+ NVRDataModel.debug("ModalCtrl:Photo saved successfuly");
+ }
+
+ function SaveError(e)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kErrorSave'),
+ noBackdrop: true,
+ duration: 2000
+ });
+ NVRDataModel.log("Error saving image: " + e);
+ //console.log("***ERROR");
+ }
+
+ //-------------------------------------------------------------
+ // Turns on or off an alarm forcibly (mode true = on, false = off)
+ //-------------------------------------------------------------
+ $scope.enableAlarm = function(mid, mode)
+ {
+
+ if (mode) // trigger alarm
+ {
+ $rootScope.zmPopup = SecuredPopups.show('show',
+ {
+ title: 'Confirm',
+ template: $translate.instant('kForceAlarmConfirm') + $scope.monitorName + "?",
+ buttons: [
+ {
+ text: $translate.instant('kButtonYes'),
+ onTap: function(e)
+ {
+ enableAlarm(mid, mode);
+ }
+ },
+ {
+ text: $translate.instant('kButtonNo'),
+ onTap: function(e)
+ {
+ return;
+ }
+ }]
+
+ });
+ }
+ else
+ enableAlarm(mid, mode);
+
+ function enableAlarm(mid, mode)
+ {
+ var apiurl = NVRDataModel.getLogin().apiurl;
+ var c = mode ? "on" : "off";
+ var alarmurl = apiurl + "/monitors/alarm/id:" + mid + "/command:" + c + ".json";
+ NVRDataModel.log("Invoking " + alarmurl);
+
+ var status = mode ? $translate.instant('kForcingAlarm') : $translate.instant('kCancellingAlarm');
+ $ionicLoading.show(
+ {
+ template: status,
+ noBackdrop: true,
+ duration: zm.largeHttpTimeout,
+ });
+
+ $http.get(alarmurl)
+ .then(function(data)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSuccess'),
+ noBackdrop: true,
+ duration: 2000,
+ });
+ },
+ function(error)
+ {
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kAlarmAPIError'),
+ noBackdrop: true,
+ duration: 3000,
+ });
+ NVRDataModel.debug("Error in enableAlarm " + JSON.stringify(error));
+ });
+ }
+
+ };
+
+ //-----------------------------------------------------------------------
+ // color for monitor state
+ //-----------------------------------------------------------------------
+
+ $scope.stateColor = function()
+ {
+ var status = [$translate.instant('kMonIdle'),
+ $translate.instant('kMonPreAlarm'),
+ $translate.instant('kMonAlarmed'),
+ $translate.instant('kMonAlert'),
+ $translate.instant('kMonRecord')
+ ];
+ //console.log ("***MONSTATUS**"+$scope.monStatus+"**");
+ var color = "";
+ switch ($scope.monStatus)
+ {
+ case "":
+ color = "background-color:none";
+ break;
+ case status[0]:
+ color = "background-color:#4B77BE";
+ break;
+ case status[1]:
+ color = "background-color:#e67e22";
+ break;
+ case status[2]:
+ color = "background-color:#D91E18";
+ break;
+ case status[3]:
+ color = "background-color:#e67e22";
+ break;
+ case status[4]:
+ color = "background-color:#26A65B";
+ break;
+ }
+
+ return "padding-left:4px;padding-right:4px;" + color;
+ };
+
+ //-----------------------------------------------------------------------
+ // Saves a snapshot of the monitor image to phone storage
+ //-----------------------------------------------------------------------
+
+ $scope.saveImageToPhoneWithPerms = function(mid)
+ {
+ if ($rootScope.platformOS != 'android')
+ {
+ saveImageToPhone(mid);
+ return;
+ }
+
+ NVRDataModel.debug("ModalCtrl: Permission checking for write");
+ var permissions = cordova.plugins.permissions;
+ permissions.hasPermission(permissions.WRITE_EXTERNAL_STORAGE, checkPermissionCallback, null);
+
+ function checkPermissionCallback(status)
+ {
+ if (!status.hasPermission)
+ {
+ SaveError("No permission to write to external storage");
+ }
+ permissions.requestPermission(permissions.WRITE_EXTERNAL_STORAGE, succ, err);
+ }
+
+ function succ(s)
+ {
+ saveImageToPhone(mid);
+ }
+
+ function err(e)
+ {
+ SaveError("Error in requestPermission");
+ }
+ };
+
+ function saveImageToPhone(mid)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kSavingSnapshot') + '...',
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+ NVRDataModel.debug("ModalCtrl: SaveImageToPhone called");
+ var canvas, context, imageDataUrl, imageData;
+ var loginData = NVRDataModel.getLogin();
+ var url = loginData.streamingurl +
+ '/zms?mode=single&monitor=' + mid +
+ $rootScope.authSession;
+ NVRDataModel.log("SavetoPhone:Trying to save image from " + url);
+
+ var img = new Image();
+ img.onload = function()
+ {
+ // console.log("********* ONLOAD");
+ canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ context = canvas.getContext('2d');
+ context.drawImage(img, 0, 0);
+
+ imageDataUrl = canvas.toDataURL('image/jpeg', 1.0);
+ imageData = imageDataUrl.replace(/data:image\/jpeg;base64,/, '');
+
+ if ($rootScope.platformOS != "desktop")
+ {
+ try
+ {
+
+ cordova.exec(
+ SaveSuccess,
+ SaveError,
+ 'Canvas2ImagePlugin',
+ 'saveImageDataToLibrary', [imageData]
+ );
+ }
+ catch (e)
+ {
+
+ SaveError(e.message);
+ }
+ }
+ else
+ {
+
+ var fname = $scope.monitorName + "-" +
+ moment().format('MMM-DD-YY_HH-mm-ss') + ".png";
+ canvas.toBlob(function(blob)
+ {
+ saveAs(blob, fname);
+ SaveSuccess();
+
+ });
+ }
+ };
+ try
+ {
+ img.src = url;
+ // console.log ("SAVING IMAGE SOURCE");
+ }
+ catch (e)
+ {
+ SaveError(e.message);
+
+ }
+ }
+
+ //-------------------------------------------------------------
+ //reloaads mon - do we need it?
+ //-------------------------------------------------------------
+
+ $scope.reloadView = function()
+ {
+ NVRDataModel.log("Reloading view for modal view, recomputing rand");
+ $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1);
+ $scope.isModalActive = true;
+ };
+
+ $scope.scaleImage = function()
+ {
+
+ $scope.imageFit = !$scope.imageFit;
+ if ($scope.imageFit)
+ $scope.aspectFit="xMidYMid meet";
+ else
+ $scope.aspectFit = "xMidYMid slice";
+
+ // console.log("Switching image style to " + $scope.imageFit);
+ };
+
+ $scope.$on('$ionicView.enter', function()
+ {
+
+ //https://server/zm/api/zones/forMonitor/X.json
+
+ });
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ // console.log("**MODAL: Stopping modal timer");
+ $scope.isModalActive = false;
+ $interval.cancel(intervalModalHandle);
+ $interval.cancel(cycleHandle);
+ });
+
+ $scope.$on('$ionicView.beforeLeave', function()
+ {
+
+ NVRDataModel.log("Nullifying the streams...");
+
+ var element = document.getElementById("singlemonitor");
+ if (element)
+ {
+ NVRDataModel.debug("Nullifying " + element.src);
+ element.src = "";
+ }
+
+ });
+
+ $scope.$on('$ionicView.unloaded', function()
+ {
+ $scope.isModalActive = false;
+
+ $interval.cancel(intervalModalHandle);
+ $interval.cance(cycleHandle);
+
+ });
+
+ $scope.$on('modal.removed', function()
+ {
+ $scope.isModalActive = false;
+ //console.log("**MODAL REMOVED: Stopping modal timer");
+ $interval.cancel(intervalModalHandle);
+ $interval.cancel(cycleHandle);
+
+ NVRDataModel.debug("Modal removed - killing connkey");
+ controlStream(17, "", $scope.connKey, -1);
+
+ // Execute action
+ });
+
+ //-------------------------------------------------------------
+ // called to kill connkey, not sure if we really need it
+ // I think we are calling window.stop() which is a hammer
+ // anyway
+ //-------------------------------------------------------------
+
+ function controlStream(cmd, disp, connkey, ndx)
+ {
+ // console.log("Command value " + cmd);
+
+ if (disp)
+ {
+ $ionicLoading.hide();
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + '...',
+ noBackdrop: true,
+ duration: zm.loadingTimeout,
+ });
+ }
+ var loginData = NVRDataModel.getLogin();
+
+ /*
+ var CMD_NONE = 0;
+ var CMD_PAUSE = 1;
+ var CMD_PLAY = 2;
+ var CMD_STOP = 3;
+ var CMD_FASTFWD = 4;
+ var CMD_SLOWFWD = 5;
+ var CMD_SLOWREV = 6;
+ var CMD_FASTREV = 7;
+ var CMD_ZOOMIN = 8;
+ var CMD_ZOOMOUT = 9;
+ var CMD_PAN = 10;
+ var CMD_SCALE = 11;
+ var CMD_PREV = 12;
+ var CMD_NEXT = 13;
+ var CMD_SEEK = 14;
+ var CMD_QUIT = 17;
+ var CMD_QUERY = 99;
+ */
+
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ var req = $http(
+ {
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ //'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ //console.log("****RETURNING " + foo);
+ return foo;
+ },
+
+ data:
+ {
+ view: "request",
+ request: "stream",
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken,
+
+ }
+ });
+ req.success(function(resp)
+ {
+
+ if (resp.result == "Ok" && ndx != -1)
+ {
+ var ld = NVRDataModel.getLogin();
+ var apiurl = ld.apiurl + "/events/" + resp.status.event + ".json";
+ //console.log ("API " + apiurl);
+ $http.get(apiurl)
+ .success(function(data)
+ {
+ if ($scope.MontageMonitors[ndx].eventUrlTime != data.event.Event.StartTime)
+ {
+
+ var element = angular.element(document.getElementById($scope.MontageMonitors[ndx].Monitor.Id + "-timeline"));
+ element.removeClass('animated slideInRight');
+ element.addClass('animated slideOutRight');
+ $timeout(function()
+ {
+ element.removeClass('animated slideOutRight');
+ element.addClass('animated slideInRight');
+ $scope.MontageMonitors[ndx].eventUrlTime = data.event.Event.StartTime;
+ }, 300);
+
+ }
+
+ })
+ .error(function(data)
+ {
+ $scope.MontageMonitors[ndx].eventUrlTime = "-";
+ });
+
+ }
+
+ });
+
+ req.error(function(resp)
+ {
+ //console.log("ERROR: " + JSON.stringify(resp));
+ NVRDataModel.log("Error sending event command " + JSON.stringify(resp), "error");
+ });
+ }
+
+ $scope.toggleListMenu = function()
+ {
+
+
+ $scope.isToggleListMenu = !$scope.isToggleListMenu;
+ //console.log ("isToggleListMenu:"+$scope.isToggleListMenu);
+ };
+
+ //-------------------------------------------------------------
+ // Zoom in and out via +- for desktops
+ //-------------------------------------------------------------
+ $scope.zoomImage = function(val)
+ {
+
+ if ($scope.isZoneEdit)
+ {
+ $ionicLoading.show(
+ {
+ //template: $translate.instant('kError'),
+ template: 'zoom disabled in zone edit mode',
+ noBackdrop: true,
+ duration: 2000
+ });
+
+ return;
+ }
+ var zl = parseInt($ionicScrollDelegate.$getByHandle("imgscroll").getScrollPosition().zoom);
+ if (zl == 1 && val == -1)
+ {
+ NVRDataModel.debug("Already zoomed out max");
+ return;
+ }
+
+ zl += val;
+ NVRDataModel.debug("Zoom level is " + zl);
+ $ionicScrollDelegate.$getByHandle("imgscroll").zoomTo(zl, true);
+
+ };
+
+ //-------------------------------------------------------------
+ // Retrieves PTZ state for each monitor
+ //-------------------------------------------------------------
+ // make sure following are correct:
+ // $scope.isControllable
+ // $scope.controlid
+ //
+ function configurePTZ(mid)
+ {
+ $scope.presetAndControl = $translate.instant('kMore');
+ $scope.ptzWakeCommand = "";
+ $scope.ptzSleepCommand = "";
+ $scope.ptzResetCommand = "";
+
+ $scope.ptzMoveCommand = "undefined";
+ $scope.ptzStopCommand = "";
+
+ $scope.zoomInCommand = "";
+ $scope.zoomOutCommand = "";
+ $scope.zoomStopCommand = "zoomStop";
+ $scope.canZoom = false;
+
+ $scope.presetOn = true;
+ $scope.controlToggle = "hide buttons";
+
+ NVRDataModel.debug("configurePTZ: called with mid=" + mid);
+ var ld = NVRDataModel.getLogin();
+ var url = ld.apiurl + "/monitors/" + mid + ".json";
+ $http.get(url)
+ .success(function(data)
+ {
+ $scope.isControllable = data.monitor.Monitor.Controllable;
+
+ // *** Only for testing - comment out //
+ //$scope.isControllable = '1';
+ // for testing only
+ // $scope.isControllable = 1;
+ $scope.controlid = data.monitor.Monitor.ControlId;
+ if ($scope.isControllable == '1')
+ {
+
+ var apiurl = NVRDataModel.getLogin().apiurl;
+ var myurl = apiurl + "/controls/" + $scope.controlid + ".json";
+ NVRDataModel.debug("configurePTZ : getting controllable data " + myurl);
+
+ $http.get(myurl)
+ .success(function(data)
+ {
+
+ // *** Only for testing - comment out - start//
+ /*data.Control.Control.CanSleep = '1';
+ data.Control.Control.CanWake = '1';
+ data.Control.Control.CanReset = '1';
+ data.Control.Control.CanZoom = '1';
+ data.control.Control.HasPresets = '1';
+ data.control.Control.HasHomePreset = '1';*/
+ // *** Only for testing - comment out - end //
+
+ $scope.ptzMoveCommand = "move"; // start with as move;
+ $scope.ptzStopCommand = "";
+
+ console.log ("GOT CONTROL "+JSON.stringify(data.control.Control));
+
+ if (data.control.Control.CanZoom == '1')
+ {
+ $scope.canZoom = true;
+ if (data.control.Control.CanZoomCon == '1')
+ {
+ $scope.zoomInCommand = "zoomConTele";
+ $scope.zoomOutCommand = "zoomConWide";
+
+ }
+ else if (data.control.Control.CanZoomRel == '1')
+ {
+ $scope.zoomInCommand = "zoomRelTele";
+ $scope.zoomOutCommand = "zoomRelWide";
+ }
+ else if (data.control.Control.CanZoomAbs == '1')
+ {
+ $scope.zoomInCommand = "zoomRelAbs";
+ $scope.zoomOutCommand = "zoomRelAbs";
+ }
+ }
+
+ NVRDataModel.debug("configurePTZ: control data returned " + JSON.stringify(data));
+
+
+ if (data.control.Control.CanMoveMap == '1')
+ {
+
+ //seems moveMap uses Up/Down/Left/Right,
+ // so no prefix
+ $scope.ptzMoveCommand = "";
+ $scope.ptzStopCommand = "moveStop";
+ console.log ("MoveAbs set");
+ }
+
+ if (data.control.Control.CanMoveAbs == '1')
+ {
+
+ $scope.ptzMoveCommand = "moveAbs";
+ $scope.ptzStopCommand = "moveStop";
+ console.log ("MoveAbs set");
+ }
+
+ if (data.control.Control.CanMoveRel == '1')
+ {
+
+ $scope.ptzMoveCommand = "moveRel";
+ $scope.ptzStopCommand = "moveStop";
+ }
+
+
+
+ // Prefer con over rel if both enabled
+ // I've tested con
+
+ if (data.control.Control.CanMoveCon == '1')
+ {
+
+ $scope.ptzMoveCommand = "moveCon";
+ $scope.ptzStopCommand = "moveStop";
+ }
+ //CanMoveMap
+
+ // presets
+ NVRDataModel.debug("ConfigurePTZ Preset value is " + data.control.Control.HasPresets);
+ $scope.ptzPresets = [];
+
+ if (data.control.Control.HasPresets == '1')
+ {
+ //$scope.presetAndControl = $translate.instant('kPresets');
+
+ $scope.ptzPresetCount = parseInt(data.control.Control.NumPresets);
+ //$scope.ptzPresetCount =80;
+
+ NVRDataModel.debug("ConfigurePTZ Number of presets is " + $scope.ptzPresetCount);
+
+ for (var p = 0; p < $scope.ptzPresetCount; p++)
+ {
+ $scope.ptzPresets.push(
+ {
+ name: (p + 1).toString(),
+ icon: '',
+ cmd: "presetGoto" + (p + 1).toString(),
+ style: 'button-royal'
+ });
+
+ }
+
+ if (data.control.Control.HasHomePreset == '1')
+ {
+ $scope.ptzPresets.unshift(
+ {
+ name: '',
+ icon: "ion-ios-home",
+ cmd: 'presetHome',
+ style: 'button-royal'
+ });
+
+ }
+
+ }
+ /*else
+ {
+ $scope.presetAndControl = $translate.instant('kMore');
+ }*/
+ // lets add these to the end
+ // strictly speaking, they aren't really presets, but meh for now
+
+ // no need to darken these buttons if presets are not there
+ var buttonAccent = "button-dark";
+ if ($scope.ptzPresets.length == 0)
+ {
+ buttonAccent = "";
+ }
+
+ if (data.control.Control.CanWake == '1')
+ {
+
+ $scope.ptzPresets.push(
+ {
+ name: 'W',
+ icon: "ion-eye",
+ cmd: 'wake',
+ style: 'button-royal ' + buttonAccent
+ });
+
+ }
+
+ if (data.control.Control.CanSleep == '1')
+ {
+ $scope.ptzPresets.push(
+ {
+ name: 'S',
+ icon: "ion-eye-disabled",
+ cmd: 'sleep',
+ style: 'button-royal ' + buttonAccent
+ });
+
+ }
+
+ if (data.control.Control.CanReset == '1')
+ {
+ $scope.ptzPresets.push(
+ {
+ name: 'R',
+ icon: "ion-ios-loop-strong",
+ cmd: 'reset',
+ style: 'button-royal ' + buttonAccent
+ });
+
+ }
+
+ NVRDataModel.log("ConfigurePTZ Modal: ControlDB reports PTZ command to be " + $scope.ptzMoveCommand);
+ })
+ .error(function(data)
+ {
+ // console.log("** Error retrieving move PTZ command");
+ NVRDataModel.log("ConfigurePTZ : Error retrieving PTZ command " + JSON.stringify(data), "error");
+ });
+
+ }
+ else
+ {
+ NVRDataModel.log("configurePTZ " + mid + " is not PTZ controllable");
+ }
+ })
+ .error(function(data)
+ {
+ // console.log("** Error retrieving move PTZ command");
+ NVRDataModel.log("configurePTZ : Error retrieving PTZ command " + JSON.stringify(data), "error");
+ });
+
+ }
+
+ function getZones()
+ {
+ //https://server/zm/api/zones/forMonitor/7.json
+ var api = NVRDataModel.getLogin().apiurl+"/zones/forMonitor/"+$scope.monitorId+".json";
+ NVRDataModel.debug ("Getting zones using:"+api);
+ originalZones = [];
+ $http.get (api)
+ .then (function (succ) {
+ console.log (JSON.stringify(succ));
+ for (var i=0; i < succ.data.zones.length; i++)
+ {
+ originalZones.push ({
+ coords:succ.data.zones[i].Zone.Coords,
+ area: succ.data.zones[i].Zone.Area,
+ type:succ.data.zones[i].Zone.Type});
+ }
+
+ },
+ function (err) {
+ NVRDataModel.debug ("Error getting zones :"+JSON.stringify(err));
+
+ });
+
+ }
+
+ $scope.$on('modal.shown', function()
+ {
+
+ $scope.monStatus = "";
+ $scope.isToggleListMenu = true;
+ //console.log (">>>>>>>>>>>>>>>>>>>STOOOP");
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ /*document.addEventListener("mouseup", moveStop, false);
+ document.addEventListener("touchend", moveStop, false);
+
+ document.addEventL`istener("mousemove", moveContinue, false);
+ document.addEventListener("touchmove", moveContinue, false);*/
+
+
+
+
+ $scope.showZones = false;
+
+ getZones();
+
+ var ld = NVRDataModel.getLogin();
+ //currentEvent = $scope.currentEvent;
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ //console.log ("************* GENERATED CONNKEY " + $scope.connKey);
+ $scope.currentFrame = 1;
+ $scope.monStatus = "";
+ $scope.isCycle = ld.cycleMonitors;
+ $scope.cycleText = $scope.isCycle ? $translate.instant('kOn') : $translate.instant('kOff');
+
+ $scope.quality = (NVRDataModel.getBandwidth() == "lowbw") ? zm.monSingleImageQualityLowBW : ld.monSingleImageQuality;
+
+ configurePTZ($scope.monitorId);
+
+ if (ld.cycleMonitors)
+ {
+ NVRDataModel.debug("Cycling enabled at " + ld.cycleMonitorsInterval);
+
+ $interval.cancel(cycleHandle);
+
+ cycleHandle = $interval(function()
+ {
+ moveToMonitor($scope.monitorId, 1);
+ // console.log ("Refreshing Image...");
+ }.bind(this), ld.cycleMonitorsInterval * 1000);
+
+ }
+
+ });
+
+}]);
diff --git a/www/js/MontageCtrl.js b/www/js/MontageCtrl.js
new file mode 100644
index 00000000..e2e26c00
--- /dev/null
+++ b/www/js/MontageCtrl.js
@@ -0,0 +1,2024 @@
+// Controller for the montage view
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,ionic,Packery, Draggabilly, imagesLoaded, ConnectSDK, moment */
+
+angular.module('zmApp.controllers')
+ .controller('zmApp.MontageCtrl', ['$scope', '$rootScope', 'NVRDataModel', 'message', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$ionicPopup', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$ionicPlatform', 'zm', '$ionicPopover', '$controller', 'imageLoadingDataShare', '$window', '$localstorage', '$translate', 'SecuredPopups', function($scope, $rootScope, NVRDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $ionicPopup, $stateParams, $ionicHistory, $ionicScrollDelegate, $ionicPlatform, zm, $ionicPopover, $controller, imageLoadingDataShare, $window, $localstorage, $translate, SecuredPopups)
+ {
+
+ //---------------------------------------------------------------------
+ // Controller main
+ //---------------------------------------------------------------------
+
+ var intervalHandleMontage; // image re-load handler
+ var intervalHandleAlarmStatus; // status of each alarm state
+ var intervalHandleMontageCycle;
+
+ var gridcontainer;
+ var pckry, draggie;
+ var draggies;
+ var loginData;
+ var timestamp;
+ var sizeInProgress;
+ var modalIntervalHandle;
+ var ld;
+ var refreshSec;
+
+ $rootScope.$on("auth-success", function () {
+ NVRDataModel.debug("REAUTH");
+ console.log ("RETAUTH");
+ NVRDataModel.stopNetwork();
+ });
+
+
+ //--------------------------------------------------------------------------------------
+ // Handles bandwidth change, if required
+ //
+ //--------------------------------------------------------------------------------------
+
+ $rootScope.$on("bandwidth-change", function(e, data)
+ {
+ // not called for offline, I'm only interested in BW switches
+ NVRDataModel.debug("Got network change:" + data);
+ var ds;
+ if (data == 'lowbw')
+ {
+ ds = $translate.instant('kLowBWDisplay');
+ }
+ else
+ {
+ ds = $translate.instant('kHighBWDisplay');
+ }
+ NVRDataModel.displayBanner('net', [ds]);
+ var ld = NVRDataModel.getLogin();
+ refreshSec = (NVRDataModel.getBandwidth() == 'lowbw') ? ld.refreshSecLowBW : ld.refreshSec;
+
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleMontageCycle);
+
+
+ intervalHandleMontage = $interval(function()
+ {
+ loadNotifications();
+ }.bind(this), refreshSec * 1000);
+
+ intervalHandleMontageCycle = $interval(function()
+ {
+ cycleMontageProfiles();
+ }.bind(this), NVRDataModel.getLogin().cycleMontageInterval* 1000);
+
+ if (NVRDataModel.getBandwidth() == 'lowbw')
+ {
+ NVRDataModel.debug("Enabling low bandwidth parameters");
+ $scope.LoginData.montageQuality = zm.montageQualityLowBW;
+ $scope.LoginData.singleImageQuality = zm.eventSingleImageQualityLowBW;
+ $scope.LoginData.montageHistoryQuality = zm.montageQualityLowBW;
+
+ }
+ });
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+
+ $ionicPlatform.registerBackButtonAction(function(e)
+ {
+ e.preventDefault();
+ if ($scope.modal != undefined && $scope.modal.isShown())
+ {
+ // switch off awake, as liveview is finished
+ NVRDataModel.debug("Modal is open, closing it");
+ NVRDataModel.setAwake(false);
+ $scope.isModalActive = false;
+ cleanupOnClose();
+ }
+ else
+ {
+ NVRDataModel.debug("Modal is closed, so toggling or exiting");
+ if (!$ionicSideMenuDelegate.isOpenLeft())
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+
+ }
+ else
+ {
+ navigator.app.exitApp();
+ }
+
+ }
+
+ }, 1000);
+
+ /*$scope.toggleHide = function(mon)
+ {
+
+
+ if (mon.Monitor.listDisplay == 'noshow')
+ mon.Monitor.listDisplay = 'show';
+ else
+ mon.Monitor.listDisplay = 'noshow';
+
+
+
+ };*/
+
+ // called by afterEnter to load Packery
+ function initPackery()
+ {
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kArrangingImages'),
+ noBackdrop: true,
+ duration: zm.loadingTimeout
+ });
+
+ var progressCalled = false;
+ draggies = [];
+ var layouttype = true;
+ var ld = NVRDataModel.getLogin();
+
+ var positionsStr = ld.packeryPositions;
+ var positions = {};
+
+ if (positionsStr == '' || positionsStr == undefined)
+ {
+ NVRDataModel.log("Did NOT find a packery layout");
+ layouttype = true;
+ }
+ else
+ {
+
+ //console.log ("POSITION STR IS " + positionsStr);
+ positions = JSON.parse(positionsStr);
+ NVRDataModel.log("found a packery layout");
+
+ layouttype = false;
+ }
+
+ var cnt = 0;
+ $scope.MontageMonitors.forEach(function(elem)
+ {
+ if ((elem.Monitor.Enabled != '0') && (elem.Monitor.Function != 'None') )
+ cnt++;
+ });
+
+ NVRDataModel.log("Monitors that are active and not DOM hidden: " + cnt + " while grid has " + positions.length);
+
+ if (cnt > NVRDataModel.getLogin().maxMontage)
+ {
+ cnt = NVRDataModel.getLogin().maxMontage;
+ NVRDataModel.log("restricting monitor count to " + cnt + " due to max-montage setting");
+ }
+
+ if (cnt != positions.length)
+ {
+
+ NVRDataModel.log("Whoops!! Monitors have changed. I'm resetting layouts, sorry!");
+ layouttype = true;
+ positions = {};
+ }
+
+ var elem = angular.element(document.getElementById("mygrid"));
+
+ //console.log ("**** mygrid is " + JSON.stringify(elem));
+
+ pckry = new Packery('.grid',
+ {
+ itemSelector: '.grid-item',
+ percentPosition: true,
+ columnWidth: '.grid-sizer',
+ gutter: 0,
+ initLayout: layouttype,
+ shiftPercentResize: true
+
+ });
+
+ imagesLoaded(elem).on('progress', function(instance, img)
+ {
+
+ var result = img.isLoaded ? 'loaded' : 'broken';
+ NVRDataModel.debug('~~loaded image is ' + result + ' for ' + img.img.src);
+
+ // lay out every image if a pre-arranged position has not been found
+
+ $timeout(function()
+ {
+ if (layouttype) pckry.layout();
+ }, 100);
+
+ progressCalled = true;
+
+ // if (layouttype) $timeout (function(){layout(pckry);},100);
+ });
+
+ imagesLoaded(elem).on('always', function()
+ {
+ //console.log ("******** ALL IMAGES LOADED");
+ // $scope.$digest();
+ NVRDataModel.debug("All images loaded");
+
+ $scope.areImagesLoading = false;
+
+ $ionicLoading.hide();
+
+ if (!progressCalled)
+ {
+ NVRDataModel.log("*** PROGRESS WAS NOT CALLED");
+ // pckry.reloadItems();
+ }
+
+ $timeout(function()
+ {
+
+ pckry.getItemElements().forEach(function(itemElem)
+ {
+
+ draggie = new Draggabilly(itemElem);
+ pckry.bindDraggabillyEvents(draggie);
+ draggies.push(draggie);
+ draggie.disable();
+ draggie.unbindHandles();
+ });
+
+ pckry.on('dragItemPositioned', itemDragged);
+
+ if (!isEmpty(positions))
+ {
+ NVRDataModel.log("Arranging as per packery grid");
+
+
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ for (var j = 0; j < positions.length; j++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.Id == positions[j].attr)
+ {
+ if (isNaN(positions[j].size)) positions[j].size=20;
+ $scope.MontageMonitors[i].Monitor.gridScale = positions[j].size;
+ $scope.MontageMonitors[i].Monitor.listDisplay = positions[j].display;
+ NVRDataModel.debug("Setting monitor ID: " + $scope.MontageMonitors[i].Monitor.Id + " to size: " + positions[j].size + " and display:" + positions[j].display);
+ }
+ //console.log ("Index:"+positions[j].attr+ " with size: " + positions[j].size);
+ }
+ }
+
+ NVRDataModel.debug("All images loaded, doing image layout");
+
+ }
+ $timeout(function()
+ {
+ //NVRDataModel.log("Force calling resize");
+ ///pckry.reloadItems();
+ ///positions is defined only if layouttype was false
+ console.log (">>> Positions is "+ JSON.stringify(positions));
+ if (!layouttype) pckry.initShiftLayout(positions, "data-item-id");
+ // now do a jiggle
+ $timeout(function()
+ {
+ NVRDataModel.debug("doing the jiggle and dance...");
+ pckry.resize(true);
+ }, 300);
+
+ }, 100);
+
+ //pckry.onresize();
+
+ }, 20);
+
+ });
+
+ function itemDragged(item)
+ {
+ NVRDataModel.debug("drag complete");
+ $timeout (function(){pckry.shiftLayout();},20);
+
+ pckry.once ('layoutComplete', function() {
+
+ var positions = pckry.getShiftPositions('data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.packeryPositions = JSON.stringify(positions);
+ console.log ("Saving " + ld.packeryPositions);
+ // console.log ("FULL OBJECT "+ JSON.stringify(ld));
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName = $translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+ NVRDataModel.debug("saved new positions");
+ });
+
+ //pckry.getItemElements().forEach(function (itemElem) {
+
+ //console.log (itemElem.attributes['data-item-id'].value+" size "+itemElem.attributes['data-item-size'].value );
+ // });
+
+
+ }
+
+ }
+
+ function isEmpty(obj)
+ {
+ for (var prop in obj)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ // color for monitor state in montage
+ //-----------------------------------------------------------------------
+
+ $scope.stateColor = function()
+ {
+ //console.log ("***MONSTATUS**"+$scope.monStatus+"**");
+ var attr = "";
+ switch ($scope.monStatus)
+ {
+ case "":
+ attr = "color:rgba(0, 0, 0, 0)";
+ break;
+ case "idle":
+ attr = "color:rgba(0, 0, 0, 0)";
+ break;
+ case "pre-alarm":
+ attr = "color:#e67e22";
+ break;
+ case "alarmed":
+ attr = "color:#D91E18";
+ break;
+ case "alert":
+ attr = "color:#e67e22";
+ break;
+ case "record":
+ attr = "color:#26A65B";
+ break;
+ }
+
+ return attr;
+ };
+
+
+ function findNext (key,obj)
+ {
+
+ console.log (" key is: "+ key);
+ console.log ("array is " + JSON.stringify (obj));
+ var keys = Object.keys(obj);
+
+ var len = keys.length;
+ var curindex = keys.indexOf(key);
+ var modulus = (curindex + 1) % len;
+
+ console.log ("*********** len="+len+" curr="+curindex+" next="+modulus);
+
+ //console.log ("Keys array "+ JSON.stringify(keys));
+
+ //console.log ("Current index: "+ keys.indexOf(key) );
+ //console.log ("returning index of " + (keys.indexOf(key) + 1) % (keys.length));
+ // console.log ("keys length is "+ keys.length);
+ return keys[modulus];
+
+ /* var size = Object.keys(obj).length;
+ var i;
+ for (i=0; i<size; i++)
+ {
+ if (Object.keys(obj)[i] == key)
+ break;
+ }
+ i = (i + 1) % size;
+ return Object.keys(obj)[i];*/
+
+ }
+
+ //----------------------------------------------
+ // cycle profiles
+ //-----------------------------------------------
+
+ function cycleMontageProfiles()
+ {
+
+ var ld = NVRDataModel.getLogin();
+
+ if (!ld.cycleMontageProfiles)
+ {
+ // NVRDataModel.debug ("cycling disabled");
+ return;
+
+ }
+
+ if ($scope.reOrderActive)
+ {
+ NVRDataModel.debug ("not cycling, re-order in progress");
+ return;
+ }
+
+ if ($scope.isDragabillyOn)
+ {
+ NVRDataModel.debug ("not cycling, edit in progress");
+ return;
+
+ }
+
+ var nextProfile = findNext(ld.currentMontageProfile, ld.packeryPositionsArray);
+
+ if (nextProfile == ld.currentMontageProfile)
+ {
+ NVRDataModel.debug ("Not cycling profiles, looks like you only have one");
+ }
+ else
+ {
+ NVRDataModel.debug ("Cycling profile from: "+ld.currentMontageProfile+" to:"+nextProfile);
+ switchMontageProfile(nextProfile);
+
+ }
+
+
+ }
+
+ //-----------------------------------------------------------------------
+ // cycle through all displayed monitors and check alarm status
+ //-----------------------------------------------------------------------
+
+ function loadAlarmStatus()
+ {
+
+ if ((NVRDataModel.versionCompare($rootScope.apiVersion, "1.30") == -1) ||
+ (NVRDataModel.getBandwidth() == 'lowbw') ||
+ (NVRDataModel.getLogin().disableAlarmCheckMontage == true))
+ {
+
+ return;
+ }
+
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if (($scope.MontageMonitors[i].Monitor.Function == 'None') ||
+ ($scope.MontageMonitors[i].Monitor.Enabled == '0') ||
+ ($scope.MontageMonitors[i].Monitor.listDisplay == 'noshow'))
+ {
+ continue;
+ }
+ getAlarmStatus($scope.MontageMonitors[i]);
+
+ }
+
+ }
+
+ //-----------------------------------------------------------------------
+ // get alarm status over HTTP for a single monitor
+ //-----------------------------------------------------------------------
+ function getAlarmStatus(monitor)
+ {
+ var apiurl = NVRDataModel.getLogin().apiurl;
+ //console.log ("ALARM CALLED WITH " +JSON.stringify(monitor));
+
+ var alarmurl = apiurl + "/monitors/alarm/id:" + monitor.Monitor.Id + "/command:status.json";
+ // console.log("Alarm Check: Invoking " + alarmurl);
+
+ $http.get(alarmurl)
+ .then(function(data)
+ {
+ // NVRDataModel.debug ("Success in monitor alarmed status " + JSON.stringify(data));
+
+ var sid = parseInt(data.data.status);
+ switch (sid)
+ {
+ case 0: // idle
+ monitor.Monitor.alarmState = 'color:rgba(0,0,0,0);';
+ break;
+ case 1: // pre alarm
+ monitor.Monitor.alarmState = 'color:#e67e22;';
+ break;
+ case 2: // alarm
+ monitor.Monitor.alarmState = 'color:#D91E18;';
+ break;
+ case 3: // alert
+ monitor.Monitor.alarmState = 'color:#e67e22;';
+ break;
+ case 4:
+ monitor.Monitor.alarmState = 'color:#26A65B;';
+ break;
+
+ }
+
+ },
+ function(error)
+ {
+
+ monitor.Monitor.alarmState = 'color:rgba(0,0,0,0);';
+ NVRDataModel.debug("Error in monitor alarmed status ");
+ });
+ }
+
+ function randEachTime() {
+ $scope.randToAvoidCacheMem = new Date().getTime();
+ //$scope.randToAvoidCacheMem = "1";
+ //console.log ("Generating:"+$scope.randToAvoidCacheMem);
+ }
+
+ //-----------------------------------------------------------------------
+ // re-compute rand so snapshot in montage reloads
+ //-----------------------------------------------------------------------
+
+ function loadNotifications()
+ {
+
+ randEachTime();
+ //console.log ($scope.randToAvoidCacheMem);
+
+ if ($scope.areImagesLoading)
+ {
+ NVRDataModel.debug("skipping image refresh, packery is still loading");
+ return;
+ }
+
+ //if (pckry && !$scope.isDragabillyOn) pckry.shiftLayout();
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+
+ // if you see the time move, montage should move
+
+ if ($scope.iconTimeNow == 'local')
+ $scope.timeNow = moment().format(NVRDataModel.getTimeFormatSec());
+ else
+ $scope.timeNow = moment().tz(NVRDataModel.getTimeZoneNow()).format(NVRDataModel.getTimeFormatSec());
+ //$scope.timeNow = moment().format(NVRDataModel.getTimeFormatSec());
+
+ //console.log ("Inside Montage timer...");
+
+ }
+
+ $scope.cancelReorder = function()
+ {
+ $scope.modal.remove();
+ };
+
+ $scope.saveReorder = function()
+ {
+ NVRDataModel.debug("Saving monitor hide/unhide");
+
+ // redo packery as monitor status has changed
+ // DOM may need reloading if you've hidden/unhidden stuff
+ $scope.MontageMonitors = $scope.copyMontage;
+ $scope.modal.remove();
+
+ $timeout(function()
+ {
+
+ draggies.forEach(function(drag)
+ {
+ drag.destroy();
+ });
+
+ pckry.reloadItems();
+ draggies = [];
+ pckry.once('layoutComplete', savePackeryOrder);
+ pckry.layout();
+
+ }, 400);
+
+ };
+
+ function savePackeryOrder()
+ {
+ $timeout(function()
+ {
+ var positions = pckry.getShiftPositions('data-item-id');
+ NVRDataModel.debug("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.packeryPositions = JSON.stringify(positions);
+ //console.log ("Savtogging " + ld.packeryPositions);
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName = $translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+
+ pckry.getItemElements().forEach(function(itemElem)
+ {
+ draggie = new Draggabilly(itemElem);
+ pckry.bindDraggabillyEvents(draggie);
+ draggies.push(draggie);
+ draggie.disable();
+ });
+
+ $ionicScrollDelegate.$getByHandle("montage-delegate").scrollTop();
+
+ // Now also ask DataModel to update its monitor display status
+ NVRDataModel.reloadMonitorDisplayStatus();
+ //$scope.MontageMonitors = angular.copy(NVRDataModel.getMonitorsNow());
+ //$scope.MontageMonitors = NVRDataModel.getMonitorsNow();
+ pckry.layout();
+ }, 20);
+ }
+
+ $scope.getCycleStatus = function()
+ {
+ var c = NVRDataModel.getLogin().cycleMontageProfiles;
+ var str = (c) ? $translate.instant('kOn'):$translate.instant('kOff');
+ return str;
+ };
+
+ $scope.toggleCycle = function()
+ {
+ var ld = NVRDataModel.getLogin();
+ ld.cycleMontageProfiles = !ld.cycleMontageProfiles;
+ NVRDataModel.setLogin(ld);
+ NVRDataModel.debug ("cycle="+ld.cycleMontageProfiles);
+ NVRDataModel.debug ("cycle interval="+ld.cycleMontageInterval);
+
+
+ };
+
+ $scope.toggleHide = function(i)
+ {
+
+ if ($scope.copyMontage[i].Monitor.listDisplay == 'show')
+ $scope.copyMontage[i].Monitor.listDisplay = 'noshow';
+ else
+ $scope.copyMontage[i].Monitor.listDisplay = 'show';
+
+ NVRDataModel.debug("index " + i + " is now " + $scope.copyMontage[i].Monitor.listDisplay);
+ };
+
+ $scope.hideUnhide = function()
+ {
+ if ($scope.isDragabillyOn)
+ {
+ dragToggle();
+ }
+ // make a copy of the current list and work on that
+ // this is to avoid packery screw ups while you are hiding/unhiding
+ $scope.copyMontage = angular.copy($scope.MontageMonitors);
+ $ionicModal.fromTemplateUrl('templates/reorder-modal.html',
+ {
+ scope: $scope,
+ animation: 'slide-in-up',
+ id:'reorder',
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+ $scope.reOrderActive = true;
+ $scope.modal.show();
+ });
+ };
+
+
+ $scope.$on('modal.removed', function(e, m)
+ {
+
+ if (m.id != 'reorder')
+ return;
+ $scope.reOrderActive = false;
+
+ //console.log ("************** FOOTAGE CLOSED");
+
+ });
+
+ /*
+ $scope.closeReorderModal = function () {
+
+ $scope.modal.remove();
+
+ };
+ */
+
+ //----------------------------------------------------------------
+ // Alarm emit handling
+ //----------------------------------------------------------------
+ $rootScope.$on("alarm", function(event, args)
+ {
+ // FIXME: I should probably unregister this instead
+ if (typeof $scope.monitors === undefined)
+ return;
+ //console.log ("***EVENT TRAP***");
+ var alarmMonitors = args.message;
+ for (var i = 0; i < alarmMonitors.length; i++)
+ {
+ //console.log ("**** TRAPPED EVENT: "+alarmMonitors[i]);
+
+ for (var j = 0; j < $scope.MontageMonitors.length; j++)
+ {
+ if ($scope.MontageMonitors[j].Monitor.Id == alarmMonitors[i])
+ {
+ NVRDataModel.debug("Enabling alarm for Monitor:" + $scope.monitors[j].Monitor.Id);
+ $scope.MontageMonitors[j].Monitor.isAlarmed = true;
+ scheduleRemoveFlash(j);
+ }
+ }
+
+ }
+
+ });
+
+ function scheduleRemoveFlash(id)
+ {
+ NVRDataModel.debug("Scheduled a " + zm.alarmFlashTimer + "ms timer for dis-alarming monitor ID:" + $scope.MontageMonitors[id].Monitor.Id);
+ $timeout(function()
+ {
+ $scope.MontageMonitors[id].Monitor.isAlarmed = false;
+ NVRDataModel.debug("dis-alarming monitor ID:" + $scope.MontageMonitors[id].Monitor.Id);
+ }, zm.alarmFlashTimer);
+ }
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ $scope.handleAlarmsWhileMinimized = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+
+ $scope.minimal = !$scope.minimal;
+ NVRDataModel.debug("MontageCtrl: switch minimal is " + $scope.minimal);
+ ionic.Platform.fullScreen($scope.minimal, !$scope.minimal);
+ //console.log ("alarms:Cancelling timer");
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleMontageCycle);
+ $interval.cancel(intervalHandleAlarmStatus);
+
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //-------------------------------------------------------------
+ // this is checked to make sure we are not pulling images
+ // when app is in background. This is a problem with Android,
+ // for example
+ //-------------------------------------------------------------
+
+ $scope.isBackground = function()
+ {
+ //console.log ("Is background called from Montage and returned " +
+ //NVRDataModel.isBackground());
+ return NVRDataModel.isBackground();
+ };
+
+ //---------------------------------------------------------------------
+ // Triggered when you enter/exit full screen
+ //---------------------------------------------------------------------
+ $scope.switchMinimal = function()
+ {
+ $scope.minimal = !$scope.minimal;
+ NVRDataModel.debug("MontageCtrl: switch minimal is " + $scope.minimal);
+ // console.log("Hide Statusbar");
+ ionic.Platform.fullScreen($scope.minimal, !$scope.minimal);
+ //console.log ("minimal switch:Cancelling timer");
+ $interval.cancel(intervalHandleMontage); //we will renew on reload
+ $interval.cancel(intervalHandleMontageCycle);
+ $interval.cancel(intervalHandleAlarmStatus);
+ // We are reloading this view, so we don't want entry animations
+ $ionicHistory.nextViewOptions(
+ {
+ disableAnimate: true,
+ disableBack: true
+ });
+ $state.go("montage",
+ {
+ minimal: $scope.minimal,
+ isRefresh: true
+ });
+ return;
+ };
+
+ //---------------------------------------------------------------------
+ // Show/Hide PTZ control in monitor view
+ //---------------------------------------------------------------------
+ $scope.togglePTZ = function()
+ {
+ $scope.showPTZ = !$scope.showPTZ;
+ };
+
+
+ function getIndex (mid)
+ {
+ var ndx = 0;
+ for (var i=0; i< $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.Id == mid)
+ {
+ ndx = i;
+ break;
+ }
+ }
+ return ndx;
+
+ }
+
+ $scope.toggleStamp = function ()
+ {
+ if (!$scope.isDragabillyOn) return;
+ var found = false;
+
+
+ for (var i=0; i< $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == 'dragborder-selected')
+ {
+
+ findPackeryElement(i);
+ }
+ }
+
+ function findPackeryElement(i)
+ {
+ pckry.getItemElements().forEach(function(elem)
+ {
+
+ var id = elem.getAttribute("data-item-id");
+ if (id == $scope.MontageMonitors[i].Monitor.Id)
+ {
+ if ($scope.MontageMonitors[i].Monitor.isStamp)
+ pckry.unstamp(elem);
+ else
+ pckry.stamp(elem);
+
+ $scope.MontageMonitors[i].Monitor.isStamp = !$scope.MontageMonitors[i].Monitor.isStamp;
+ NVRDataModel.debug ("Stamp for "+$scope.MontageMonitors[i].Monitor.Name + " is:"+$scope.MontageMonitors[i].Monitor.isStamp );
+ //break;
+
+ }
+ });
+ }
+
+
+ };
+
+ $scope.hideMonitor = function (mid)
+ {
+ if (!$scope.isDragabillyOn) return;
+ var found = false;
+ for (var i=0; i< $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == 'dragborder-selected')
+ {
+ $scope.MontageMonitors[i].Monitor.listDisplay = 'noshow';
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ found = true;
+ }
+
+ }
+ if (found)
+ {
+ pckry.once ('layoutComplete', saveUpdatedLayout);
+ $timeout (function() {pckry.shiftLayout();},300);
+ }
+
+ function saveUpdatedLayout()
+ {
+ $timeout(function()
+ {
+ var positions = pckry.getShiftPositions('data-item-id');
+ console.log("SAVING");
+ var ld = NVRDataModel.getLogin();
+
+ ld.packeryPositions = JSON.stringify(positions);
+ //console.log ("Saving " + ld.packeryPositions);
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName = $translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+ $ionicLoading.hide();
+ //$scope.sliderChanging = false;
+ }, 20);
+ }
+
+ };
+
+ $scope.toggleSelectItem = function(mid)
+ {
+ var ndx = getIndex(mid);
+ //console.log ("TOGGLE DETECTED AT INDEX:"+ndx+" NAME="+$scope.MontageMonitors[ndx].Monitor.Name);
+ if ($scope.MontageMonitors[ndx].Monitor.selectStyle !== "undefined" && $scope.MontageMonitors[ndx].Monitor.selectStyle == "dragborder-selected")
+ {
+ $scope.MontageMonitors[ndx].Monitor.selectStyle = "";
+ }
+ else
+ {
+ $scope.MontageMonitors[ndx].Monitor.selectStyle = "dragborder-selected";
+ }
+ //console.log ("Switched value to " + $scope.MontageMonitors[ndx].Monitor.selectStyle);
+ };
+
+ //---------------------------------------------------------------------
+ // Called when you enable/disable dragging
+ //---------------------------------------------------------------------
+
+ $scope.dragToggle = function()
+ {
+ dragToggle();
+
+ };
+
+ function dragToggle()
+ {
+ var i;
+ $scope.isDragabillyOn = !$scope.isDragabillyOn;
+
+ for ( i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.isStamp = false;
+ }
+
+ $ionicSideMenuDelegate.canDragContent($scope.isDragabillyOn ? false : true);
+
+ //$timeout(function(){pckry.reloadItems();},10);
+ NVRDataModel.debug("setting dragabilly to " + $scope.isDragabillyOn);
+ if ($scope.isDragabillyOn)
+ {
+ $scope.toggleSubMenu = true;
+
+ $scope.dragBorder = "dragborder";
+ NVRDataModel.debug("Enabling drag for " + draggies.length + " items");
+ for (i = 0; i < draggies.length; i++)
+ {
+ draggies[i].enable();
+ draggies[i].bindHandles();
+ }
+
+ // reflow and reload as some may be hidden
+ // $timeout(function(){pckry.reloadItems();$timeout(function(){pckry.layout();},300);},100);
+ }
+ else
+ {
+ $scope.dragBorder = "";
+ NVRDataModel.debug("Disabling drag for " + draggies.length + " items");
+ for (i = 0; i < draggies.length; i++)
+ {
+ draggies[i].disable();
+ draggies[i].unbindHandles();
+ }
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ }
+ // reflow and reload as some may be hidden
+ $timeout(function()
+ {
+ $timeout(function()
+ {
+ var positions = pckry.getShiftPositions('data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.packeryPositions = JSON.stringify(positions);
+ //console.log ("Saving " + ld.packeryPositions);
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName =$translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+ }, 300);
+ }, 100);
+
+ }
+ }
+
+ //---------------------------------------------------------------------
+ // main monitor modal open - if drag is not on, this is called on touch
+ //---------------------------------------------------------------------
+
+ $scope.openModal = function(mid, controllable, controlid, connKey, monitor)
+ {
+ openModal(mid, controllable, controlid, connKey, monitor);
+ };
+
+ function openModal(mid, controllable, controlid, connKey, monitor)
+ {
+ NVRDataModel.debug("MontageCtrl: Open Monitor Modal with monitor Id=" + mid + " and Controllable:" + controllable + " with control ID:" + controlid);
+ // $scope.isModalActive = true;
+ // Note: no need to setAwake(true) as its already awake
+ // in montage view
+
+ NVRDataModel.log("Cancelling montage timer, opening Modal");
+ // NVRDataModel.log("Starting Modal timer");
+ //console.log ("openModal:Cancelling timer");
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleMontageCycle);
+ $interval.cancel(intervalHandleAlarmStatus);
+
+ $scope.monitor = monitor;
+ $scope.showPTZ = false;
+ $scope.monitorId = mid;
+ $scope.monitorName = NVRDataModel.getMonitorName(mid);
+ $scope.controlid = controlid;
+
+ //$scope.LoginData = NVRDataModel.getLogin();
+ $rootScope.modalRand = Math.floor(Math.random() * (999999 - 111111 + 1)) + 111111;
+
+ $scope.ptzMoveCommand = "";
+ $scope.ptzStopCommand = "";
+
+ $scope.zoomInCommand = "";
+ $scope.zoomOutCommand = "";
+ $scope.zoomStopCommand = "zoomStop";
+ $scope.canZoom = false;
+
+ $scope.presetOn = false;
+
+ $scope.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ $scope.isControllable = controllable;
+ $scope.refMonitor = monitor;
+
+ // This is a modal to show the monitor footage
+ // We need to switch to always awake if set so the feed doesn't get interrupted
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ // This is a modal to show the monitor footage
+ $ionicModal.fromTemplateUrl('templates/monitors-modal.html',
+ {
+ scope: $scope,
+ animation: 'slide-in-up',
+ id: 'monitorsmodal'
+
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait'),
+ noBackdrop: true,
+ duration: zm.loadingTimeout
+ });
+
+ // we don't really need this as we have stopped the timer
+ // $scope.isModalActive = true;
+
+ //$timeout (function() {pckry.shiftLayout();},zm.packeryTimer);
+ $scope.modal.show();
+
+ });
+
+ }
+
+ //---------------------------------------------------------------------
+ //
+ //---------------------------------------------------------------------
+
+ function cleanupOnClose()
+ {
+ $scope.modal.remove();
+ $timeout(function()
+ {
+ NVRDataModel.log("MontageCtrl:Stopping network pull...");
+ if (NVRDataModel.isForceNetworkStop()) NVRDataModel.stopNetwork();
+ }, 50);
+
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $scope.isModalActive = false;
+
+ NVRDataModel.log("Restarting montage timer, closing Modal...");
+ var ld = NVRDataModel.getLogin();
+ // console.log ("closeModal: Cancelling timer");
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleAlarmStatus);
+ $interval.cancel(intervalHandleMontageCycle);
+
+ intervalHandleMontage = $interval(function()
+ {
+ loadNotifications();
+ // console.log ("Refreshing Image...");
+ }.bind(this), refreshSec * 1000);
+
+ intervalHandleAlarmStatus = $interval(function()
+ {
+ loadAlarmStatus();
+ // console.log ("Refreshing Image...");
+ }.bind(this), 5000);
+
+ intervalHandleMontageCycle = $interval(function()
+ {
+ cycleMontageProfiles();
+ // console.log ("Refreshing Image...");
+ }.bind(this), 5000);
+
+
+ // $timeout (function() {pckry.shiftLayout();},zm.packeryTimer);
+
+ }
+
+ $scope.closeModal = function()
+ {
+ NVRDataModel.debug("MontageCtrl: Close & Destroy Monitor Modal");
+ cleanupOnClose();
+ // $scope.isModalActive = false;
+ // Note: no need to setAwake(false) as needs to be awake
+ // in montage view
+
+ };
+
+ //---------------------------------------------------------------------
+ // In Android, the app runs full steam while in background mode
+ // while in iOS it gets suspended unless you ask for specific resources
+ // So while this view, we DON'T want Android to keep sending 1 second
+ // refreshes to the server for images we are not seeing
+ //---------------------------------------------------------------------
+
+ function onPause()
+ {
+ NVRDataModel.debug("MontageCtrl: onpause called");
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleMontageCycle);
+ $interval.cancel(intervalHandleAlarmStatus);
+ // $interval.cancel(modalIntervalHandle);
+
+ // FIXME: Do I need to setAwake(false) here?
+ }
+
+ function onResume()
+ {
+
+ }
+
+ $scope.openMenu = function()
+ {
+ $timeout(function()
+ {
+ $rootScope.stateofSlide = $ionicSideMenuDelegate.isOpen();
+ }, 500);
+
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.$on('$destroy', function() {
+
+ });
+
+ $scope.$on('$ionicView.loaded', function()
+ {
+ // console.log("**VIEW ** Montage Ctrl Loaded");
+ });
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ // console.log("**VIEW ** Montage Ctrl Left, force removing modal");
+ if ($scope.modal) $scope.modal.remove();
+ });
+
+ function orientationChanged()
+ {
+
+ }
+
+
+ // remove a saved montage profile
+ $scope.deleteMontageProfile = function()
+ {
+ var posArray;
+
+ try
+ {
+ posArray = NVRDataModel.getLogin().packeryPositionsArray;
+ //console.log ("PA="+JSON.stringify(posArray));
+
+ }
+ catch (e)
+ {
+ NVRDataModel.debug("error parsing packery array positions");
+ posArray = {};
+ }
+
+ //console.log ("posArray="+JSON.stringify(posArray));
+
+ $scope.listdata = [];
+ for (var key in posArray)
+ {
+ if (posArray.hasOwnProperty(key))
+ {
+ $scope.listdata.push(key);
+ }
+ }
+
+ if (!$scope.listdata.length)
+ {
+
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kMontageNoSavedProfiles'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ return;
+ }
+
+ $scope.data = {
+ 'selectedVal': ''
+ };
+
+ $rootScope.zmPopup = SecuredPopups.show('confirm',
+ {
+ template: '<ion-list> ' +
+ ' <ion-radio-fix ng-repeat="item in listdata" ng-value="item" ng-model="data.selectedVal"> ' +
+ ' {{item}} ' +
+ ' </ion-item> ' +
+ '</ion-list> ',
+
+ title: $translate.instant('kSelect'),
+ subTitle:$translate.instant('kSelectDelete'),
+ scope: $scope,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+
+ }).then(function(res)
+ {
+ NVRDataModel.debug("Deleting profile: " + $scope.data.selectedVal);
+ delete posArray[$scope.data.selectedVal];
+ var ld = NVRDataModel.getLogin();
+ ld.packeryPositionsArray = posArray;
+
+ if (ld.currentMontageProfile == $scope.data.selectedVal)
+ {
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName = $translate.instant ('kMontage');
+
+ }
+
+ if ($scope.currentMontageProfile == $scope.data.selectedVal)
+ $scope.currentProfileName = $translate.instant('kMontage');
+
+ NVRDataModel.setLogin(ld);
+
+ });
+
+ };
+
+
+ function switchMontageProfile (mName)
+ {
+ $interval.cancel(intervalHandleMontageCycle);
+ intervalHandleMontageCycle = $interval(function()
+ {
+ cycleMontageProfiles();
+ // console.log ("Refreshing Image...");
+ }.bind(this), NVRDataModel.getLogin().cycleMontageInterval* 1000);
+
+
+ //console.log ("SELECTED " + $scope.data.selectedVal);
+ var ld = NVRDataModel.getLogin();
+ //console.log ("OLD POS="+ld.packeryPositions);
+ ld.packeryPositions = ld.packeryPositionsArray[mName];
+ ld.currentMontageProfile = mName;
+ $scope.currentProfileName =mName;
+ //console.log ("NEW POS="+ld.packeryPositions);
+ NVRDataModel.setLogin(ld);
+ //console.log ("SAVING "+ld.packeryPositions.name+ " but "+$scope.data.selectedVal);
+
+ //$scope.MontageMonitors = angular.copy(NVRDataModel.getMonitorsNow());
+
+
+ draggies.forEach(function(drag)
+ {
+ drag.destroy();
+ });
+ draggies = [];
+ pckry.destroy();
+ NVRDataModel.reloadMonitorDisplayStatus();
+ $scope.MontageMonitors = NVRDataModel.getMonitorsNow();
+ $timeout (function() {initPackery();},zm.packeryTimer);
+
+
+
+ }
+ // switch to another montage profile
+ $scope.switchMontageProfile = function()
+ {
+ var posArray;
+
+ try
+ {
+ posArray = NVRDataModel.getLogin().packeryPositionsArray;
+ //console.log ("PA="+JSON.stringify(posArray));
+
+ }
+ catch (e)
+ {
+ NVRDataModel.debug("error parsing packery array positions");
+ posArray = {};
+ }
+
+ //console.log ("posArray="+JSON.stringify(posArray));
+
+ $scope.listdata = [];
+ for (var key in posArray)
+ {
+ if (posArray.hasOwnProperty(key))
+ {
+ $scope.listdata.push(key);
+ }
+ }
+ if ($scope.listdata.indexOf($translate.instant('kMontageDefaultProfile')) == -1)
+ $scope.listdata.push($translate.instant('kMontageDefaultProfile'));
+
+ if (!$scope.listdata.length)
+ {
+
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kMontageNoSavedProfiles'),
+
+ });
+ return;
+ }
+
+ $scope.data = {
+ 'selectedVal': ''
+ };
+
+ $rootScope.zmPopup = SecuredPopups.show('confirm',
+ {
+ template: '<ion-list> ' +
+ ' <ion-radio-fix ng-repeat="item in listdata" ng-value="item" ng-model="data.selectedVal"> ' +
+ ' {{item}} ' +
+ ' </ion-item> ' +
+ '</ion-list> ',
+
+ title: $translate.instant('kSelect'),
+ subTitle:$translate.instant('kSelectSwitch'),
+ scope: $scope,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+
+
+ }).then(function(res)
+ {
+ if (res)
+ {
+ // destroy cycle timer and redo it
+ //
+ switchMontageProfile($scope.data.selectedVal);
+
+
+ //pckry.reloadItems();
+ }
+
+ });
+
+ };
+
+ // save current configuration into a profile
+ $scope.saveMontageProfile = function()
+ {
+
+ var posArray;
+
+ try
+ {
+ posArray = NVRDataModel.getLogin().packeryPositionsArray;
+ //console.log ("PA="+JSON.stringify(posArray));
+
+ }
+ catch (e)
+ {
+ NVRDataModel.debug("error parsing packery array positions");
+ posArray = {};
+ }
+ $scope.data = {
+ montageName: ""
+ };
+
+ $scope.listdata = [];
+ for (var key in posArray)
+ {
+ if (posArray.hasOwnProperty(key))
+ {
+ $scope.listdata.push(key);
+ }
+ }
+ if ($scope.listdata.indexOf($translate.instant('kMontageDefaultProfile')) == -1)
+ $scope.listdata.push($translate.instant('kMontageDefaultProfile'));
+
+
+ var templ = "<input autocapitalize='none' autocomplete='off' autocorrect='off' type='text' ng-model='data.montageName'>";
+
+ if ($scope.listdata.length)
+ templ += '<br/><div class="item item-divider">'+$translate.instant('kMontageSavedProfiles')+'</div>'+
+ '<ion-list> ' +
+ ' <ion-radio-fix ng-repeat="item in listdata" ng-value="item" ng-model="data.montageName"> ' +
+ ' {{item}} ' +
+ ' </ion-item> ' +
+ '</ion-list> ';
+
+
+ $rootScope.zmPopup = SecuredPopups.show('confirm',
+ {
+ title: $translate.instant('kMontageSave'),
+ template: templ,
+ subTitle: $translate.instant('kMontageSaveSubtitle'),
+ scope: $scope,
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+
+ }).then(function(res)
+ {
+ console.log(res);
+ if (res) // ok
+ {
+
+ var ld = NVRDataModel.getLogin();
+
+ if ($scope.data.montageName != '')
+ {
+ // lets allow them to save default
+ //if ($scope.data.montageName != $translate.instant('kMontageDefaultProfile'))
+ if (1)
+ {
+ var getMonPos = pckry.getShiftPositions('data-item-id');
+ var unHidden = false;
+
+ // if you are saving to default all monitor profile
+ // then I will undo any hidden monitors
+ if ($scope.data.montageName == $translate.instant('kMontageDefaultProfile'))
+ {
+ for ( var p=0; p < getMonPos.length; p++)
+ {
+ //console.log ("CHECK");
+ if (getMonPos[p].display != 'show')
+ {
+ getMonPos[p].display = 'show';
+ unHidden = true;
+ }
+ }
+ }
+
+ var pos = JSON.stringify(getMonPos);
+
+ //console.log ("SAVING POS = "+pos);
+
+ ld.packeryPositionsArray[$scope.data.montageName] = pos;
+ NVRDataModel.debug("Saving " + $scope.data.montageName + " with:" + pos);
+ ld.currentMontageProfile =$scope.data.montageName ;
+ NVRDataModel.setLogin(ld);
+ $scope.currentProfileName = $scope.data.montageName;
+
+ if (unHidden)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('alert',
+ {
+ title: $translate.instant('kNote'),
+ template: $translate.instant('kMontageSaveDefaultWarning'),
+ okText: $translate.instant('kButtonOk'),
+
+ });
+ switchMontageProfile($translate.instant('kMontageDefaultProfile'));
+
+
+ }
+
+ }
+
+
+ }
+
+ }
+ });
+
+ };
+
+ $scope.toggleSubMenuFunction = function()
+ {
+
+ $scope.toggleSubMenu = !$scope.toggleSubMenu;
+
+ NVRDataModel.debug("toggling size buttons:" + $scope.toggleSubMenu);
+ if ($scope.toggleSubMenu) $ionicScrollDelegate.$getByHandle("montage-delegate").scrollTop();
+ var ld = NVRDataModel.getLogin();
+ ld.showMontageSubMenu = $scope.toggleSubMenu;
+ NVRDataModel.setLogin(ld);
+ };
+
+ // minimal has to be beforeEnter or header won't hide
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+ $scope.minimal = $stateParams.minimal;
+ //console.log ("**************** MINIMAL ENTER " + $scope.minimal);
+ $scope.zmMarginTop = $scope.minimal ? 0 : 15;
+
+ });
+
+ //avoid bogus scale error
+ $scope.LoginData = NVRDataModel.getLogin();
+
+ $scope.toggleTimeType = function()
+ {
+ if (NVRDataModel.isTzSupported())
+ {
+ if ($scope.iconTimeNow == 'server')
+ {
+ $scope.iconTimeNow = 'local';
+ $scope.timeNow = $translate.instant('kPleaseWait');
+ }
+ else
+ {
+ $scope.iconTimeNow = 'server';
+ $scope.timeNow = $translate.instant('kPleaseWait');
+ }
+ }
+ else
+ NVRDataModel.debug("timezone API not supported, can't display");
+ };
+
+ $scope.$on('$ionicView.afterEnter', function()
+ {
+ NVRDataModel.debug("Setting image mode to snapshot, will change to image when packery is all done");
+ $scope.areImagesLoading = true;
+ $scope.isDragabillyOn = false;
+ $scope.reOrderActive = false;
+
+ if (NVRDataModel.isTzSupported())
+ $scope.iconTimeNow = 'server';
+ else
+ $scope.iconTimeNow = 'local';
+
+ if ($scope.iconTimeNow == 'local')
+ $scope.timeNow = moment().format(NVRDataModel.getTimeFormatSec());
+ else
+ $scope.timeNow = moment().tz(NVRDataModel.getTimeZoneNow()).format(NVRDataModel.getTimeFormatSec());
+
+ $scope.gridScale = "grid-item-50";
+ $scope.LoginData = NVRDataModel.getLogin();
+ //FIXME
+
+ if (NVRDataModel.getBandwidth() == 'lowbw')
+ {
+ NVRDataModel.debug("Enabling low bandwidth parameters");
+ $scope.LoginData.montageQuality = zm.montageQualityLowBW;
+ $scope.LoginData.singleImageQuality = zm.eventSingleImageQualityLowBW;
+ $scope.LoginData.montageHistoryQuality = zm.montageQualityLowBW;
+
+ }
+
+ $scope.monLimit = $scope.LoginData.maxMontage;
+ $scope.toggleSubMenu = NVRDataModel.getLogin().showMontageSubMenu;
+
+ $scope.monitors = message;
+ $scope.MontageMonitors = angular.copy(message);
+ $scope.sliderChanging = false;
+ loginData = NVRDataModel.getLogin();
+
+ $scope.isRefresh = $stateParams.isRefresh;
+ sizeInProgress = false;
+ $scope.imageStyle = true;
+ intervalHandleMontage = "";
+ intervalHandleMontageCycle = "";
+ $scope.isModalActive = false;
+ $scope.isReorder = false;
+
+ $ionicSideMenuDelegate.canDragContent($scope.minimal ? true : true);
+
+ $scope.areImagesLoading = true;
+ var ld = NVRDataModel.getLogin();
+
+ refreshSec = (NVRDataModel.getBandwidth() == 'lowbw') ? ld.refreshSecLowBW : ld.refreshSec;
+
+ NVRDataModel.debug("bandwidth: " + NVRDataModel.getBandwidth() + " montage refresh set to: " + refreshSec);
+
+ //console.log("Setting Awake to " + NVRDataModel.getKeepAwake());
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleMontageCycle);
+ $interval.cancel(intervalHandleAlarmStatus);
+
+ intervalHandleMontage = $interval(function()
+ {
+ loadNotifications();
+ // console.log ("Refreshing Image...");
+ }.bind(this), refreshSec * 1000);
+
+ NVRDataModel.debug ("Setting up cycle interval of:"+ NVRDataModel.getLogin().cycleMontageInterval* 1000);
+ intervalHandleMontageCycle = $interval(function()
+ {
+ cycleMontageProfiles();
+ // console.log ("Refreshing Image...");
+ }.bind(this), NVRDataModel.getLogin().cycleMontageInterval* 1000);
+
+ intervalHandleAlarmStatus = $interval(function()
+ {
+ loadAlarmStatus();
+ // console.log ("Refreshing Image...");
+ }.bind(this), 5000);
+
+ loadNotifications();
+
+ if ($scope.MontageMonitors.length == 0)
+ {
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kNoMonitors'),
+ template: $translate.instant('kCheckCredentials'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+
+ ld = NVRDataModel.getLogin();
+
+ $rootScope.authSession = "undefined";
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kNegotiatingStreamAuth'),
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 300,
+ showDelay: 0
+ });
+
+ NVRDataModel.log("Inside Montage Ctrl:We found " + $scope.monitors.length + " monitors");
+
+ // set them all at 50% for packery
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ $scope.MontageMonitors[i].Monitor.alarmState = 'color:rgba(0,0,0,0);';
+ $scope.MontageMonitors[i].Monitor.isStamp = false;
+
+ }
+
+ $rootScope.validMonitorId = $scope.monitors[0].Monitor.Id;
+ NVRDataModel.getAuthKey($rootScope.validMonitorId, (Math.floor((Math.random() * 999999) + 1)).toString())
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ //console.log(success);
+ $rootScope.authSession = success;
+ NVRDataModel.log("Stream authentication construction: " +
+ $rootScope.authSession);
+ $timeout(function()
+ {
+ initPackery();
+ }, zm.packeryTimer);
+
+ },
+ function(error)
+ {
+
+ $ionicLoading.hide();
+ NVRDataModel.debug("MontageCtrl: Error in authkey retrieval " + error);
+ //$rootScope.authSession="";
+ NVRDataModel.log("MontageCtrl: Error returned Stream authentication construction. Retaining old value of: " + $rootScope.authSession);
+ $timeout(function()
+ {
+ initPackery();
+ }, zm.packeryTimer);
+ });
+
+ //console.log("**VIEW ** Montage Ctrl AFTER ENTER");
+ window.addEventListener("resize", orientationChanged, false);
+
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ });
+
+ $scope.$on('$ionicView.beforeLeave', function()
+ {
+ // console.log("**VIEW ** Montage Ctrl Left, force removing modal");
+
+ //console.log ("beforeLeave:Cancelling timer");
+ $interval.cancel(intervalHandleMontage);
+ $interval.cancel(intervalHandleMontageCycle);
+ $interval.cancel(intervalHandleAlarmStatus);
+ pckry.destroy();
+ window.removeEventListener("resize", orientationChanged, false);
+
+ // make sure this is applied in scope digest to stop network pull
+ // thats why we are doing it beforeLeave
+
+ if (NVRDataModel.isForceNetworkStop())
+ {
+ NVRDataModel.log("MontageCtrl:Stopping network pull...");
+ NVRDataModel.stopNetwork();
+
+ }
+
+ });
+
+ $scope.$on('$ionicView.unloaded', function() {
+
+ });
+
+ $scope.resetSizes = function()
+ {
+ var somethingReset = false;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.isDragabillyOn)
+ {
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == "dragborder-selected")
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ somethingReset = true;
+ }
+ }
+ else
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ // somethingReset = true;
+ }
+ }
+ if (!somethingReset && $scope.isDragabillyOn) // nothing was selected
+ {
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ }
+ }
+
+ $timeout(function()
+ {
+ pckry.reloadItems();
+
+ pckry.once('layoutComplete', function()
+ {
+ //console.log ("Layout complete");
+ var positions = pckry.getShiftPositions('data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+
+ ld.packeryPositions = JSON.stringify(positions);
+ //console.log ("Saving " + ld.packeryPositions);
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName = $translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+
+ $timeout(function()
+ {
+ NVRDataModel.debug("doing the jiggle and dance...");
+ pckry.resize(true);
+ }, 300);
+
+ // $scope.slider.monsize = 2;
+ });
+ pckry.layout();
+
+ }, 20);
+
+ };
+
+ function layout(pckry)
+ {
+ pckry.shiftLayout();
+ }
+
+
+ $scope.squeezeMonitors = function()
+ {
+ pckry.once('layoutComplete', resizeComplete);
+ $timeout (function() {pckry.layout();});
+
+ function resizeComplete()
+ {
+ //console.log ("HERE");
+ $timeout(function()
+ {
+ var positions = pckry.getShiftPositions('data-item-id');
+ console.log("SAVING");
+ var ld = NVRDataModel.getLogin();
+
+ ld.packeryPositions = JSON.stringify(positions);
+ //console.log ("Saving " + ld.packeryPositions);
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName =$translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+ $ionicLoading.hide();
+ $scope.sliderChanging = false;
+ }, 20);
+
+ }
+
+
+ };
+ //---------------------------------------------------------
+ // slider is tied to the view slider for montage
+ //Remember not to use a variable. I'm using an object
+ // so it's passed as a reference - otherwise it makes
+ // a copy and the value never changes
+ //---------------------------------------------------------
+
+ $scope.sliderChanged = function(dirn)
+ {
+
+ if ($scope.sliderChanging)
+ {
+ console.log("too fast my friend");
+ //$scope.slider.monsize = oldSliderVal;
+ return;
+ }
+
+ $scope.sliderChanging = true;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait'),
+ noBackdrop: true,
+ duration: 5000
+ });
+
+ var somethingReset = false;
+
+ var oldScales = {};
+ pckry.getItemElements().forEach(function(elem)
+ {
+ var id = elem.getAttribute("data-item-id");
+ var sz = elem.getAttribute("data-item-size");
+ if (isNaN(sz)) sz=20;
+ oldScales[id] = sz;
+ console.log("REMEMBERING " + id + ":" + sz);
+
+ });
+
+ // this only changes items that are selected
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+
+ var curVal = parseInt($scope.MontageMonitors[i].Monitor.gridScale) || 20;
+ curVal = curVal + (5 * dirn);
+ if (curVal < 10) curVal = 10;
+ if (curVal > 100) curVal = 100;
+ //console.log ("For Index: " + i + " From: " + $scope.MontageMonitors[i].Monitor.gridScale + " To: " + curVal);
+
+ if ($scope.isDragabillyOn)
+ {
+ // only do this for selected monitors
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == "dragborder-selected")
+ {
+
+ $scope.MontageMonitors[i].Monitor.gridScale = curVal;
+ somethingReset = true;
+ }
+ }
+ else
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = curVal;
+ //somethingReset = true;
+
+ }
+
+ }
+
+ // this changes all items if none were selected
+ if (!somethingReset && $scope.isDragabillyOn) // nothing was selected
+ {
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ var cv = parseInt($scope.MontageMonitors[i].Monitor.gridScale) || 20;
+ cv = cv + (5 * dirn);
+ if (cv < 10) cv = 10;
+ if (cv > 100) cv = 100;
+ $scope.MontageMonitors[i].Monitor.gridScale = cv;
+ //console.log ("*******GRIDSCALE="+)
+ }
+ }
+
+ // reload sizes from DOM and trigger a layout
+
+ $timeout(function()
+ {
+ console.log("Calling re-layout");
+ //pckry.reloadItems();
+
+ if (dirn == 1) //expand
+ {
+ pckry.getItemElements().forEach(function(elem)
+ {
+ var id = elem.getAttribute("data-item-id");
+ var sz = elem.getAttribute("data-item-size");
+ if (isNaN(sz)) sz=20;
+ console.log("NOW IT IS-> " + id + ":" + sz);
+ if (oldScales[id] != sz)
+ {
+ console.log("Calling FIT on " + id + " size:" + oldScales[id] + "->" + sz);
+ pckry.once('fitComplete', resizeComplete);
+ pckry.fit(elem);
+
+ }
+ });
+ }
+ else //shrink
+ {
+ console.log("Calling shift");
+ pckry.once('layoutComplete', resizeComplete);
+ pckry.shiftLayout();
+
+ }
+
+ }, 20);
+
+ /* if (!somethingReset) {
+ //console.log (">>>SOMETHING NOT RESET");
+ $timeout(function () {
+ pckry.layout();
+ }, zm.packeryTimer);
+ } else {
+
+ //console.log (">>>SOMETHING RESET");
+ $timeout(function () {
+ layout(pckry);
+ }, zm.packeryTimer);
+ }*/
+ function resizeComplete()
+ {
+ //console.log ("HERE");
+ $timeout(function()
+ {
+ var positions = pckry.getShiftPositions('data-item-id');
+ console.log("SAVING");
+ var ld = NVRDataModel.getLogin();
+
+ ld.packeryPositions = JSON.stringify(positions);
+ //console.log ("Saving " + ld.packeryPositions);
+ ld.currentMontageProfile = "";
+ $scope.currentProfileName = $translate.instant ('kMontage');
+ NVRDataModel.setLogin(ld);
+ $ionicLoading.hide();
+ $scope.sliderChanging = false;
+ }, 20);
+
+ }
+
+ };
+
+ $scope.$on('$ionicView.afterEnter', function()
+ {
+ // This rand is really used to reload the monitor image in img-src so it is not cached
+ // I am making sure the image in montage view is always fresh
+ // I don't think I am using this anymore FIXME: check and delete if needed
+ // $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ });
+
+ $scope.currentProfileName = NVRDataModel.getLogin().currentMontageProfile || $translate.instant ('kMontage');
+
+ $scope.reloadView = function()
+ {
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ NVRDataModel.log("User action: image reload " + $rootScope.rand);
+ };
+
+ $scope.doRefresh = function()
+ {
+
+ // console.log("***Pull to Refresh, recomputing Rand");
+ NVRDataModel.log("Reloading view for montage view, recomputing rand");
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $scope.monitors = [];
+ imageLoadingDataShare.set(0);
+
+ var refresh = NVRDataModel.getMonitors(1);
+
+ refresh.then(function(data)
+ {
+ $scope.monitors = data;
+ $scope.$broadcast('scroll.refreshComplete');
+ });
+ };
+
+ }]);
diff --git a/www/js/MontageHistoryCtrl.js b/www/js/MontageHistoryCtrl.js
new file mode 100644
index 00000000..8aff342e
--- /dev/null
+++ b/www/js/MontageHistoryCtrl.js
@@ -0,0 +1,1496 @@
+// Controller for the montage view
+/* jshint -W041, -W093, -W083 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,ionic,Masonry,moment,Packery, Draggabilly, imagesLoaded, Chart */
+// FIXME: This is a copy of montageCtrl - needs a lot of code cleanup
+angular.module('zmApp.controllers').controller('zmApp.MontageHistoryCtrl', ['$scope', '$rootScope', 'NVRDataModel', 'message', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$ionicPopup', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$ionicPlatform', 'zm', '$ionicPopover', '$controller', 'imageLoadingDataShare', '$window', '$translate', 'qHttp', '$q', function($scope, $rootScope, NVRDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $ionicPopup, $stateParams, $ionicHistory, $ionicScrollDelegate, $ionicPlatform, zm, $ionicPopover, $controller, imageLoadingDataShare, $window, $translate, qHttp, $q)
+{
+ //--------------------------------------------------------------------------------------
+ // Handles bandwidth change, if required
+ //
+ //--------------------------------------------------------------------------------------
+ $rootScope.$on("bandwidth-change", function(e, data)
+ {
+ // nothing to do for now
+ // eventUrl will use lower BW in next query cycle
+ });
+
+ $scope.getLocalTZ = function()
+ {
+ return moment.tz.guess();
+ };
+ //--------------------------------------
+ // formats events dates in a nice way
+ //---------------------------------------
+ $scope.prettifyDateTimeFirst = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format(NVRDataModel.getTimeFormat() + '/MMM Do');
+ else
+ return moment(str).format(NVRDataModel.getTimeFormat() + '/MMM Do');
+ };
+ $scope.prettifyDate = function(str)
+ {
+ return moment(str).format('MMM Do, YYYY ' + NVRDataModel.getTimeFormat());
+ };
+
+ function prettifyDate(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format('MMM Do');
+ else
+ return moment(str).format('MMM Do');
+ }
+ $scope.prettifyTime = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format('h:mm a');
+ else
+ return moment(str).format('h:mm a');
+ };
+ $scope.prettify = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format(NVRDataModel.getTimeFormat() + ' on MMMM Do YYYY');
+ else
+ return moment(str).format(NVRDataModel.getTimeFormat() + ' on MMMM Do YYYY');
+ };
+ $scope.humanizeTime = function(str)
+ {
+ // if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).fromNow();
+ // else
+ // return moment(str).fromNow();
+
+ };
+ // if you change date in footer, change hrs
+ $scope.dateChanged = function()
+ {
+ $scope.datetimeValueFrom.hrs = Math.round(moment.duration(moment().diff(moment($scope.datetimeValueFrom.value))).asHours());
+ };
+ // if you change hrs in footer, change date
+ $scope.hrsChanged = function()
+ {
+ $scope.datetimeValueFrom.value = moment().subtract($scope.datetimeValueFrom.hrs, 'hours').toDate();
+ timefrom.toDate();
+ };
+
+ function orientationChanged()
+ {
+ // NVRDataModel.debug("Detected orientation change, redoing packery resize");
+ /* $timeout(function () {
+ pckry.onresize();
+ });*/
+ }
+ //--------------------------------------
+ // pause/unpause nph-zms
+ //---------------------------------------
+ $scope.togglePause = function(mid)
+ {
+ //console.log ("TOGGLE PAUSE " + mid);
+ var m = -1;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.Id == mid)
+ {
+ m = i;
+ break;
+ }
+ }
+ if (m != -1)
+ {
+
+ $scope.MontageMonitors[m].Monitor.isPaused = !$scope.MontageMonitors[m].Monitor.isPaused;
+ var cmd = 1;
+ NVRDataModel.debug("Sending CMD:" + cmd + " for monitor " + $scope.MontageMonitors[m].Monitor.Name);
+ controlEventStream(cmd, "", $scope.MontageMonitors[m].Monitor.connKey, -1);
+ }
+ };
+
+ function sendCmd(mid, cmd, extra)
+ {
+
+ var m = -1;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.Id == mid)
+ {
+ m = i;
+ break;
+ }
+ }
+ if (m != -1)
+ {
+ NVRDataModel.debug("Sending CMD:" + cmd + " for monitor " + $scope.MontageMonitors[m].Monitor.Name);
+ return controlEventStream(cmd, "", $scope.MontageMonitors[m].Monitor.connKey, -1, extra);
+ }
+
+ }
+ $scope.seek = function(mid, p)
+ {
+ NVRDataModel.debug("Slider called with mid=" + mid + " progress=" + p);
+
+ var m = -1;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.Id == mid)
+ {
+ m = i;
+ break;
+ }
+ }
+ if (m != -1)
+ {
+ $scope.MontageMonitors[i].Monitor.seek = true;
+ }
+
+ sendCmd(mid, '14', "&offset=" + p)
+ .then(function(success)
+ {
+ //console.log ("Removing seek status from " + $scope.MontageMonitors[i].Monitor.Name);
+ $scope.MontageMonitors[i].Monitor.seek = false;
+
+ },
+ function(err)
+ {
+ //console.log ("Removing seek status from " + $scope.MontageMonitors[i].Monitor.Name);
+ $scope.MontageMonitors[i].Monitor.seek = false;
+ });
+
+ };
+ $scope.moveFaster = function(mid)
+ {
+ sendCmd(mid, 4);
+ };
+ $scope.moveSlower = function(mid)
+ {
+ sendCmd(mid, 5);
+ };
+ $scope.movePlay = function(mid)
+ {
+
+ var m = -1;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.Id == mid)
+ {
+ m = i;
+ break;
+ }
+ }
+ if (m != -1)
+ {
+ $scope.MontageMonitors[m].Monitor.isPaused = false;
+ var cmd = 2;
+ NVRDataModel.debug("Sending CMD:" + cmd + " for monitor " + $scope.MontageMonitors[m].Monitor.Name);
+ controlEventStream(cmd, "", $scope.MontageMonitors[m].Monitor.connKey, -1);
+ }
+ };
+ //--------------------------------------
+ // Called when ion-footer collapses
+ // note that on init it is also called
+ //---------------------------------------
+ $scope.footerExpand = function()
+ {
+ // console.log ("**************** EXPAND CALLED ***************");
+ $ionicSideMenuDelegate.canDragContent(false);
+ };
+ $scope.footerCollapse = function()
+ {
+ footerCollapse();
+ };
+ /* Note this is also called when the view is first loaded */
+ function footerCollapse()
+ {
+ if (readyToRun == false)
+ {
+ NVRDataModel.debug("fake call to footerCollapse - ignoring");
+ return;
+ }
+
+ if ($scope.MontageMonitors == undefined)
+ {
+ NVRDataModel.debug("montage array is undefined and not ready");
+ return;
+ }
+
+ $interval.cancel($rootScope.eventQueryInterval);
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait'),
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+ $scope.dragBorder = "";
+ $scope.isDragabillyOn = false;
+ $ionicSideMenuDelegate.canDragContent(false);
+ NVRDataModel.stopNetwork("MontageHistory-footerCollapse");
+ var ld = NVRDataModel.getLogin();
+ $scope.sliderVal.realRate = $scope.sliderVal.rate * 100;
+
+ var TimeObjectFrom = moment($scope.datetimeValueFrom.value).format("YYYY-MM-DD HH:mm");
+ var TimeObjectTo = moment().format('YYYY-MM-DD HH:mm');
+
+ // At this point of time, we need to ensure From and To are changed to server time
+ //if (NVRDataModel.getLogin().useLocalTimeZone)
+ if (1)
+ {
+ var localtz = moment.tz.guess();
+ var servertz = NVRDataModel.getTimeZoneNow();
+
+ NVRDataModel.log("Local timezone conversion is on, converting from " + localtz + " to " + servertz);
+ NVRDataModel.log("Original From: " + TimeObjectFrom + " Original To: " + TimeObjectTo);
+
+ TimeObjectFrom = moment.tz(TimeObjectFrom, localtz).tz(servertz).format("YYYY-MM-DD HH:mm");
+ TimeObjectTo = moment.tz(TimeObjectTo, localtz).tz(servertz).format("YYYY-MM-DD HH:mm");
+
+ NVRDataModel.log("Converted From: " + TimeObjectFrom + " Converted To: " + TimeObjectTo);
+
+ }
+
+ var apiurl;
+
+ // release all active streams
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ $scope.MontageMonitors[i].Monitor.eid = "-1";
+ // generate new connKeys if timeline changes
+ if ($scope.MontageMonitors[i].Monitor.eventUrl != 'img/noevent.png')
+ {
+ // this means this mid was showing a message, now we need to change it
+ // so kill prev. stream first
+ NVRDataModel.log("footerCollapse: Calling kill with " + $scope.MontageMonitors[i].Monitor.connKey + " for Monitor:" + $scope.MontageMonitors[i].Monitor.Name);
+ //var tmpCK = angular.copy($scope.MontageMonitors[i].Monitor.connKey);
+ //timedControlEventStream(2500, 17, "", tmpCK, -1);
+ controlEventStream(17, "", $scope.MontageMonitors[i].Monitor.connKey, -1);
+ $scope.MontageMonitors[i].Monitor.eventUrl = "img/noevent.png";
+ $scope.MontageMonitors[i].Monitor.eid = "-1";
+ $scope.MontageMonitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ $scope.MontageMonitors[i].Monitor.noGraph = true;
+ //console.log ("Generating connkey: " +$scope.MontageMonitors[i].Monitor.connKey);
+ }
+ }
+ // grab events that start on or after the time
+ apiurl = ld.apiurl + "/events/index/StartTime >=:" + TimeObjectFrom + "/AlarmFrames >=:" + (ld.enableAlarmCount ? ld.minAlarmCount : 0) + ".json";
+ NVRDataModel.log("Event timeline API is " + apiurl);
+ // make sure there are no more than 5 active streams (noevent is ok)
+ $scope.currentLimit = $scope.monLimit;
+ //qHttp.get(apiurl)
+ $http(
+ {
+ method: 'get',
+ url: apiurl
+ }).then(function(succ)
+ {
+ var data = succ.data;
+ var ld = NVRDataModel.getLogin();
+ NVRDataModel.debug("Got " + data.events.length + "new history events...");
+ var eid, mid, stime;
+ for (i = 0; i < data.events.length; i++)
+ {
+ mid = data.events[i].Event.MonitorId;
+ eid = data.events[i].Event.Id;
+ //console.log ("Event ID:"+eid);
+ stime = data.events[i].Event.StartTime;
+ // only take the first one for each monitor
+ for (var j = 0; j < $scope.MontageMonitors.length; j++)
+ {
+ $scope.MontageMonitors[j].Monitor.isPaused = false;
+ // that's the earliest match and play gapless from there
+ if ($scope.MontageMonitors[j].Monitor.Id == mid)
+ {
+ if ($scope.MontageMonitors[j].Monitor.eventUrl == 'img/noevent.png')
+ {
+ // console.log ("Old value of event url " + $scope.MontageMonitors[j].eventUrl);
+ //console.log ("ldurl is " + ld.streamingurl);
+ var bw = NVRDataModel.getBandwidth() == "lowbw" ? zm.eventMontageQualityLowBW : ld.montageHistoryQuality;
+ $scope.MontageMonitors[j].Monitor.eventUrl = ld.streamingurl + "/nph-zms?source=event&mode=jpeg&event=" + eid + "&frame=1&replay=gapless&rate=" + $scope.sliderVal.realRate + "&connkey=" + $scope.MontageMonitors[j].Monitor.connKey + "&scale=" + bw + "&rand=" + $rootScope.rand;
+ //console.log ("Setting event URL to " +$scope.MontageMonitors[j].Monitor.eventUrl);
+ // console.log ("SWITCHING TO " + $scope.MontageMonitors[j].eventUrl);
+ $scope.MontageMonitors[j].Monitor.eventUrlTime = stime;
+ $scope.MontageMonitors[j].Monitor.eid = eid;
+ $scope.MontageMonitors[j].Monitor.eventDuration = data.events[i].Event.Length;
+ $scope.MontageMonitors[j].Monitor.sliderProgress = {
+ progress: 0
+ };
+ //console.log(">>> Setting Event for " + $scope.MontageMonitors[j].Monitor.Name + " to " + eid);
+ // now lets get the API for that event for graphing
+ $scope.MontageMonitors[j].Monitor.noGraph = true;
+
+ }
+ }
+ }
+ }
+ // make sure we do our best to get that duration for all monitors
+ // in the above call, is possible some did not make the cut in the first page
+ NVRDataModel.log("Making sure all monitors have a fair chance...");
+ var promises = [];
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ //console.log("Fair chance check for " + $scope.MontageMonitors[i].Monitor.Name);
+ if ($scope.MontageMonitors[i].Monitor.eventUrl == 'img/noevent.png')
+ {
+ var indivGrab = ld.apiurl + "/events/index/MonitorId:" + $scope.MontageMonitors[i].Monitor.Id + "/StartTime >=:" + TimeObjectFrom + "/AlarmFrames >=:" + (ld.enableAlarmCount ? ld.minAlarmCount : 0) + ".json";
+ NVRDataModel.debug("Monitor " + $scope.MontageMonitors[i].Monitor.Id + ":" + $scope.MontageMonitors[i].Monitor.Name + " does not have events, trying " + indivGrab);
+ var p = getExpandedEvents(i, indivGrab);
+ promises.push(p);
+
+ }
+
+ }
+ $q.all(promises).then(doPackery);
+
+ // At this stage, we have both a general events grab, and specific event grabs for MIDS that were empty
+
+ function doPackery()
+ {
+ // $ionicLoading.hide();
+ //console.log("REDOING PACKERY & DRAG");
+ NVRDataModel.debug("Re-creating packery and draggy");
+ if (pckry !== undefined)
+ {
+ // remove current draggies
+ draggies.forEach(function(drag)
+ {
+ drag.destroy();
+ });
+ draggies = [];
+ // destroy existing packery object
+ pckry.destroy();
+ initPackery();
+
+ $rootScope.eventQueryInterval = $interval(function()
+ {
+ checkAllEvents();
+ }.bind(this), zm.eventHistoryTimer);
+ }
+ }
+ }, function(err)
+ {
+ NVRDataModel.debug("history ERROR:" + JSON.stringify(err));
+ });
+
+ function getExpandedEvents(i, indivGrab)
+ {
+ var d = $q.defer();
+ var ld = NVRDataModel.getLogin();
+ // console.log ("Expanded API: " + indivGrab);
+ $http(
+ {
+ method: 'get',
+ url: indivGrab
+ }).then(function(succ)
+ {
+ var data = succ.data;
+ // console.log ("EXPANDED DATA FOR MONITOR " + i + JSON.stringify(data));
+ if (data.events.length > 0)
+ {
+ if (!NVRDataModel.isBackground())
+ {
+ var bw = NVRDataModel.getBandwidth() == "lowbw" ? zm.eventMontageQualityLowBW : ld.montageHistoryQuality;
+ $scope.MontageMonitors[i].Monitor.eventUrl = ld.streamingurl + "/nph-zms?source=event&mode=jpeg&event=" + data.events[0].Event.Id + "&frame=1&replay=gapless&rate=" + $scope.sliderVal.realRate + "&connkey=" + $scope.MontageMonitors[i].Monitor.connKey + "&scale=" + bw + "&rand=" + $rootScope.rand;
+ //console.log ("SWITCHING TO " + $scope.MontageMonitors[i].eventUrl);
+ $scope.MontageMonitors[i].Monitor.eventUrlTime = data.events[0].Event.StartTime;
+ $scope.MontageMonitors[i].Monitor.eid = data.events[0].Event.Id;
+ $scope.MontageMonitors[i].Monitor.noGraph = true;
+ $scope.MontageMonitors[i].Monitor.sliderProgress = {
+ progress: 0
+ };
+ $scope.MontageMonitors[i].Monitor.eventDuration = data.events[0].Event.Length;
+ //console.log(">>> Setting Event for " + $scope.MontageMonitors[i].Monitor.Name + " to " + data.events[0].Event.Id);
+ NVRDataModel.log("Found expanded event " + data.events[0].Event.Id + " for monitor " + $scope.MontageMonitors[i].Monitor.Id);
+ }
+ else
+ {
+ // $scope.MontageMonitors[i].eventUrl="img/noevent.png";
+ // $scope.MontageMonitors[i].eventUrlTime = "";
+ // NVRDataModel.log ("Setting img src to null as data received in background");
+ }
+ }
+ d.resolve(true);
+ return d.promise;
+ },
+ function(err)
+ {
+ d.resolve(true);
+ return d.promise;
+ }
+
+ );
+ return d.promise;
+ }
+ }
+ //---------------------------------------------------------
+ // This is periodically called to get the current playing
+ // event by zms. I use this to display a timestamp
+ // Its a 2 step process - get event Id then go a Event
+ // API call to get time stamp. Sucks
+ //---------------------------------------------------------
+ function checkAllEvents()
+ {
+ //console.log("Timer:Events are checked....");
+
+ //if (pckry && !$scope.isDragabillyOn) pckry.shiftLayout();
+
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ // don't check for monitors that are not shown
+ // because nph connkey won't exist and the response
+ // will fail
+ if ($scope.MontageMonitors[i].Monitor.eventUrl != "" && $scope.MontageMonitors[i].Monitor.eventUrl != 'img/noevent.png' && $scope.MontageMonitors[i].Monitor.connKey != '' && $scope.MontageMonitors[i].Monitor.Function != 'None' && $scope.MontageMonitors[i].Monitor.listDisplay != 'noshow' && $scope.MontageMonitors[i].Monitor.Enabled != '0')
+ {
+ // NVRDataModel.debug("Checking event status for " + $scope.MontageMonitors[i].Monitor.Name + ":" + $scope.MontageMonitors[i].Monitor.eventUrl + ":" + $scope.MontageMonitors[i].Monitor.Function + ":" + $scope.MontageMonitors[i].Monitor.listDisplay);
+ // console.log ("Sending query 99 for " + $scope.MontageMonitors[i].Monitor.Name + " with ck="+$scope.MontageMonitors[i].Monitor.connKey);
+ controlEventStream('99', '', $scope.MontageMonitors[i].Monitor.connKey, i);
+ }
+ }
+ }
+ //--------------------------------------------------------------
+ // Used to control zms for a connkey. If ndx is not -1,
+ // then it also calls an event API for the returned eid
+ // and stores its time in the montage monitors array
+ //--------------------------------------------------------------
+ $scope.controlEventStream = function(cmd, disp, connkey, ndx)
+ {
+ controlEventStream(cmd, disp, connkey, ndx);
+ };
+
+ function timedControlEventStream(mTime, cmd, disp, connkey, ndx)
+ {
+ var mMtime = mTime || 2000;
+ NVRDataModel.debug("Deferring control " + cmd + " by " + mMtime);
+ $timeout(function()
+ {
+ subControlStream(cmd, connkey);
+ }, mMtime);
+ }
+
+ function subControlStream(cmd, connkey)
+ {
+ var loginData = NVRDataModel.getLogin();
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ var req = qHttp(
+ {
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded', //'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj) str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ //console.log("****SUB RETURNING " + foo);
+ return foo;
+ },
+ data:
+ {
+ view: "request",
+ request: "stream",
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken, // user: loginData.username,
+ // pass: loginData.password
+ }
+ });
+ req.then(function(succ)
+ {
+ NVRDataModel.debug("subControl success:" + JSON.stringify(succ));
+ }, function(err)
+ {
+ NVRDataModel.debug("subControl error:" + JSON.stringify(err));
+ });
+ }
+
+ function controlEventStream(cmd, disp, connkey, ndx, extras)
+ {
+ // console.log("Command value " + cmd);
+
+ var d = $q.defer();
+ if (disp)
+ {
+ $ionicLoading.hide();
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: zm.loadingTimeout,
+ });
+ }
+ var loginData = NVRDataModel.getLogin();
+ /*
+ var CMD_NONE = 0;
+ var CMD_PAUSE = 1;
+ var CMD_PLAY = 2;
+ var CMD_STOP = 3;
+ var CMD_FASTFWD = 4;
+ var CMD_SLOWFWD = 5;
+ var CMD_SLOWREV = 6;
+ var CMD_FASTREV = 7;
+ var CMD_ZOOMIN = 8;
+ var CMD_ZOOMOUT = 9;
+ var CMD_PAN = 10;
+ var CMD_SCALE = 11;
+ var CMD_PREV = 12;
+ var CMD_NEXT = 13;
+ var CMD_SEEK = 14;
+ var CMD_QUERY = 99;
+ */
+ // You need to POST commands to control zms
+ // Note that I am url encoding the parameters into the URL
+ // If I leave it as JSON, it gets converted to OPTONS due
+ // to CORS behaviour and ZM/Apache don't seem to handle it
+ //console.log("POST: " + loginData.url + '/index.php');
+ //console.log ("AUTH IS " + $rootScope.authSession);
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ var req = qHttp(
+ {
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded', //'Accept': '*/*',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj) str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ if (extras) foo = foo + extras;
+ //console.log("****RETURNING " + foo);
+ return foo;
+ },
+ data:
+ {
+ view: "request",
+ request: "stream",
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken, // user: loginData.username,
+ // pass: loginData.password
+ }
+ });
+ req.then(function(succ)
+ {
+ var resp = succ.data;
+
+ //console.log ("zms response: " + JSON.stringify(resp));
+
+ // move progress bar if event id is the same
+ if (resp.result == "Ok" && ndx != -1 && (resp.status.event == $scope.MontageMonitors[ndx].Monitor.eid))
+ {
+ if (!$scope.MontageMonitors[ndx].Monitor.seek)
+ {
+ $scope.MontageMonitors[ndx].Monitor.sliderProgress.progress = resp.status.progress;
+ }
+ else
+ {
+ NVRDataModel.debug("Skipping progress as seek is active for " + $scope.MontageMonitors[ndx].Monitor.Name);
+ }
+ }
+
+ if (resp.result == "Ok" && ndx != -1 && ((resp.status.event != $scope.MontageMonitors[ndx].Monitor.eid) || $scope.MontageMonitors[ndx].Monitor.noGraph == true))
+ {
+ $scope.MontageMonitors[ndx].Monitor.noGraph = false;
+ // $scope.MontageMonitors[ndx].Monitor.sliderProgress.progress = 0;
+ NVRDataModel.debug("Fetching details, as event changed for " + $scope.MontageMonitors[ndx].Monitor.Name + " from " + $scope.MontageMonitors[ndx].Monitor.eid + " to " + resp.status.event);
+ var ld = NVRDataModel.getLogin();
+ var apiurl = ld.apiurl + "/events/" + resp.status.event + ".json";
+ //console.log ("API " + apiurl);
+ qHttp(
+ {
+ method: 'get',
+ url: apiurl
+ }).then(function(succ)
+ {
+ var data = succ.data;
+ var currentEventTime = moment(data.event.Event.StartTime);
+ var maxTime = moment();
+ //NVRDataModel.debug ("Monitor: " + $scope.MontageMonitors[ndx].Monitor.Id + " max time="+maxTime + "("+$scope.datetimeValueTo.value+")"+ " current="+currentEventTime + "("+data.event.Event.StartTime+")");
+
+ NVRDataModel.debug("creating graph for " + $scope.MontageMonitors[ndx].Monitor.Name);
+ var framearray = {
+ labels: [],
+ datasets: [
+ {
+ backgroundColor: 'rgba(242, 12, 12, 0.5)',
+ borderColor: 'rgba(242, 12, 12, 0.5)',
+ data: [],
+ }]
+ };
+ framearray.labels = [];
+ var ld = NVRDataModel.getLogin();
+ //console.log(">>>>> GRAPH");
+ for (i = 0; i < data.event.Frame.length; i++)
+ {
+ var ts = moment(data.event.Frame[i].TimeStamp).format(timeFormat);
+ //console.log ("pushing s:" + event.Frame[i].Score+" t:"+ts);
+ framearray.datasets[0].data.push(
+ {
+ x: ts,
+ y: data.event.Frame[i].Score
+ });
+ framearray.labels.push("");
+ }
+ $timeout(function()
+ {
+ drawGraph(framearray, $scope.MontageMonitors[ndx].Monitor.Id);
+ }, 100);
+ var element = angular.element(document.getElementById($scope.MontageMonitors[ndx].Monitor.Id + "-timeline"));
+ element.removeClass('animated flipInX');
+ element.addClass('animated flipOutX');
+ $timeout(function()
+ {
+ element.removeClass('animated flipOutX');
+ element.addClass('animated flipInX');
+ $scope.MontageMonitors[ndx].Monitor.eventUrlTime = data.event.Event.StartTime;
+ var bw = NVRDataModel.getBandwidth() == "lowbw" ? zm.eventMontageQualityLowBW : ld.montageHistoryQuality;
+ $scope.MontageMonitors[ndx].Monitor.eventUrl = ld.streamingurl + "/nph-zms?source=event&mode=jpeg&event=" + data.event.Event.Id + "&frame=1&replay=gapless&rate=" + $scope.sliderVal.realRate + "&connkey=" + $scope.MontageMonitors[ndx].Monitor.connKey + "&scale=" + bw + "&rand=" + $rootScope.rand;
+ $scope.MontageMonitors[ndx].Monitor.eid = data.event.Event.Id;
+ $scope.MontageMonitors[ndx].Monitor.sliderProgress = {
+ progress: 0
+ };
+ $scope.MontageMonitors[ndx].Monitor.eventDuration = data.event.Event.Length;
+ //console.log(">>> Setting Event for " + $scope.MontageMonitors[ndx].Monitor.Name + " to " + data.event.Event.Id);
+ }, 700);
+
+ }, function(err)
+ {
+ NVRDataModel.debug("skipping graph as detailed API failed for " + $scope.MontageMonitors[ndx].Monitor.Name);
+ $scope.MontageMonitors[ndx].Monitor.eventUrlTime = "-";
+ });
+ }
+ d.resolve(true);
+ return d.promise;
+ }, function(err)
+ {
+ d.reject(false);
+ NVRDataModel.log("Error sending event command " + JSON.stringify(err), "error");
+ return d.promise;
+ });
+ return d.promise;
+ }
+ $scope.isBackground = function()
+ {
+ return NVRDataModel.isBackground();
+ };
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+ $scope.handleAlarmsWhileMinimized = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ $scope.minimal = !$scope.minimal;
+ NVRDataModel.debug("MontageHistoryCtrl: switch minimal is " + $scope.minimal);
+ ionic.Platform.fullScreen($scope.minimal, !$scope.minimal);
+ $interval.cancel(intervalHandle);
+ $interval.cancel($rootScope.eventQueryInterval);
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false,
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+ //-------------------------------------------------------------
+ // this is checked to make sure we are not pulling images
+ // when app is in background. This is a problem with Android,
+ // for example
+ //-------------------------------------------------------------
+ $scope.isBackground = function()
+ {
+ //console.log ("Is background called from Montage and returned " +
+ //NVRDataModel.isBackground());
+ return NVRDataModel.isBackground();
+ };
+ $scope.toggleControls = function()
+ {
+ $scope.showControls = !$scope.showControls;
+ };
+ $scope.toggleSelectItem = function(ndx)
+ {
+ if ($scope.MontageMonitors[ndx].Monitor.selectStyle !== "undefined" && $scope.MontageMonitors[ndx].Monitor.selectStyle == "dragborder-selected")
+ {
+ $scope.MontageMonitors[ndx].Monitor.selectStyle = "";
+ }
+ else
+ {
+ $scope.MontageMonitors[ndx].Monitor.selectStyle = "dragborder-selected";
+ }
+ //console.log ("Switched value to " + $scope.MontageMonitors[ndx].Monitor.selectStyle);
+ };
+ //---------------------------------------------------------------------
+ // Called when you enable/disable dragging
+ //---------------------------------------------------------------------
+ $scope.dragToggle = function()
+ {
+ dragToggle();
+ };
+
+ function dragToggle()
+ {
+ var i;
+ $scope.isDragabillyOn = !$scope.isDragabillyOn;
+ $ionicSideMenuDelegate.canDragContent($scope.isDragabillyOn ? false : true);
+ //$timeout(function(){pckry.reloadItems();},10);
+ NVRDataModel.debug("setting dragabilly to " + $scope.isDragabillyOn);
+ if ($scope.isDragabillyOn)
+ {
+ $scope.showSizeButtons = true;
+ $scope.dragBorder = "dragborder";
+ NVRDataModel.debug("Enabling drag for " + draggies.length + " items");
+ for (i = 0; i < draggies.length; i++)
+ {
+ draggies[i].enable();
+ draggies[i].bindHandles();
+ }
+ // reflow and reload as some may be hidden
+ // $timeout(function(){pckry.reloadItems();$timeout(function(){pckry.layout();},300);},100);
+ }
+ else
+ {
+ $scope.dragBorder = "";
+ NVRDataModel.debug("Disabling drag for " + draggies.length + " items");
+ for (i = 0; i < draggies.length; i++)
+ {
+ draggies[i].disable();
+ draggies[i].unbindHandles();
+ }
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ }
+ // reflow and reload as some may be hidden
+ $timeout(function()
+ {
+ $timeout(function()
+ {
+ pckry.shiftLayout();
+ /*var positions = pckry.getShiftPositions('data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.packeryPositions = JSON.stringify(positions);
+ NVRDataModel.setLogin(ld);*/
+ }, 300);
+ }, 100);
+ }
+ }
+ //---------------------------------------------------------------------
+ // Show/Hide PTZ control in monitor view
+ //---------------------------------------------------------------------
+ $scope.togglePTZ = function()
+ {
+ $scope.showPTZ = !$scope.showPTZ;
+ };
+ $scope.callback = function()
+ {
+ // console.log("dragging");
+ };
+ $scope.onDropComplete = function(index, obj, event)
+ {
+ //console.log("dragged");
+ var otherObj = $scope.MontageMonitors[index];
+ var otherIndex = $scope.MontageMonitors.indexOf(obj);
+ $scope.MontageMonitors[index] = obj;
+ $scope.MontageMonitors[otherIndex] = otherObj;
+ };
+ //---------------------------------------------------------------------
+ // changes order of montage display
+ //---------------------------------------------------------------------
+ $scope.toggleMontageDisplayOrder = function()
+ {
+ $scope.packMontage = !$scope.packMontage;
+ loginData.packMontage = $scope.packMontage;
+ NVRDataModel.setLogin(loginData);
+ //console.log ("Switching orientation");
+ };
+ //---------------------------------------------------------------------
+ // In Android, the app runs full steam while in background mode
+ // while in iOS it gets suspended unless you ask for specific resources
+ // So while this view, we DON'T want Android to keep sending 1 second
+ // refreshes to the server for images we are not seeing
+ //---------------------------------------------------------------------
+ function onPause()
+ {
+ NVRDataModel.debug("MontageHistoryCtrl: onpause called");
+ $interval.cancel($rootScope.eventQueryInterval);
+ $interval.cancel(intervalHandle);
+ // $interval.cancel(modalIntervalHandle);
+ // FIXME: Do I need to setAwake(false) here?
+ }
+
+ function onResume()
+ {}
+ $scope.openMenu = function()
+ {
+ $timeout(function()
+ {
+ $rootScope.stateofSlide = $ionicSideMenuDelegate.isOpen();
+ }, 500);
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+ $scope.$on('$destroy', function()
+ {
+ NVRDataModel.debug("Cancelling eventQueryInterval");
+ $interval.cancel($rootScope.eventQueryInterval);
+ });
+ $scope.$on('$ionicView.loaded', function()
+ {
+ //console.log("**VIEW ** MontageHistoryCtrl Loaded");
+ });
+ $scope.$on('$ionicView.enter', function()
+ {
+ NVRDataModel.debug("**VIEW ** MontageHistory Ctrl Entered");
+ var ld = NVRDataModel.getLogin();
+ //console.log("Setting Awake to " + NVRDataModel.getKeepAwake());
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+ NVRDataModel.debug("query timer started");
+ $interval.cancel($rootScope.eventQueryInterval);
+ //console.log ("****************** TIMER STARTED INSIDE ENTER");
+ $rootScope.eventQueryInterval = $interval(function()
+ {
+ checkAllEvents();
+ }.bind(this), zm.eventHistoryTimer);
+ });
+ /*$scope.$on ('$ionicView.unloaded', function() {
+ console.log ("******** HISTORY UNLOADED KILLING WINDOW ************");
+ window.stop();
+ });*/
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+ // NVRDataModel.log ("Before Enter History: initing connkeys");
+ });
+ $scope.$on('$ionicView.beforeLeave', function()
+ {
+ //console.log("**VIEW ** Event History Ctrl Left, force removing modal");
+ if ($scope.modal) $scope.modal.remove();
+ NVRDataModel.log("BeforeLeave: Nullifying the streams...");
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ var element = document.getElementById("img-" + i);
+ /*if (element)
+ {
+ NVRDataModel.debug("BeforeLeave: Nullifying " + element.src);
+ element.src="";
+ //element.removeAttribute('src');
+
+ //$scope.$apply(nullify(element));
+ //element.src="";
+ }*/
+ }
+ NVRDataModel.log("Cancelling event query timer");
+ $interval.cancel($rootScope.eventQueryInterval);
+ NVRDataModel.log("MontageHistory:Stopping network pull...");
+ // make sure this is applied in scope digest to stop network pull
+ // thats why we are doing it beforeLeave
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.MontageMonitors[i].Monitor.connKey != '' && $scope.MontageMonitors[i].Monitor.eventUrl != 'img/noevent.png' && $scope.MontageMonitors[i].Monitor.Function != 'None' && $scope.MontageMonitors[i].Monitor.lisDisplay != 'noshow' && $scope.MontageMonitors[i].Monitor.Enabled != '0')
+ {
+ NVRDataModel.log("Before leave: Calling kill with " + $scope.MontageMonitors[i].Monitor.connKey);
+ var tmpCK = angular.copy($scope.MontageMonitors[i].Monitor.connKey);
+ timedControlEventStream(2500, 17, "", tmpCK, -1);
+ }
+ }
+ pckry.destroy();
+ window.removeEventListener("resize", orientationChanged, false);
+ NVRDataModel.log("Forcing a window.stop() here");
+ NVRDataModel.stopNetwork("MontageHistory-beforeLeave");
+ });
+ $scope.$on('$ionicView.unloaded', function() {});
+ $scope.sliderChanged = function(dirn)
+ {
+ //console.log("SLIDER CHANGED");
+ if ($scope.sliderChanging)
+ {
+ // console.log ("too fast my friend");
+ //$scope.slider.monsize = oldSliderVal;
+ // return;
+ }
+ $scope.sliderChanging = true;
+ var somethingReset = false;
+ // this only changes items that are selected
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ var curVal = parseInt($scope.MontageMonitors[i].Monitor.gridScale);
+ curVal = curVal + (10 * dirn);
+ if (curVal < 10) curVal = 10;
+ if (curVal > 100) curVal = 100;
+ //console.log ("For Index: " + i + " From: " + $scope.MontageMonitors[i].Monitor.gridScale + " To: " + curVal);
+ if ($scope.isDragabillyOn)
+ {
+ // only do this for selected monitors
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == "dragborder-selected")
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = curVal;
+ somethingReset = true;
+ }
+ }
+ else
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = curVal;
+ //somethingReset = true;
+ }
+ }
+ // this changes all items if none were selected
+ if (!somethingReset && $scope.isDragabillyOn) // nothing was selected
+ {
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ var cv = parseInt($scope.MontageMonitors[i].Monitor.gridScale);
+ cv = cv + (10 * dirn);
+ if (cv < 10) cv = 10;
+ if (cv > 100) cv = 100;
+ $scope.MontageMonitors[i].Monitor.gridScale = cv;
+ }
+ }
+ //pckry.reloadItems();
+ pckry.once('layoutComplete', function()
+ {
+ /* $timeout(function () {
+ var positions = pckry.EHgetShiftPositions('eh-data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.EHpackeryPositions = JSON.stringify(positions);
+ NVRDataModel.setLogin(ld);
+ $ionicLoading.hide();
+ $scope.sliderChanging = false;
+ }, zm.packeryTimer);*/
+ });
+ if (!somethingReset)
+ {
+ //console.log (">>>SOMETHING NOT RESET");
+ $timeout(function()
+ {
+ pckry.layout();
+ }, zm.packeryTimer);
+ }
+ else
+ {
+ //console.log (">>>SOMETHING RESET");
+ $timeout(function()
+ {
+ layout(pckry);
+ }, zm.packeryTimer);
+ }
+ };
+
+ function layout(pckry)
+ {
+ pckry.shiftLayout();
+ }
+ $scope.resetSizes = function()
+ {
+ var somethingReset = false;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ if ($scope.isDragabillyOn)
+ {
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == "dragborder-selected")
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ somethingReset = true;
+ }
+ }
+ else
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ // somethingReset = true;
+ }
+ }
+ if (!somethingReset && $scope.isDragabillyOn) // nothing was selected
+ {
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ }
+ }
+ $timeout(function()
+ {
+ pckry.reloadItems();
+ $timeout(function()
+ {
+ pckry.layout();
+ }, zm.packeryTimer); // force here - no shiftlayout
+ }, 100);
+ };
+
+ function isEmpty(obj)
+ {
+ for (var prop in obj)
+ {
+ return false;
+ }
+ return true;
+ }
+ // called by afterEnter to load Packery
+ function initPackery()
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kArrangingImages'),
+ noBackdrop: true,
+ duration: zm.loadingTimeout
+ });
+ var progressCalled = false;
+ draggies = [];
+ var layouttype = true;
+ var ld = NVRDataModel.getLogin();
+
+ var elem = angular.element(document.getElementById("mygrid"));
+ pckry = new Packery('.grid',
+ {
+ itemSelector: '.grid-item',
+ percentPosition: true,
+ columnWidth: '.grid-sizer',
+ gutter: 0,
+ initLayout: true
+
+ });
+ //console.log ("**** mygrid is " + JSON.stringify(elem));
+ imagesLoaded(elem).on('progress', function(instance, img)
+ {
+ var result = img.isLoaded ? 'loaded' : 'broken';
+ NVRDataModel.debug('~~loaded image is ' + result + ' for ' + img.img.src);
+ pckry.layout();
+ progressCalled = true;
+ // if (layouttype) $timeout (function(){layout(pckry);},100);
+ });
+ imagesLoaded(elem).once('always', function()
+ {
+ //console.log("******** ALL IMAGES LOADED");
+ $scope.$digest();
+ NVRDataModel.debug("All images loaded");
+ $ionicLoading.hide();
+
+ $scope.areImagesLoading = false;
+
+ if (!progressCalled)
+ {
+ NVRDataModel.log("*** PROGRESS WAS NOT CALLED");
+ pckry.reloadItems();
+ }
+
+ $timeout(function()
+ {
+
+ pckry.getItemElements().forEach(function(itemElem)
+ {
+ draggie = new Draggabilly(itemElem);
+ pckry.bindDraggabillyEvents(draggie);
+ draggies.push(draggie);
+ draggie.disable();
+ draggie.unbindHandles();
+ });
+
+ pckry.on('dragItemPositioned', itemDragged);
+
+ /*if (!isEmpty(positions)) {
+ NVRDataModel.log("Arranging as per packery grid");
+
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+ for (var j = 0; j < positions.length; j++) {
+ if ($scope.MontageMonitors[i].Monitor.Id == positions[j].attr) {
+ $scope.MontageMonitors[i].Monitor.gridScale = positions[j].size;
+ $scope.MontageMonitors[i].Monitor.listDisplay = positions[j].display;
+ NVRDataModel.debug("Setting monitor ID: " + $scope.MontageMonitors[i].Monitor.Id + " to size: " + positions[j].size + " and display:" + positions[j].display);
+ }
+ //console.log ("Index:"+positions[j].attr+ " with size: " + positions[j].size);
+ }
+ }
+
+
+ NVRDataModel.debug("All images loaded, doing image layout");
+ $timeout(function () {
+ pckry.initShiftLayout(positions, 'data-item-id');
+ }, 0);
+ }*/
+
+ $timeout(function()
+ {
+ NVRDataModel.log("Force calling resize");
+ pckry.layout();
+ $scope.packeryDone = true;
+ }, zm.packeryTimer); // don't ask
+
+ }, zm.packeryTimer);
+
+ });
+
+ function itemDragged(item)
+ {
+ NVRDataModel.debug("drag complete");
+ }
+ }
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+ // This rand is really used to reload the monitor image in img-src so it is not cached
+ // I am making sure the image in montage view is always fresh
+ // I don't think I am using this anymore FIXME: check and delete if needed
+ // $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $scope.showControls = true;
+ $scope.packeryDone = false;
+ readyToRun = false;
+ $scope.MontageMonitors = message;
+
+ doInitCode();
+
+ });
+ $scope.reloadView = function()
+ {
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ NVRDataModel.log("User action: image reload " + $rootScope.rand);
+ };
+ $scope.doRefresh = function()
+ {
+ //console.log("***Pull to Refresh, recomputing Rand");
+ NVRDataModel.log("Reloading view for montage view, recomputing rand");
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $scope.MontageMonitors = [];
+ imageLoadingDataShare.set(0);
+ var refresh = NVRDataModel.getMonitors(1);
+ refresh.then(function(data)
+ {
+ $scope.MontageMonitors = data.data;
+ $scope.$broadcast('scroll.refreshComplete');
+ });
+ };
+
+ function drawGraph(f, mid)
+ {
+ //console.log("Graphing on " + "eventchart-" + mid);
+ var cv = document.getElementById("eventchart-" + mid);
+ var ctx = cv.getContext("2d");
+ frameoptions = {
+ responsive: true,
+ legend: false,
+ title:
+ {
+ display: false,
+ text: ""
+ },
+ scales:
+ {
+ yAxes: [
+ {
+ display: false,
+ scaleLabel:
+ {
+ display: false,
+ labelString: 'value',
+ }
+ }],
+ xAxes: [
+ {
+ type: 'time',
+ display: false,
+ time:
+ {
+ format: timeFormat,
+ tooltipFormat: 'll HH:mm',
+ min: f.datasets[0].data[0].x,
+ max: f.datasets[0].data[f.datasets[0].data.length - 1].x,
+ displayFormats:
+ {}
+ },
+ scaleLabel:
+ {
+ display: false,
+ labelString: ''
+ }
+ }]
+ }
+ };
+ $timeout(function()
+ {
+ var myChart = new Chart(ctx,
+ {
+ type: 'line',
+ data: f,
+ options: frameoptions,
+ });
+ });
+ }
+ //---------------------------------------------------------------------
+ // Controller main
+ //---------------------------------------------------------------------
+ var intervalHandle;
+ var modalIntervalHandle;
+ var timeFormat;
+ var curYear;
+ var readyToRun;
+ var frameoptions;
+ var timeto, timefrom;
+ var commonCss;
+ var sizeInProgress;
+ var ld;
+ var pckry;
+ var draggies;
+ var i;
+ var draggie;
+ var loginData;
+ var oldmonitors;
+ var gridcontainer;
+ var montageOrder, hiddenOrder;
+
+ $scope.sliderVal = {
+ rate: 2,
+ realRate: 200,
+ hideNoEvents: false,
+ enableGapless: true,
+ exactMatch: false,
+ showTimeline: true
+ };
+ $scope.timeFormat = "yyyy-MM-dd " + NVRDataModel.getTimeFormat();
+ $scope.displayDateTimeSliders = true;
+ $scope.showtimers = true;
+ $scope.loginData = NVRDataModel.getLogin();
+
+ $scope.slider_modal_options_rate = {
+ from: 1,
+ to: 10,
+ realtime: true,
+ step: 1,
+ className: "mySliderClass", //modelLabels:function(val) {return "";},
+ smooth: false,
+ css: commonCss,
+ dimension: 'X'
+ };
+
+ $scope.datetimeValueFrom = {
+ value: "",
+ hrs: ""
+ };
+ $scope.datetimeValueTo = {
+ value: ""
+ };
+
+ $rootScope.eventQueryInterval = "";
+
+ function doInitCode()
+
+ {
+
+ $scope.isModalActive = false;
+
+ $scope.hrsAgo = 4;
+ window.addEventListener("resize", orientationChanged, false);
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ timeFormat = 'MM/DD/YYYY HH:mm:ss';
+ curYear = new Date().getFullYear();
+ readyToRun = false;
+
+ frameoptions = [];
+ // default = start of day
+ timeto = moment();
+ timefrom = moment().startOf('day');
+ $scope.datetimeValueTo.value = timeto.toDate();
+ $scope.sliderVal.rate = 1;
+ $scope.sliderVal.realRate = $scope.sliderVal.rate * 100;
+
+ $scope.datetimeValueFrom.value = timefrom.toDate();
+ $scope.datetimeValueFrom.hrs = Math.round(moment.duration(moment().diff(moment($scope.datetimeValueFrom.value))).asHours());
+
+ commonCss = {
+ background:
+ {
+ "background-color": "silver"
+ },
+ before:
+ {
+ "background-color": "purple"
+ },
+ default:
+ {
+ "background-color": "white"
+ }, // default value: 1px
+ after:
+ {
+ "background-color": "green"
+ }, // zone after default value
+ pointer:
+ {
+ "background-color": "red"
+ }, // circle pointer
+ range:
+ {
+ "background-color": "red"
+ } // use it if double value
+ };
+
+ $scope.monitorSize = []; // array with montage sizes per monitor
+ $scope.scaleDirection = []; // 1 = increase -1 = decrease
+ // The difference between old and original is this:
+ // old will have a copy of the last re-arranged monitor list
+ // while original will have a copy of the order returned by ZM
+ var oldMonitors = []; // To keep old order if user cancels after sort;
+ // Montage display order may be different so don't
+ // mangle monitors as it will affect other screens
+ // in Montage screen we will work with this local copy
+ //$scope.MontageMonitors = angular.copy ($scope.monitors);
+ var montageOrder = []; // This array will keep the ordering in montage view
+ var hiddenOrder = []; // 1 = hide, 0 = don't hide
+ var tempMonitors = message;
+ if (tempMonitors.length == 0)
+ {
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: $translate.instant('kNoMonitors'),
+ template: $translate.instant('kPleaseCheckCredentials'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("login");
+ return;
+ }
+
+ NVRDataModel.log("Inside MontageHistoryCtrl:We found " + $scope.MontageMonitors.length + " monitors");
+ // $scope.MontageMonitors = NVRDataModel.applyMontageMonitorPrefs(message, 1)[0];
+ var loginData = NVRDataModel.getLogin();
+ // init monitors
+ NVRDataModel.debug(">>Initializing connkeys and images...");
+ for (i = 0; i < $scope.MontageMonitors.length; i++)
+ {
+ //$scope.MontageMonitors[i].Monitor.connKey='';
+ $scope.MontageMonitors[i].Monitor.eid = "-1";
+ $scope.MontageMonitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ $scope.MontageMonitors[i].Monitor.eventUrl = 'img/noevent.png';
+ $scope.MontageMonitors[i].Monitor.eid = "-1";
+ $scope.MontageMonitors[i].Monitor.eventUrlTime = "";
+ $scope.MontageMonitors[i].Monitor.isPaused = false;
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ $scope.MontageMonitors[i].Monitor.alarmState = 'color:rgba(0,0,0,0);';
+ $scope.MontageMonitors[i].Monitor.sliderProgress = {
+ progress: 0
+ };
+ }
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+ $ionicPlatform.registerBackButtonAction(function(e)
+ {
+ e.preventDefault();
+ if ($scope.modal && $scope.modal.isShown())
+ {
+ // switch off awake, as liveview is finished
+ NVRDataModel.debug("Modal is open, closing it");
+ NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+ $scope.isModalActive = false;
+ }
+ else
+ {
+ NVRDataModel.debug("Modal is closed, so toggling or exiting");
+ if (!$ionicSideMenuDelegate.isOpenLeft())
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ }
+ else
+ {
+ navigator.app.exitApp();
+ }
+ }
+ }, 1000);
+ $scope.isRefresh = $stateParams.isRefresh;
+ sizeInProgress = false;
+ $ionicSideMenuDelegate.canDragContent(false);
+ $scope.LoginData = NVRDataModel.getLogin();
+ $scope.monLimit = $scope.LoginData.maxMontage;
+ $scope.currentLimit = $scope.LoginData.maxMontage;
+ if ($rootScope.platformOS != 'ios')
+ {
+ NVRDataModel.log("Limiting montage to 5, thanks to Chrome's stupid connection limit");
+ $scope.currentLimit = 5;
+ $scope.monLimit = 5;
+ }
+ $rootScope.authSession = "undefined";
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kNegotiatingStreamAuth'),
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 300,
+ showDelay: 0
+ });
+ ld = NVRDataModel.getLogin();
+ //console.log ("MONITORS " + JSON.stringify($scope.monitors));
+ $rootScope.validMonitorId = $scope.MontageMonitors[0].Monitor.Id;
+ NVRDataModel.getAuthKey($rootScope.validMonitorId).then(function(success)
+ {
+ $ionicLoading.hide();
+ //console.log(success);
+ $rootScope.authSession = success;
+ NVRDataModel.log("Stream authentication construction: " + $rootScope.authSession);
+ $timeout(function()
+ {
+ initPackery();
+ readyToRun = true;
+ footerCollapse();
+ }, zm.packeryTimer);
+
+ }, function(error)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.debug("MontageHistoryCtrl: Error in authkey retrieval " + error);
+ //$rootScope.authSession="";
+ NVRDataModel.log("MontageHistoryCtrl: Error returned Stream authentication construction. Retaining old value of: " + $rootScope.authSession);
+ $timeout(function()
+ {
+ initPackery();
+ readyToRun = true;
+ footerCollapse();
+ }, zm.packeryTimer);
+ });
+ }
+
+}]);
diff --git a/www/js/NewsCtrl.js b/www/js/NewsCtrl.js
new file mode 100644
index 00000000..e1e030ec
--- /dev/null
+++ b/www/js/NewsCtrl.js
@@ -0,0 +1,132 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,moment*/
+
+angular.module('zmApp.controllers').controller('zmApp.NewsCtrl', ['$scope', '$rootScope', '$ionicModal', 'NVRDataModel', '$ionicSideMenuDelegate', '$ionicHistory', '$state', '$http', 'zm', function($scope, $rootScope, $ionicModal, NVRDataModel, $ionicSideMenuDelegate, $ionicHistory, $state, $http, zm)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ // console.log("**VIEW ** News Ctrl Entered");
+ NVRDataModel.setAwake(false);
+
+ });
+
+ $scope.isUnread = function(itemdate)
+ {
+ var lastDate = NVRDataModel.getLatestBlogPostChecked();
+ //console.log ("BLOG DATE="+itemdate+" LAST DATE="+lastDate);
+ //get("latestBlogPostChecked");
+ if (!lastDate) return true;
+ var mLastDate = moment(lastDate);
+ var mItemDate = moment(itemdate);
+ //var unread = mItemDate.diff(mLastDate) >0) ? true:false;
+ //console.log (unread);
+ return (mItemDate.diff(mLastDate, 'seconds') > 0) ? true : false;
+
+ };
+
+ $scope.loadPost = function(item, itemdate)
+ {
+ var lastDate =
+ NVRDataModel.getLatestBlogPostChecked(); //zmStorageService.get("latestBlogPostChecked");
+
+ if (!lastDate)
+ {
+ NVRDataModel.debug("First time checking blog posts, I see");
+ NVRDataModel.setLatestBlogPostChecked(itemdate);
+ //zmStorageService.set("latestBlogPostChecked", itemdate);
+ }
+ else
+ {
+ NVRDataModel.debug("last post checked is " + lastDate);
+ NVRDataModel.debug("current post dated is " + itemdate);
+
+ var mLastDate = moment(lastDate);
+ var mItemDate = moment(itemdate);
+ if (mItemDate.diff(mLastDate, 'seconds') > 0)
+ {
+ NVRDataModel.debug("Updating lastDate to this post");
+
+ NVRDataModel.setLatestBlogPostChecked(itemdate); //zmStorageService.set("latestBlogPostChecked", itemdate);
+
+ if (itemdate == $scope.newsItems[0].date)
+ {
+ // we are reading the latest post
+ $rootScope.newBlogPost = "";
+ }
+ }
+
+ }
+
+ window.open(item, '_blank', 'location=yes');
+ return false;
+ };
+
+ $scope.newsItems = [];
+
+
+ $http.get(zm.blogUrl,
+ {transformResponse: function(d,h)
+ {
+ var trunc = "])}while(1);</x>";
+ d = d.substr(trunc.length);
+ return d;
+ }
+ })
+ .success(function(datastr)
+ {
+
+
+ // console.log ("DATA:"+data);
+ //
+ var data = JSON.parse(datastr);
+ for (var i = 0; i < data.payload.posts.length; i++)
+ {
+ $scope.newsItems.push(
+ {
+ title: data.payload.posts[i].title,
+ url: "https://medium.com/zmninja/"+data.payload.posts[i].uniqueSlug,
+ date: moment(data.payload.posts[i].createdAt).format("YYYY-MM-DD HH:mm:ss")
+ });
+ }
+
+ });
+
+}]);
diff --git a/www/js/PortalLoginCtrl.js b/www/js/PortalLoginCtrl.js
new file mode 100644
index 00000000..6a9a9f2d
--- /dev/null
+++ b/www/js/PortalLoginCtrl.js
@@ -0,0 +1,454 @@
+/* jshint -W041 */
+/* jshint -W083 */
+/*This is for the loop closure I am using in line 143 */
+/* jslint browser: true*/
+/* global vis,cordova,StatusBar,angular,console,moment */
+angular.module('zmApp.controllers').controller('zmApp.PortalLoginCtrl', ['$ionicPlatform', '$scope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$rootScope', '$http', '$q', '$state', '$ionicLoading', '$ionicPopover', '$ionicScrollDelegate', '$ionicModal', '$timeout', 'zmAutoLogin', '$ionicHistory', '$cordovaTouchID', 'EventServer', '$translate', function($ionicPlatform, $scope, zm, NVRDataModel, $ionicSideMenuDelegate, $rootScope, $http, $q, $state, $ionicLoading, $ionicPopover, $ionicScrollDelegate, $ionicModal, $timeout, zmAutoLogin, $ionicHistory, $cordovaTouchID, EventServer, $translate)
+{
+
+ $scope.$on('$ionicView.enter',
+ function()
+ {
+
+ NVRDataModel.debug("Inside Portal login Enter handler");
+ loginData = NVRDataModel.getLogin();
+
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+
+ $scope.pindata = {};
+ if ($ionicSideMenuDelegate.isOpen())
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ NVRDataModel.debug("Sliding menu close");
+ }
+
+ $scope.pinPrompt = false; // if true, then PIN is displayed else skip
+
+ if (NVRDataModel.isLoggedIn())
+ {
+ NVRDataModel.log("User credentials are provided");
+
+ // You can login either via touch ID or typing in your code
+ if ($ionicPlatform.is('ios') && loginData.usePin)
+ {
+ $cordovaTouchID.checkSupport()
+ .then(function()
+ {
+ // success, TouchID supported
+ $cordovaTouchID.authenticate("")
+ .then(function()
+ {
+ NVRDataModel.log("Touch Success");
+ // Don't assign pin as it may be alphanum
+ unlock(true);
+
+ },
+ function()
+ {
+ NVRDataModel.log("Touch Failed");
+ });
+ }, function(error)
+ {
+ NVRDataModel.log("TouchID not supported");
+ });
+ }
+ else // touch was not used
+ {
+ NVRDataModel.log("not checking for touchID");
+ }
+
+ if (loginData.usePin)
+ {
+ // this shows the pin prompt on screen
+ $scope.pinPrompt = true;
+ // dont call unlock, let the user type in code
+
+ }
+ else // no PIN Code so go directly to auth
+ {
+
+ unlock(true);
+ }
+
+ }
+ else // login creds are not present
+ {
+ NVRDataModel.debug("PortalLogin: Not logged in, so going to login");
+ if (NVRDataModel.isFirstUse())
+ {
+ NVRDataModel.debug("First use, showing warm and fuzzy...");
+ $ionicHistory.nextViewOptions(
+ {
+ disableAnimate: true,
+ disableBack: true
+ });
+ $state.go('first-use');
+ return;
+ }
+ else
+ {
+ if (!$rootScope.userCancelledAuth)
+ {
+ $ionicHistory.nextViewOptions(
+ {
+ disableAnimate: true,
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+ else
+ {
+ // do this only once - rest for next time
+ $rootScope.userCancelledAuth = false;
+ }
+ }
+ }
+
+ });
+
+ //-------------------------------------------------------------------------------
+ // remove status is pin is empty
+ //-------------------------------------------------------------------------------
+
+ $scope.pinChange = function()
+ {
+ if ($scope.pindata.pin == null)
+ {
+ $scope.pindata.status = "";
+ }
+ };
+
+ //-------------------------------------------------------------------------------
+ // unlock app if PIN is correct
+ //-------------------------------------------------------------------------------
+ $scope.unlock = function()
+ {
+ // call with false meaning check for pin
+ unlock(false);
+ };
+
+ //------------------------------------------------------------------------
+ // Aaron Lager hack - can't figure out why he gets a 401 after
+ // successful login and then it works after resaving
+ //------------------------------------------------------------------------
+ function tryLoggingSecondTimeHack()
+ {
+ var d = $q.defer();
+
+ zmAutoLogin.doLogin("<button class='button button-clear' style='line-height: normal; min-height: 0; min-width: 0;color:#fff;' ng-click='$root.cancelAuth()'><i class='ion-close-circled'></i>&nbsp;" + $translate.instant('kAuthenticating') + "...</button>")
+ .then(function(data) // success
+ {
+ NVRDataModel.debug("2nd auth login worked");
+ NVRDataModel.getAPIversion()
+ .then(function(data)
+ {
+ NVRDataModel.getKeyConfigParams(1);
+ NVRDataModel.log("2nd auth:Got API version: " + data);
+ $rootScope.apiVersion = data;
+ var ld = NVRDataModel.getLogin();
+ if (NVRDataModel.versionCompare(data, zm.minAppVersion) == -1 && data != "0.0.0")
+ {
+
+ $state.go('lowversion',
+ {
+ "ver": data
+ });
+ return;
+ }
+
+ if (NVRDataModel.versionCompare(data, zm.recommendedAppVersion) == -1 && data != "0.0.0")
+ {
+
+ $state.go('importantmessage',
+ {
+ "ver": data
+ });
+ return;
+ }
+
+ /*if (data == "0.0.0")
+ {
+
+ NVRDataModel.log("2nd Auth:API getVersion succeeded but returned 0.0.0 " + JSON.stringify(data));
+ NVRDataModel.displayBanner('error', ['ZoneMinder authentication failed']);
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }*/
+ // coming here means continue
+ EventServer.refresh();
+
+ var statetoGo = $rootScope.lastState ? $rootScope.lastState : 'montage';
+ //NVRDataModel.debug ("logging state transition");
+ NVRDataModel.debug("2nd Auth: Transitioning state to: " +
+ statetoGo + " with param " + JSON.stringify($rootScope.lastStateParam));
+ $state.go(statetoGo, $rootScope.lastStateParam);
+ return;
+
+ },
+ function(error)
+ {
+ NVRDataModel.debug("2nd auth API failed, going to login");
+ d.reject("failed 2nd auth");
+ return (d.promise);
+
+ });
+
+ },
+ function(error)
+ {
+ NVRDataModel.debug("2nd auth hack failed, going to login");
+ d.reject("failed 2nd auth");
+ return (d.promise);
+ });
+
+ return (d.promise);
+ }
+
+ function evaluateTappedNotification()
+ {
+ if ($rootScope.tappedNotification)
+ {
+
+ var ld = NVRDataModel.getLogin();
+ NVRDataModel.log("Came via push tap. onTapScreen=" + ld.onTapScreen);
+ //console.log ("***** NOTIFICATION TAPPED ");
+ $rootScope.tappedNotification = 0;
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+
+ if (ld.onTapScreen == $translate.instant('kTapMontage'))
+ {
+ NVRDataModel.debug("Going to montage");
+ $state.go("montage",
+ {},
+ {
+ reload: true
+ });
+
+ return;
+ }
+ else if (ld.onTapScreen == $translate.instant('kTapEvents'))
+ {
+ NVRDataModel.debug("Going to events");
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ else // we go to live
+ {
+ NVRDataModel.debug("Going to live view ");
+ $state.go("monitors",
+ {},
+ {
+ reload: true
+ });
+ return;
+ }
+ }
+
+ }
+
+ function unlock(idVerified)
+ {
+ /*
+ idVerified == true means no pin check needed
+ == false means check PIN
+ */
+
+ NVRDataModel.debug("unlock called with check PIN=" + idVerified);
+ if (idVerified || ($scope.pindata.pin == loginData.pinCode))
+ {
+ NVRDataModel.debug("PIN code entered is correct, or there is no PIN set");
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ zmAutoLogin.stop(); //safety
+ zmAutoLogin.start();
+
+ // PIN is fine, or not set so lets login
+ zmAutoLogin.doLogin("<button class='button button-clear' style='line-height: normal; min-height: 0; min-width: 0;color:#fff;' ng-click='$root.cancelAuth()'><i class='ion-close-circled'></i>&nbsp;" + $translate.instant('kAuthenticating') + "...</button>")
+ .then(function(data) // success
+ {
+ NVRDataModel.debug("PortalLogin: auth success");
+
+
+ // $state.go("login" ,{"wizard": false});
+ //login was ok, so get API details
+ NVRDataModel.getAPIversion()
+ .then(function(data)
+ {
+ NVRDataModel.log("Got API version: " + data);
+ $rootScope.apiVersion = data;
+ var ld = NVRDataModel.getLogin();
+ if (NVRDataModel.versionCompare(data, zm.minAppVersion) == -1 && data != "0.0.0")
+ {
+
+ $state.go('lowversion',
+ {
+ "ver": data
+ });
+ return;
+ }
+
+ if (NVRDataModel.versionCompare(data, zm.recommendedAppVersion) == -1 && data != "0.0.0")
+ {
+
+ // console.log (">>>>>>>>>>>>> HERE AND VERSION SAYS " +NVRDataModel.versionCompare(data, zm.recommendedAppVersion));
+ //console.log ("GOING TO IMPORTANT");
+ $state.go('importantmessage',
+ {
+ "ver": data
+ });
+ return;
+ }
+
+ /*if (data == "0.0.0")
+ {
+
+ NVRDataModel.log("API getVersion succeeded but returned 0.0.0 " + JSON.stringify(data));
+ NVRDataModel.displayBanner('error', ['ZoneMinder authentication failed']);
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+
+ }*/
+ // coming here means continue
+ // console.log (">>>>>>>>>>>>>>>>>>>>>>>>>NEVER");
+
+ NVRDataModel.getKeyConfigParams(1);
+ NVRDataModel.getTimeZone();
+ EventServer.refresh();
+ if ($rootScope.tappedNotification != 1)
+ {
+ console.log ("NOTIFICATION TAPPED INSIDE CHECK IS "+$rootScope.tappedNotification);
+ var statetoGo = $rootScope.lastState ? $rootScope.lastState : 'montage';
+ NVRDataModel.debug("logging state transition");
+ NVRDataModel.debug("Transitioning state to: " +
+ statetoGo + " with param " + JSON.stringify($rootScope.lastStateParam));
+
+ $state.go(statetoGo, $rootScope.lastStateParam);
+ return;
+
+ }
+ else
+ evaluateTappedNotification();
+
+
+ },
+ function(error)
+ { // API Error
+ NVRDataModel.log("API Error handler: going to login getAPI returned error: " + JSON.stringify(error));
+ //NVRDataModel.displayBanner('error', ['ZoneMinder authentication failed']);
+
+ NVRDataModel.debug("Doing the Aaron Hack after 1 sec....");
+ $timeout(function()
+ {
+ tryLoggingSecondTimeHack()
+ .then(function success(s)
+ {
+ NVRDataModel.log("2nd time login hack worked!, nothing to do");
+ NVRDataModel.getTimeZone();
+ },
+ function error(e)
+ {
+
+ if ($rootScope.apiValid == true)
+ {
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+ else
+ {
+ $state.go("invalidapi");
+ return;
+ }
+
+ });
+
+ return;
+
+ }, 1000);
+
+ });
+
+
+
+ },
+ // coming here means auth error
+ // so go back to login
+ function(error)
+ {
+ NVRDataModel.debug("PortalLogin: error authenticating " +
+ JSON.stringify(error));
+ if (!$rootScope.userCancelledAuth)
+ {
+ NVRDataModel.displayBanner('error', ['ZoneMinder authentication failed', 'Please check API settings']);
+ $ionicHistory.nextViewOptions(
+ {
+ disableAnimate: true,
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+ else
+ {
+ // if user cancelled auth I guess we go to login
+ $rootScope.userCancelledAuth = false;
+ $state.go("login",
+ {
+ "wizard": false
+ });
+ return;
+ }
+ });
+ }
+ else
+ {
+ $scope.pindata.status = "Invalid PIN";
+
+ // wobble the input box on error
+ var element = angular.element(document.getElementById("pin-box"));
+
+ element.addClass("animated shake")
+ .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend',
+ function()
+ {
+ element.removeClass("animated shake");
+ });
+ }
+ }
+
+ //-------------------------------------------------------------------------------
+ // Controller Main
+ //-------------------------------------------------------------------------------
+ // console.log("************* ENTERING PORTAL MAIN ");
+ NVRDataModel.log("Entering Portal Main");
+ var loginData;
+ $ionicSideMenuDelegate.canDragContent(true);
+
+}]);
diff --git a/www/js/StateCtrl.js b/www/js/StateCtrl.js
new file mode 100644
index 00000000..4faec748
--- /dev/null
+++ b/www/js/StateCtrl.js
@@ -0,0 +1,410 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+// controller for State View
+
+angular.module('zmApp.controllers').controller('zmApp.StateCtrl', ['$ionicPopup', '$scope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$ionicLoading', '$ionicModal', '$state', '$http', '$rootScope', '$timeout', '$ionicHistory', '$translate', function(
+ $ionicPopup, $scope, zm, NVRDataModel, $ionicSideMenuDelegate, $ionicLoading, $ionicModal, $state, $http, $rootScope, $timeout, $ionicHistory, $translate)
+{
+
+ //----------------------------------------------------------------------
+ // Controller main
+ //----------------------------------------------------------------------
+ $scope.zmRun = "...";
+ $scope.zmLoad = "...";
+ $scope.zmDisk = "...";
+ $scope.color = "";
+ $scope.showDanger = false;
+ $scope.dangerText = [$translate.instant('kStateShowControls'), $translate.instant('kStateHideControls')];
+ $scope.dangerButtonColor = ["button-positive", "button-assertive"];
+ $scope.customState = "";
+ $scope.allStateNames = [];
+
+ $rootScope.zmPopup = "";
+
+ var loginData = NVRDataModel.getLogin();
+
+ var apiRun = loginData.apiurl + "/host/daemonCheck.json";
+ var apiLoad = loginData.apiurl + "/host/getLoad.json";
+ var apiDisk = loginData.apiurl + "/host/getDiskPercent.json";
+ var apiCurrentState = loginData.apiurl + "/States.json";
+
+ var apiExec = loginData.apiurl + "/states/change/";
+
+ var inProgress = 0; // prevents user from another op if one is in progress
+ getRunStatus();
+
+ // Let's stagger this by 500ms each to see if Chrome lets these through
+ // This may also help if your Apache is not configured to let multiple connections through
+
+ $timeout(function()
+ {
+ NVRDataModel.debug("invoking LoadStatus...");
+ getLoadStatus();
+ }, 2000);
+
+ $timeout(function()
+ {
+ NVRDataModel.debug("invoking CurrentState...");
+ getCurrentState();
+ }, 4000);
+
+ /*
+ $timeout(function () {
+ NVRDataModel.debug("invoking DiskStatus...");
+ getDiskStatus();
+ }, 6000);
+ */
+ //-------------------------------------------------------------------------
+ // Lets make sure we set screen dim properly as we enter
+ // The problem is we enter other states before we leave previous states
+ // from a callback perspective in ionic, so we really can't predictably
+ // reset power state on exit as if it is called after we enter another
+ // state, that effectively overwrites current view power management needs
+ //------------------------------------------------------------------------
+ $scope.$on('$ionicView.enter', function()
+ {
+ // console.log("**VIEW ** Montage Ctrl Entered");
+ NVRDataModel.setAwake(false);
+ });
+
+ //---------------------------------------------------------
+ // This gets the current run state custom name
+ // if applicable
+ //---------------------------------------------------------
+ function getCurrentState()
+ {
+ NVRDataModel.debug("StateCtrl: getting state using " + apiCurrentState);
+ $http.get(apiCurrentState)
+ .then(
+ function(success)
+ {
+ NVRDataModel.debug("State results: " + JSON.stringify(success));
+ var customStateArray = success.data.states;
+ var i = 0;
+ var found = false;
+ $scope.allStateNames = [];
+ for (i = 0; i < customStateArray.length; i++)
+ {
+ $scope.allStateNames.push(customStateArray[i].State.Name);
+ if (customStateArray[i].State.IsActive == '1')
+ {
+ $scope.customState = customStateArray[i].State.Name;
+ found = true;
+ }
+ }
+ if (!found) $scope.customState = "";
+
+ },
+ function(error)
+ {
+ NVRDataModel.debug("StateCtrl: Error retrieving state list " + JSON.stringify(error));
+ $scope.customState = "";
+
+ }
+ );
+
+ }
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ }
+ };
+
+ //---------------------------------------------------------
+ // Allows the user to select a custom run state
+ //---------------------------------------------------------
+ $scope.selectCustomState = function()
+ {
+ $scope.myopt = {
+ selectedState: ""
+ };
+ //console.log(JSON.stringify($scope.allStateNames));
+ NVRDataModel.log("List of custom states: " + JSON.stringify($scope.allStateNames));
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ scope: $scope,
+ template: '<ion-radio-fix ng-repeat="item in allStateNames" ng-value="item" ng-model="myopt.selectedState"> {{item}} </ion-radio-fix>',
+
+ title: $translate.instant('kSelectRunState'),
+ subTitle: $translate.instant('kCurrentState') + $scope.customState ? ($translate.instant('kCurrentState') + ": " + $scope.customState) : "",
+ buttons: [
+ {
+ text: $translate.instant('kButtonCancel'),
+ onTap: function(e)
+ {
+ return "CANCEL";
+ }
+
+ },
+ {
+ text: $translate.instant('kButtonOk'),
+ onTap: function(e)
+ {
+ return "OK";
+
+ }
+ }]
+ });
+
+ // It seems invoking a popup within a popup handler
+ // causes issues. Doing this outside due to that reason
+ $rootScope.zmPopup.then(function(res)
+ {
+ // console.log("GOT : " + JSON.stringify(res));
+ if (res == "OK")
+ {
+ if ($scope.myopt.selectedState != "")
+ controlZM($scope.myopt.selectedState);
+ }
+ });
+ };
+
+ //----------------------------------------------------------------------
+ // returns disk space in gigs taken up by events
+ //----------------------------------------------------------------------
+ function getDiskStatus()
+ {
+ NVRDataModel.debug("StateCtrl/getDiskStatus: " + apiDisk);
+ $http.get(apiDisk)
+ .then(
+ function(success)
+ {
+ NVRDataModel.debug("StateCtrl/getDiskStatus: success");
+ NVRDataModel.debug("Disk results: " + JSON.stringify(success));
+ var obj = success.data.usage;
+ if (obj.Total.space != undefined)
+ {
+ $scope.zmDisk = parseFloat(obj.Total.space).toFixed(1).toString() + "G";
+ }
+ else
+ {
+ $scope.zmDisk = "unknown";
+ NVRDataModel.log("Error retrieving disk space, API returned null for obj.Total.space");
+ }
+
+ },
+ function(error)
+ {
+ $scope.zmDisk = "unknown";
+ // console.log("ERROR:" + JSON.stringify(error));
+ NVRDataModel.log("Error retrieving DiskStatus: " + JSON.stringify(error), "error");
+ }
+ );
+ }
+
+ //----------------------------------------------------------------------
+ // returns ZM running status
+ //----------------------------------------------------------------------
+ function getRunStatus()
+ {
+ NVRDataModel.debug("StateCtrl/getRunStatus: " + apiRun);
+ $http.get(apiRun)
+ .then(
+ function(success)
+ {
+ NVRDataModel.debug("StateCtrl/getRunStatus: success");
+ NVRDataModel.debug("Run results: " + JSON.stringify(success));
+ switch (success.data.result)
+ {
+ case 1:
+ $scope.zmRun = $translate.instant('kZMRunning');
+ $scope.color = 'color:green;';
+ break;
+ case 0:
+ $scope.zmRun = $translate.instant('kZMStopped');
+ $scope.color = 'color:red;';
+ break;
+ default:
+ $scope.zmRun = $translate.instant('kZMUndetermined');
+ $scope.color = 'color:orange;';
+
+ break;
+ }
+
+ // console.log("X"+success.data.result+"X");
+ },
+ function(error)
+ {
+ //console.log("ERROR in getRun: " + JSON.stringify(error));
+ NVRDataModel.log("Error getting RunStatus " + JSON.stringify(error), "error");
+ $scope.color = 'color:red;';
+ $scope.zmRun = $translate.instant('kZMUndetermined');
+ }
+ );
+
+ }
+
+ //----------------------------------------------------------------------
+ // gets ZM load - max[0], avg[1], min[2]
+ //----------------------------------------------------------------------
+ function getLoadStatus()
+ {
+ NVRDataModel.debug("StateCtrl/getLoadStatus: " + apiLoad);
+ $http.get(apiLoad)
+ .then(
+ function(success)
+ {
+ NVRDataModel.debug("Load results: " + JSON.stringify(success));
+ //console.log(JSON.stringify(success));
+ // load returns 3 params - one in the middle is avg.
+ NVRDataModel.debug("StateCtrl/getLoadStatus: success");
+ $scope.zmLoad = success.data.load[1];
+
+ // console.log("X"+success.data.result+"X");
+ },
+ function(error)
+ {
+ //console.log("ERROR in getLoad: " + JSON.stringify(error));
+ NVRDataModel.log("Error retrieving loadStatus " + JSON.stringify(error), "error");
+ $scope.zmLoad = 'undetermined';
+ }
+ );
+ }
+
+ //----------------------------------------------------------------------
+ // start/stop/restart ZM
+ //----------------------------------------------------------------------
+
+ function performZMoperation(str)
+ {
+
+ NVRDataModel.debug("inside performZMoperation with " + str);
+
+ $scope.zmRun = "...";
+ $scope.color = 'color:orange;';
+ $scope.customState = "";
+ NVRDataModel.debug("StateCtrl/controlZM: POST Control command is " + apiExec + str + ".json");
+ inProgress = 1;
+ $http.post(apiExec + str + ".json")
+ .then(
+ function(success)
+ {
+ NVRDataModel.debug("StateCtrl/controlZM: returned success");
+ inProgress = 0;
+ switch (str)
+ {
+ case "stop":
+ $scope.zmRun = $translate.instant('kZMStopped');
+ $scope.color = 'color:red;';
+ break;
+ default:
+ $scope.zmRun = $translate.instant('kZMRunning');
+ $scope.color = 'color:green;';
+ getCurrentState();
+ break;
+
+ }
+
+ },
+ function(error)
+ {
+ //if (error.status) // it seems to return error with status 0 if ok
+ // {
+ //console.log("ERROR in Change State:" + JSON.stringify(error));
+ NVRDataModel.debug("StateCtrl/controlZM: returned error");
+ NVRDataModel.log("Error in change run state:" + JSON.stringify(error), "error");
+ $scope.zmRun = $translate.instant('kZMUndetermined');
+ $scope.color = 'color:orange;';
+ inProgress = 0;
+
+ });
+ }
+
+ function controlZM(str)
+ {
+ if (inProgress)
+ {
+ NVRDataModel.debug("StateCtrl/controlZM: operation in progress");
+ $ionicPopup.alert(
+ {
+ title: $translate.instant('kOperationInProgressTitle'),
+ template: $translate.instant('kOperationInProgressBody') + '...',
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ return;
+ }
+
+ var statesearch = "startstoprestart";
+
+ var promptstring = $translate.instant('kStateAreYouSure') + str + ' Zoneminder?';
+ if (statesearch.indexOf(str) == -1)
+ {
+ promptstring = "Are you sure you want to change state to " + str;
+ }
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ title: $translate.instant('kPleaseConfirm'),
+ template: promptstring,
+ buttons: [
+ {
+ text: $translate.instant('kButtonCancel'),
+ type: 'button-positive'
+ },
+ {
+ text: $translate.instant('kButtonOk'),
+ type: 'button-assertive',
+ onTap: function(e)
+ {
+ performZMoperation(str);
+ }
+ }]
+ });
+
+ }
+
+ // Binder so template can call controlZM
+ $scope.controlZM = function(str)
+ {
+ controlZM(str);
+ };
+
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.$on('$ionicView.leave', function()
+ {
+ console.log("**VIEW ** State Ctrl Left");
+ // FIXME not the best way...
+ // If the user exits a view before its complete,
+ // make sure he can come back in and redo
+ inProgress = 0;
+ });
+
+ $scope.doRefresh = function()
+ {
+ console.log("***Pull to Refresh");
+ NVRDataModel.debug("StateCtrl/refresh: calling getRun/Load/Disk/CurrentState");
+ getRunStatus();
+ $timeout(getLoadStatus, 2000);
+ $timeout(getCurrentState, 4000);
+ //$timeout (getDiskStatus,6000);
+ $scope.$broadcast('scroll.refreshComplete');
+
+ };
+
+}]);
diff --git a/www/js/TimelineCtrl.js b/www/js/TimelineCtrl.js
new file mode 100644
index 00000000..101e7166
--- /dev/null
+++ b/www/js/TimelineCtrl.js
@@ -0,0 +1,1608 @@
+/* jshint -W041 */
+/* jshint -W083 */
+/*This is for the loop closure I am using in line 143 */
+/* jslint browser: true*/
+/* global vis,cordova,StatusBar,angular,console,moment */
+
+// This controller creates a timeline
+// It uses the visjs library, but due to performance reasons
+// I've disabled pan and zoom and used buttons instead
+// also limits # of items to maxItems
+
+// FIXME: too much redundant code between EventCtrl and Timeline
+// Move to ModalCtrl and see if it works
+
+angular.module('zmApp.controllers').controller('zmApp.TimelineCtrl', ['$ionicPlatform', '$scope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$rootScope', '$http', '$q', 'message', '$state', '$ionicLoading', '$ionicPopover', '$ionicScrollDelegate', '$ionicModal', '$timeout', '$ionicContentBanner', '$ionicHistory', '$sce', '$stateParams', '$translate', '$ionicPopup', '$interval', function($ionicPlatform, $scope, zm, NVRDataModel, $ionicSideMenuDelegate, $rootScope, $http, $q, message, $state, $ionicLoading, $ionicPopover, $ionicScrollDelegate, $ionicModal, $timeout, $ionicContentBanner, $ionicHistory, $sce, $stateParams, $translate, $ionicPopup, $interval)
+{
+
+ //console.log("Inside Timeline controller");
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //---------------------------------------f-------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ $scope.leftButtons = [
+ {
+ type: 'button-icon icon ion-navicon',
+ tap: function(e)
+ {
+ $scope.toggleMenu();
+ }
+ }];
+
+ //-----------------------------------------------------------
+ // Used to display date range for timeline
+ //-----------------------------------------------------------
+ $scope.prettify = function(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format('MMMM Do YYYY, ' + NVRDataModel.getTimeFormat());
+ else
+ return moment(str).format('MMMM Do YYYY, ' + NVRDataModel.getTimeFormat());
+ };
+
+ //-----------------------------------------------------------
+ // used for playback when you tap on a timeline event
+ //-----------------------------------------------------------
+ $scope.calcMsTimer = function(frames, len)
+ {
+ var myframes, mylen;
+ myframes = parseFloat(frames);
+ mylen = parseFloat(len);
+ // console.log ("frames " + myframes + "length " + mylen);
+ // console.log ("*** MS COUNT " + (1000.0/(myframes/mylen)));
+ return (Math.round(1000 / (myframes / mylen)));
+ };
+
+ $scope.toggleMinAlarmFrameCount = function()
+ {
+ // console.log("Toggling");
+
+ var ld = NVRDataModel.getLogin();
+ ld.enableAlarmCount = !ld.enableAlarmCount;
+
+ NVRDataModel.setLogin(ld);
+
+ drawGraph(curFromDate, curToDate, curCount);
+
+ };
+
+ //-----------------------------------------------------------
+ // Move/Zoom are used to move the timeline around
+ //-----------------------------------------------------------
+ function move(percentage)
+ {
+ var range = timeline.getWindow();
+ var interval = range.end - range.start;
+
+ timeline.setWindow(
+ {
+ start: range.start.valueOf() - interval * percentage,
+ end: range.end.valueOf() - interval * percentage
+ });
+ }
+
+ // helps to navigate to current time quickly
+ // after a night of heavy navigation
+ $scope.gotoNow = function()
+ {
+ timeline.moveTo(timeline.getCurrentTime());
+ };
+
+ $scope.move = function(percentage)
+ {
+ move(percentage);
+
+ };
+
+ //-----------------------------------------
+ // Move by X days
+ //-----------------------------------------
+ $scope.moveDays = function(d)
+ {
+ var range = timeline.getWindow();
+ var ds = moment(range.start);
+ if (d > 0)
+ ds.add(d, 'days');
+ else
+ ds.subtract(Math.abs(d), 'days');
+
+ var es = moment(ds); // clone it!
+ es.add(1, 'day');
+
+ fromDate = ds.format("YYYY-MM-DD HH:mm:ss");
+ toDate = es.format("YYYY-MM-DD HH:mm:ss");
+
+ $scope.fromDate = fromDate;
+ $scope.toDate = toDate;
+ $rootScope.customTimelineRange = false;
+ NVRDataModel.log("moving by " + d + " day to " + fromDate + " upto " + toDate);
+ drawGraph(fromDate, toDate, maxItems);
+
+ };
+
+ function eventDetails(ev)
+ {
+ $scope.event = ev;
+ $ionicModal.fromTemplateUrl('templates/timeline-modal.html',
+ {
+ scope: $scope, // give ModalCtrl access to this scope
+ animation: 'slide-in-up',
+ id: 'analyze',
+
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $scope.modal.show();
+
+ });
+ }
+
+ //--------------------------------------------------------
+ // To show a modal dialog with the event tapped on in timeline
+ // FIXME : code repeat from Events
+ //--------------------------------------------------------
+ function openModal(event)
+ {
+
+ if ($scope.modalFromTimelineIsOpen == true)
+ {
+ // don't know why but some conflict from angular to timeline lib
+ // results in double modals at times
+ NVRDataModel.log(">>-- duplicate modal detected, preventing");
+ }
+
+ $scope.modalFromTimelineIsOpen = true;
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ // pass this event to ModalCtrl
+ $scope.currentEvent = event;
+
+ $scope.event = event;
+ // in Timeline view, make sure events stick to same monitor
+ $scope.followSameMonitor = "1";
+
+ //prepareModalEvent(event.Event.Id);
+
+ $ionicModal.fromTemplateUrl('templates/events-modal.html',
+ {
+ scope: $scope, // give ModalCtrl access to this scope
+ animation: 'slide-in-up',
+ id: 'footage'
+ })
+ .then(function(modal)
+ {
+ $scope.modal = modal;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: 10000,
+
+ });
+
+ $scope.modal.show();
+
+ var ld = NVRDataModel.getLogin();
+
+ });
+
+ }
+
+ //--------------------------------------------------------
+ //We need to destroy because we are instantiating
+ // it on open
+ //--------------------------------------------------------
+ $scope.closeModal = function()
+ {
+ $scope.modalFromTimelineIsOpen = false;
+ // $interval.cancel(eventsInterval);
+ //$interval.cancel(segmentHandle);
+ NVRDataModel.debug("TimelineCtrl:Close & Destroy Modal");
+ NVRDataModel.stopNetwork("TimelineCtrl: closeModal");
+ NVRDataModel.setAwake(false);
+ if ($scope.modal !== undefined)
+ {
+ $scope.modal.remove();
+ }
+
+ };
+
+ /* $scope.toggleGapless = function()
+ {
+ console.log ("GAPLESS TOGGLE");
+ $scope.loginData.gapless = !$scope.loginData.gapless;
+ NVRDataModel.setLogin($scope.loginData);
+
+ };*/
+
+ //-------------------------------------------------------------------------
+ // called when user switches to background
+ //-------------------------------------------------------------------------
+ function onPause()
+ {
+ NVRDataModel.debug("TimelineCtrl:onpause called");
+ $interval.cancel(updateInterval);
+ // console.log("*** Moving to Background ***"); // Handle the pause event
+
+ if ($scope.popover) $scope.popover.remove();
+
+ }
+
+ //--------------------------------------------------------
+ // This function is called by the graph ontapped function
+ // which in turn calls openModal
+ //--------------------------------------------------------
+
+ function showEvent(event)
+ {
+
+ // in context of angular
+
+ $timeout(function()
+ {
+ openModal(event);
+ });
+
+ }
+
+ $rootScope.$on('tz-updated', function()
+ {
+ $scope.tzAbbr = NVRDataModel.getTimeZoneNow();
+ NVRDataModel.debug("Timezone API updated timezone to " + NVRDataModel.getTimeZoneNow());
+ });
+
+ //-------------------------------------------------
+ // Make sure we delete the timeline
+ // This may be redundant as the root view gets
+ // destroyed but no harm
+ //-------------------------------------------------
+ $scope.$on('$ionicView.leave', function()
+ {
+
+ if (timeline)
+ {
+ $interval.cancel(updateInterval);
+ timeline.destroy();
+ console.log("**Destroying Timeline");
+
+ }
+ });
+
+ /*$scope.$on('$ionicView.enter', function() {
+
+
+
+
+ });*/
+
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+
+ //$ionicHistory.clearCache();
+ //$ionicHistory.clearHistory();
+ timeline = '';
+ $scope.newEvents = '';
+
+ });
+
+ //-------------------------------------------------
+ // FIXME: shitty hackery -- Im using a rootScope
+ // to know if you just went to custom range
+ // and back. Fix this, really.
+ // So anyway, if you did select a custom range
+ // then we "go back" to timeline, which is when
+ // we come here - so make sure we update the
+ // graph range
+ //-------------------------------------------------
+
+ $scope.$on('$ionicView.afterEnter', function()
+ {
+
+ $scope.monitors = message;
+ //console.log("***AFTER ENTER");
+
+ $scope.follow = {
+ 'time': NVRDataModel.getLogin().followTimeLine
+ };
+
+ $interval.cancel(updateInterval);
+
+ // Make sure sliding for menu is disabled so it
+ // does not interfere with graph panning
+ $ionicSideMenuDelegate.canDragContent(false);
+ var ld = NVRDataModel.getLogin();
+ maxItemsConf = ($rootScope.platformOS == 'desktop') ? zm.graphDesktopItemMax : zm.graphItemMax;
+ maxItems = ld.graphSize || maxItemsConf;
+ NVRDataModel.log("Graph items to draw is " + maxItems);
+ $scope.maxItems = maxItems;
+ $scope.translationData = {
+ maxItemsVal: maxItems
+ };
+
+ $scope.graphLoaded = false;
+ NVRDataModel.debug("TimelineCtrl/drawGraph: graphLoaded is " + $scope.graphLoaded);
+
+ //latestDateDrawn = moment().locale('en').format("YYYY-MM-DD HH:mm:ss");
+ $scope.modalFromTimelineIsOpen = false;
+ //var tempMon = message;
+
+ // lets timeline.onget the abbreviated version of TZ to display
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ $scope.tzAbbr = moment().tz(moment.tz.guess()).zoneAbbr();
+ }
+ else
+ {
+ $scope.tzAbbr = moment().tz(NVRDataModel.getTimeZoneNow()).zoneAbbr();
+ }
+
+ //console.log ("TIMELINE MONITORS: " + JSON.stringify(message));
+ //var ld = NVRDataModel.getLogin();
+ $scope.loginData = NVRDataModel.getLogin();
+
+ /* if (ld.persistMontageOrder) {
+ var iMon = NVRDataModel.applyMontageMonitorPrefs(tempMon, 2);
+ $scope.monitors = iMon[0];
+ } else*/
+
+ //console.log ("MONITORS:"+JSON.stringify($scope.monitors));
+
+ if ($rootScope.customTimelineRange)
+ {
+ $scope.currentMode = 'custom';
+ //console.log("***** CUSTOM RANGE");
+ if (moment($rootScope.fromString).isValid() &&
+ moment($rootScope.toString).isValid())
+ {
+ // console.log("FROM & TO IS CUSTOM");
+ fromDate = $rootScope.fromString;
+ toDate = $rootScope.toString;
+ $scope.fromDate = fromDate;
+ $scope.toDate = toDate;
+ drawGraph(fromDate, toDate, maxItems);
+ }
+ else
+ {
+ console.log("From:" + $rootScope.fromString + " To:" + $rootScope.toString);
+ //console.log("FROM & TO IS CUSTOM INVALID");
+
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ fromDate = moment().startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ toDate = moment().endOf('day').format("YYYY-MM-DD HH:mm:ss");
+ }
+ else
+ {
+ fromDate = moment().tz(NVRDataModel.getTimeZoneNow()).startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ toDate = moment().tz(NVRDataModel.getTimeZoneNow()).endOf('day').format("YYYY-MM-DD HH:mm:ss");
+ }
+
+ drawGraph(fromDate, toDate, maxItems);
+ }
+ }
+ else
+ {
+ $scope.currentMode = 'day';
+
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ fromDate = moment().startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ toDate = moment().endOf('day').format("YYYY-MM-DD HH:mm:ss");
+ }
+ else
+ {
+ fromDate = moment().tz(NVRDataModel.getTimeZoneNow()).startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ toDate = moment().tz(NVRDataModel.getTimeZoneNow()).endOf('day').format("YYYY-MM-DD HH:mm:ss");
+ }
+ drawGraph(fromDate, toDate, maxItems);
+ }
+
+ $ionicPopover.fromTemplateUrl('templates/timeline-popover.html',
+ {
+ scope: $scope,
+ }).then(function(popover)
+ {
+ $scope.popover = popover;
+ });
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+
+ $ionicPlatform.registerBackButtonAction(function(e)
+ {
+ e.preventDefault();
+ if ($scope.modal != undefined && $scope.modal.isShown())
+ {
+ // switch off awake, as liveview is finished
+ NVRDataModel.debug("Modal is open, closing it");
+ NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+ }
+ else
+ {
+ NVRDataModel.debug("Modal is closed, so toggling or exiting");
+ if (!$ionicSideMenuDelegate.isOpenLeft())
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+
+ }
+ else
+ {
+ navigator.app.exitApp();
+ }
+
+ }
+
+ }, 1000);
+
+ });
+
+ //-------------------------------------------------
+ // Controller main
+ //-------------------------------------------------
+
+ var graphIndex;
+ var updateInterval;
+ var lastTimeForEvent;
+ var groups, graphData;
+ var isProcessNewEventsWaiting = false;
+ var options;
+
+ $scope.mycarousel = {
+ index: 0
+ };
+ $scope.ionRange = {
+ index: 1
+ };
+
+ var curFromDate, curToDate, curCount;
+
+ document.addEventListener("pause", onPause, false);
+
+ // FIXME: Timeline awake to avoid graph redrawing
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ // fromDate and toDate will be used to plot the range for the graph
+ // We start in day mode
+ //
+ var fromDate, toDate;
+
+ fromDate = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow()).startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ toDate = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow()).endOf('day').format("YYYY-MM-DD HH:mm:ss");
+
+ $scope.fromDate = fromDate;
+ $scope.toDate = toDate;
+
+ // maxItems will be ignored during timeline draw if its desktop
+ var maxItemsConf;
+
+ var ld = NVRDataModel.getLogin();
+ var maxItems;
+
+ //flat colors for graph - https://flatuicolors.com http://www.flatuicolorpicker.com
+ var colors = ['#3498db', '#E57373', '#EB974E', '#95A5A6', '#e74c3c', '#03C9A9', ];
+
+ var container;
+ container = angular.element(document.getElementById('visualization'));
+ var timeline;
+
+ //console.log ("RETURNING MONITORS " + JSON.stringify($scope.monitors));
+ //$scope.monitors = message;
+
+ //console.log ("MONITOR DATA AFTER APPLYING : " + JSON.stringify($scope.monitors));
+
+ $scope.navControls = false;
+ var navControls = false;
+
+ //drawGraph(fromDate, toDate, maxItems);
+ //dummyDrawGraph(fromDate, toDate,maxItems);
+
+ //-------------------------------------------------
+ // Rest graph to sane state after you went
+ // wild zooming and panning :-)
+ //-------------------------------------------------
+ $scope.fit = function()
+ {
+ timeline.fit();
+ };
+
+ $scope.toggleNav = function()
+ {
+ if (navControls == true)
+ {
+ navControls = !navControls;
+ // $scope.navControls = navControls;
+ // give out animation time
+ $timeout(function()
+ {
+ $scope.navControls = navControls;
+ }, 2000);
+ }
+ else
+ {
+ navControls = !navControls;
+ $scope.navControls = navControls;
+ }
+ var element = angular.element(document.getElementById("timeline-ctrl"));
+
+ if (navControls)
+ {
+ element.removeClass("animated bounceOutLeft");
+ element.addClass("animated bounceInRight");
+ }
+ else
+ {
+ element.removeClass("animated bounceInRight");
+ element.addClass("animated bounceOutLeft");
+ }
+
+ };
+
+ function shortenTime(str)
+ {
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess()).format(NVRDataModel.getTimeFormat());
+ else
+ return moment(str).format(NVRDataModel.getTimeFormat());
+ }
+
+ $scope.toggleFollowTime = function()
+ {
+ /*if ($scope.currentMode != 'day') {
+ $rootScope.zmPopup = $ionicPopup.alert({
+ title: $translate.instant('kError'),
+ template: $translate.instant('kFollowError')
+ });
+ return;
+ }*/
+ $scope.follow.time = !$scope.follow.time;
+ var loginData = NVRDataModel.getLogin();
+ loginData.followTimeLine = $scope.follow.time;
+ NVRDataModel.setLogin(loginData);
+ };
+ //-------------------------------------------------
+ // Called with day/week/month
+ // so we can redraw the graph
+ //-------------------------------------------------
+
+ $scope.buttonClicked = function(index)
+ {
+ //console.log (index);
+ if (index == 0) //month
+ {
+ $scope.follow.time = NVRDataModel.getLogin().followTimeLine;
+ $scope.currentMode = "month";
+ NVRDataModel.log("Month view");
+ $rootScope.customTimelineRange = false;
+
+ toDate = moment().format("YYYY-MM-DD HH:mm:ss");
+ fromDate = moment().subtract(1, 'month').startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ $scope.fromDate = fromDate;
+ $scope.toDate = toDate;
+ drawGraph(fromDate, toDate, maxItems);
+ }
+ else if (index == 1) //week
+ {
+ $scope.follow.time = NVRDataModel.getLogin().followTimeLine;
+ $scope.currentMode = "week";
+ $rootScope.customTimelineRange = false;
+ NVRDataModel.log("Week view");
+ toDate = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow()).format("YYYY-MM-DD HH:mm:ss");
+ fromDate = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow()).subtract(1, 'week').startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ $scope.fromDate = fromDate;
+ $scope.toDate = toDate;
+ drawGraph(fromDate, toDate, maxItems);
+ }
+ else if (index == 2) //day
+ {
+
+ $scope.currentMode = "day";
+ $rootScope.customTimelineRange = false;
+ NVRDataModel.log("Day view");
+ //toDate = moment().format("YYYY-MM-DD HH:mm:ss");
+ fromDate = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow()).startOf('day').format("YYYY-MM-DD HH:mm:ss");
+ toDate = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow()).endOf('day').format("YYYY-MM-DD HH:mm:ss");
+ $scope.fromDate = fromDate;
+ $scope.toDate = toDate;
+ drawGraph(fromDate, toDate, maxItems);
+ }
+ else // custom
+ {
+ $scope.follow.time = NVRDataModel.getLogin().followTimeLine;
+ $scope.currentMode = "custom";
+ $rootScope.customTimelineRange = true;
+ $state.go('events-date-time-filter');
+ return;
+ }
+
+ };
+
+ /**
+ * [processNewEvents is called every X seconds when dynamic update is on. X = 10 for now]
+ * @return {[type]}
+ */
+ function processNewEvents()
+ {
+
+ //safeguard in the event http calls are still going on
+ if (!$scope.follow.time || isProcessNewEventsWaiting) return;
+
+ var ld = NVRDataModel.getLogin();
+
+ // check for last 2 minutes to account for late DB updates and what not. 5 mins was likely enough
+ //
+
+ // make sure these are server time
+ var from = moment(lastTimeForEvent).tz(NVRDataModel.getTimeZoneNow());
+ from = from.subtract(2, 'minutes').locale('en').format("YYYY-MM-DD HH:mm:ss");
+
+ var to = moment(lastTimeForEvent).tz(NVRDataModel.getTimeZoneNow());
+ to = to.locale('en').format("YYYY-MM-DD HH:mm:ss");
+
+ lastTimeForEvent = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow());
+
+ // FIXME: totally ignoring event pages - hoping it wont be more than 100 or 150 whatever
+ // the events per page limit is. Why? laziness.
+ //
+ var completedEvents = ld.apiurl + '/events/index/EndTime >=:' + from;
+ // we can add alarmCount as this is really for completed events
+ //completedEvents = completedEvents + "/AlarmFrames >=:" + (ld.enableAlarmCount ? ld.minAlarmCount : 0);
+
+ completedEvents = completedEvents + ".json";
+
+ // now get currently ongoing events
+ // as it turns out various events get stored withn null and never recover
+ // so, lets limiy to 15 m
+ //
+
+ var st = moment(lastTimeForEvent).tz(NVRDataModel.getTimeZoneNow());
+ st = st.subtract(10, 'minutes').locale('en').format("YYYY-MM-DD HH:mm:ss");
+ var ongoingEvents = ld.apiurl + '/events/index/StartTime >=:' + st + '/EndTime =:.json';
+ //NVRDataModel.debug("Getting incremental events using: " + completedEvents);
+
+ NVRDataModel.debug("Completed events API:" + completedEvents);
+ NVRDataModel.debug("Ongoing events API:+" + ongoingEvents);
+
+ isProcessNewEventsWaiting = true;
+
+ var $httpApi = $http.get(completedEvents);
+ var $httpOngoing = $http.get(ongoingEvents);
+
+ $q.all([$httpApi, $httpOngoing])
+ .then(function(dataarray)
+ {
+
+ var myevents = dataarray[0].data.events;
+
+ if (dataarray.length > 1)
+ {
+ myevents = myevents.concat(dataarray[1].data.events);
+
+ }
+
+ $scope.newEvents = '';
+ var localNewEvents = '';
+ //console.log ("GOT "+JSON.stringify(data));
+
+ for (var j = 0; j < myevents.length; j++)
+ {
+
+ // these are all in server timezone but no TZ
+
+ myevents[j].Event.StartTime = moment.tz(myevents[j].Event.StartTime, NVRDataModel.getTimeZoneNow()).format('YYYY-MM-DD HH:mm:ss');
+
+ myevents[j].Event.EndTime = moment.tz(myevents[j].Event.EndTime, NVRDataModel.getTimeZoneNow()).format('YYYY-MM-DD HH:mm:ss');
+
+ var itm = graphData.get(myevents[j].Event.Id);
+ if (itm)
+ {
+ // console.log(myevents[j].Event.Id + " already exists, updating params");
+
+ var content = "<span class='my-vis-font'>" + "(" + myevents[j].Event.Id + ")" + myevents[j].Event.Notes + " " + $translate.instant('kRecordingProgress') + "</span>";
+
+ var style;
+ var recordingInProgress = false;
+
+ if (moment(myevents[j].Event.EndTime).isValid()) // recording over
+ {
+ //console.log ("EVENT "+myevents[j].Event.Id+" emded at "+myevents[j].Event.EndTime);
+
+ content = "<span class='my-vis-font'>" + "( <i class='ion-android-notifications'></i>" + myevents[j].Event.AlarmFrames + ") " + " (" + myevents[j].Event.Id + ") " + myevents[j].Event.Notes + "</span>";
+
+ style = "background-color:" + colors[parseInt(myevents[j].Event.MonitorId) % colors.length] +
+ ";border-color:" + colors[parseInt(myevents[j].Event.MonitorId) % colors.length];
+ }
+ else // still recording
+ {
+
+ var tze;
+ tze = moment().tz(NVRDataModel.getTimeZoneNow());
+
+ myevents[j].Event.EndTime = tze.format('YYYY-MM-DD HH:mm:ss');
+
+ //console.log ("END TIME = "+ myevents[j].Event.EndTime);
+
+ style = "background-color:orange";
+ recordingInProgress = true;
+
+ }
+
+ // right at this point we need to decide if we keep or remove this event
+ //
+
+ if (ld.enableAlarmCount && ld.minAlarmCount > myevents[j].Event.AlarmFrames && !recordingInProgress)
+ {
+ // remove
+ NVRDataModel.debug("Removing Event:" + myevents[j].Event.Id + "as it doesn't have " + myevents[j].Event.AlarmFrames + " alarm frames");
+ // var old = timeline.getWindow();
+ graphData.remove(myevents[j].Event.Id);
+ // timeline.setWindow (old.start, old.end);
+ }
+ else
+ {
+
+ var tzs1, tze1;
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ tzs1 = moment.tz(myevents[j].Event.StartTime, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ tze1 = moment.tz(myevents[j].Event.EndTime, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ }
+ else
+ {
+ tzs1 = moment.tz(myevents[j].Event.StartTime, NVRDataModel.getTimeZoneNow());
+ tze1 = moment.tz(myevents[j].Event.EndTime, NVRDataModel.getTimeZoneNow());
+ }
+
+ //tzs1 = tzs1.format("YYYY-MM-DD HH:mm:ss");
+ //tze1 = tze1.format("YYYY-MM-DD HH:mm:ss");
+
+ NVRDataModel.debug("Updating Event:" + myevents[j].Event.Id + "StartTime:" + tzs1.format() + " EndTime:" + tze1.format());
+ graphData.update(
+ {
+ id: myevents[j].Event.Id,
+ content: content,
+ start: tzs1,
+ // start: myevents[j].Event.StartTime,
+ // end: myevents[j].Event.EndTime,
+ end: tze1,
+ //group: myevents[j].Event.MonitorId,
+ //type: "range",
+ style: style,
+ myframes: myevents[j].Event.Frames,
+ mydur: myevents[j].Event.Length,
+ myeid: myevents[j].Event.Id,
+ myename: myevents[j].Event.Name,
+ myvideo: myevents[j].Event.DefaultVideo,
+ myevent: myevents[j]
+
+ });
+
+ //timeline.focus(myevents[j].Event.Id);
+ //
+ timeline.moveTo(timeline.getCurrentTime());
+ //console.log ("Focus EID="+myevents[j].Event.Id);
+ localNewEvents = localNewEvents + NVRDataModel.getMonitorName(myevents[j].Event.MonitorId) + '@' + shortenTime(myevents[j].Event.StartTime) + ' (' + myevents[j].Event.Id + '),';
+
+ }
+
+ }
+ else
+ { // event is new
+
+ var isBeingRecorded = false;
+ var idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == myevents[j].Event.MonitorId && NVRDataModel.isNotHidden(myevents[j].Event.MonitorId))
+ {
+ idfound = true;
+ break;
+ }
+ }
+
+ if (idfound)
+ {
+
+ myevents[j].Event.MonitorName = NVRDataModel.getMonitorName(myevents[j].Event.MonitorId);
+
+ myevents[j].Event.streamingURL = NVRDataModel.getStreamingURL(myevents[j].Event.MonitorId);
+ myevents[j].Event.baseURL = NVRDataModel.getBaseURL(myevents[j].Event.MonitorId);
+ myevents[j].Event.imageMode = NVRDataModel.getImageMode(myevents[j].Event.MonitorId);
+ if (NVRDataModel.getLogin().url != myevents[j].Event.baseURL)
+ {
+
+ myevents[j].Event.baseURL = NVRDataModel.getLogin().url;
+ }
+
+ if (typeof myevents[j].Event.DefaultVideo === 'undefined')
+ // console.log (JSON.stringify(myevents[j]));
+ myevents[j].Event.DefaultVideo = "";
+
+ // now lets make sure we don't infinitely increase
+
+ if (graphIndex >= curCount)
+ //if (1)
+ {
+ var mv = graphData.min('id');
+ //console.log("MIN="+JSON.stringify(mv));
+ if (mv)
+ {
+ graphData.remove(mv.id);
+ graphIndex--;
+ NVRDataModel.debug("Removed Event " + mv.id + " to make space");
+ }
+
+ }
+
+ // since this is a new add its possible dates are not defined
+ if (!moment(myevents[j].Event.StartTime).isValid())
+ {
+ NVRDataModel.log("Event:" + myevents[j].Event.Id + "-Invalid Start time - this should really not happen ");
+
+ }
+
+ if (!moment(myevents[j].Event.EndTime).isValid())
+ {
+ var t1 = moment().tz(NVRDataModel.getTimeZoneNow());
+
+ myevents[j].Event.EndTime = t1.format('YYYY-MM-DD HH:mm:ss');
+
+ NVRDataModel.debug("Event:" + myevents[j].Event.Id + "-End time is invalid, setting to current time");
+
+ isBeingRecorded = true;
+
+ }
+
+ // if range doesn't allow for current time, we need to fix that
+ /*if (moment(options.max).isBefore(moment())) {
+ // console.log("Adjusting Range to fit in new event");
+ options.max = moment().add('1', 'hours').locale('en').format("YYYY-MM-DD HH:mm:ss");
+ timeline.setOptions(options);
+ }*/
+
+ var eventText = "<span class='my-vis-font'>" + "( <i class='ion-android-notifications'></i>" + (myevents[j].Event.AlarmFrames || ' unknown ') + ") " + myevents[j].Event.Notes + "</span>";
+
+ if (isBeingRecorded)
+ {
+ eventText = "<span class='my-vis-font'>" + "(" + myevents[j].Event.Id + ") " + myevents[j].Event.Notes + " " + $translate.instant('kRecordingProgress') + "</span>";
+ }
+
+ // since we concated, its possible events may be repeated
+ if (!graphData.get(myevents[j].Event.Id))
+ {
+
+ localNewEvents = localNewEvents + NVRDataModel.getMonitorName(myevents[j].Event.MonitorId) + '@' + shortenTime(myevents[j].Event.StartTime) + ' (' + myevents[j].Event.Id + '),';
+
+ var tzs2, tze2;
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ tzs2 = moment.tz(myevents[j].Event.StartTime, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ tze2 = moment.tz(myevents[j].Event.EndTime, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ }
+ else
+ {
+ tzs2 = moment.tz(myevents[j].Event.StartTime, NVRDataModel.getTimeZoneNow());
+ tze2 = moment.tz(myevents[j].Event.EndTime, NVRDataModel.getTimeZoneNow());
+ }
+
+ //tzs2 = tzs2.format("YYYY-MM-DD HH:mm:ss");
+ //tze2 = tze2.format("YYYY-MM-DD HH:mm:ss");
+
+ NVRDataModel.debug(">>> " + myevents[j].Event.Id + " New event updating graph " + " from:" + tzs2.format() + " to:" + tze2.format());
+
+ graphData.add(
+ {
+
+ id: myevents[j].Event.Id,
+ content: eventText,
+ start: tzs2,
+ //start: myevents[j].Event.StartTime,
+ // end: myevents[j].Event.EndTime,
+ end: tze2,
+ group: myevents[j].Event.MonitorId,
+ style: "background-color:orange",
+ //type: "range",
+
+ myframes: myevents[j].Event.Frames,
+ mydur: myevents[j].Event.Length,
+ myeid: myevents[j].Event.Id,
+ myename: myevents[j].Event.Name,
+ myvideo: myevents[j].Event.DefaultVideo,
+ myevent: myevents[j]
+
+ });
+ graphIndex++;
+ //timeline.focus(myevents[j].Event.Id);
+ timeline.moveTo(timeline.getCurrentTime());
+ }
+
+ //options.max = moment(fromDate).locale('en').format("YYYY-MM-DD HH:mm:ss");
+
+ } //idfound
+
+ } // new event
+
+ } // for j
+
+ // At this stage, see if we need to display new events
+ if (localNewEvents.length > 0)
+ {
+ localNewEvents = $translate.instant('kLatestEvents') + ':' + localNewEvents;
+ localNewEvents = localNewEvents.slice(0, -1);
+ $scope.newEvents = localNewEvents;
+ }
+ isProcessNewEventsWaiting = false;
+
+ },
+ function(err)
+ {
+ NVRDataModel.debug("Error getting incremental timeline data");
+ isProcessNewEventsWaiting = false;
+
+ });
+
+ // check all events that started 10+10 seconds ago
+
+ }
+
+ //-------------------------------------------------
+ // This function draws the graph
+ //-------------------------------------------------
+
+ function drawGraph(fromDate, toDate, count)
+ {
+
+ console.log("INSIDE DRAW");
+
+ $scope.newEvents = "";
+ // we only need this for day mode
+ $interval.cancel(updateInterval);
+
+ curFromDate = fromDate;
+ curToDate = toDate;
+ curCount = count;
+
+ var isFirstItem = true;
+
+ var fromDateNoLang = moment(fromDate).locale('en').format("YYYY-MM-DD HH:mm:ss");
+ var toDateNoLang = moment(toDate).locale('en').format("YYYY-MM-DD HH:mm:ss");
+
+ //latestDateDrawn =toDateNoLang;
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kLoadingGraph') + "...",
+ animation: 'fade-in',
+ showBackdrop: true,
+ maxWidth: 200,
+ showDelay: 0,
+ duration: zm.loadingTimeout, //specifically for Android - http seems to get stuck at times
+ });
+
+ NVRDataModel.log("TimelineCtrl/drawgraph: from->" + fromDateNoLang + " to->" + toDateNoLang + " count:" + count);
+ $scope.graphLoaded = false;
+ NVRDataModel.debug("TimelineCtrl/drawgraph: graphLoaded:" + $scope.graphLoaded);
+
+ if (timeline)
+ {
+ NVRDataModel.debug("TimelineCtrl/drawgraph: destroying timeline as it exists");
+ timeline.destroy();
+ }
+
+ groups = new vis.DataSet();
+ graphData = new vis.DataSet();
+ //console.log ("AFTER VIS");
+
+ var tzs, tze;
+
+ // lets scope the time graph to either local or remote time zone
+
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ tzs = moment.tz(fromDate, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ tze = moment.tz(toDate, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ }
+ else
+ {
+ tzs = moment.tz(fromDate, NVRDataModel.getTimeZoneNow());
+ tze = moment.tz(toDate, NVRDataModel.getTimeZoneNow());
+ }
+
+ //tzs = tzs.format("YYYY-MM-DD HH:mm:ss");
+ //tze = tze.format("YYYY-MM-DD HH:mm:ss");
+
+ options = {
+
+ showCurrentTime: true,
+ editable: false,
+ moment: function(date)
+ {
+
+ //var t;
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ //if (0)
+ return moment.tz(date, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ else
+ // typecast to server time zone - its in server time anyway
+ return moment.tz(date, NVRDataModel.getTimeZoneNow());
+ },
+ //throttleRedraw: 100,
+ moveable: true,
+ zoomable: true,
+ selectable: true,
+ start: tzs,
+ end: tze,
+ orientation: 'top',
+ min: tzs,
+ //max: tze,
+ zoomMin: 5 * 60 * 1000, // 1 min
+ stack: false,
+ format:
+ {
+ minorLabels:
+ {
+ minute: NVRDataModel.getTimeFormat(),
+ hour: NVRDataModel.getTimeFormat(),
+ second: 's',
+ },
+ majorLabels:
+ {
+ second: "D MMM " + NVRDataModel.getTimeFormat(),
+ }
+ },
+
+ };
+
+ graphIndex = 1; // will be used for graph ID
+
+ //console.log ("**NOLANG" + fromDateNoLang + " " + toDateNoLang);
+
+ NVRDataModel.getEventsPages(0, fromDateNoLang, toDateNoLang)
+ .then(function(data)
+ {
+ var pages = data.pageCount || 1;
+ var itemsPerPage = parseInt(data.limit);
+ var iterCount;
+
+ // So iterCount is the # of HTTP calls I need to make
+ iterCount = Math.max(Math.round(count / itemsPerPage), 1);
+ NVRDataModel.debug("TimelineCtrl/drawGraph: pages of data: " + pages + " items per page: " + itemsPerPage);
+ NVRDataModel.debug("TimelineCtrl/drawGraph: I will make " + iterCount + " HTTP Requests to get all graph data");
+
+ // I've restructured this part. I was initially using vis DataSets
+ // for dynamic binding which was easier, but due to performance reasons
+ // I am waiting for the full data to load before I draw
+ var promises = [];
+ while ((pages > 0) && (iterCount > 0))
+ {
+ var promise = NVRDataModel.getEvents(0, pages, "none", fromDateNoLang, toDateNoLang);
+ promises.push(promise);
+ pages--;
+ iterCount--;
+
+ }
+
+ $q.all(promises)
+ .then(function(data)
+ {
+ NVRDataModel.debug("TimelineCtrl/drawgraph: all pages of graph data received");
+ graphIndex = 0;
+ NVRDataModel.log("Creating " + $scope.monitors.length + " groups for the graph");
+ // create groups
+ for (var g = 0; g < $scope.monitors.length; g++)
+ {
+ groups.add(
+ {
+ id: $scope.monitors[g].Monitor.Id,
+ //mid: $scope.monitors[g].Monitor.Id,
+ content: NVRDataModel.getMonitorName($scope.monitors[g].Monitor.Id),
+ order: $scope.monitors[g].Monitor.Sequence
+ });
+ NVRDataModel.debug("TimelineCtrl/drawgraph:Adding group " +
+ NVRDataModel.getMonitorName($scope.monitors[g].Monitor.Id));
+ }
+
+ for (var j = 0; j < data.length; j++)
+ {
+ var myevents = data[j];
+
+ if (graphIndex > count)
+ {
+ NVRDataModel.log("Exiting page count graph - reached limit of " + count);
+ break;
+
+ }
+
+ for (var i = 0; i < myevents.length; i++)
+ {
+
+ // make sure group id exists before adding
+ var idfound = true;
+ var ld = NVRDataModel.getLogin();
+
+ if (ld.persistMontageOrder)
+ {
+
+ idfound = false;
+ for (var ii = 0; ii < $scope.monitors.length; ii++)
+ {
+ if ($scope.monitors[ii].Monitor.Id == myevents[i].Event.MonitorId && NVRDataModel.isNotHidden(myevents[i].Event.MonitorId))
+ {
+ idfound = true;
+ //console.log ("****************** ID MATCH " + graphIndex);
+
+ break;
+ }
+ }
+ }
+
+ myevents[i].Event.MonitorName = NVRDataModel.getMonitorName(myevents[i].Event.MonitorId);
+ // now construct base path
+
+ myevents[i].Event.streamingURL = NVRDataModel.getStreamingURL(myevents[i].Event.MonitorId);
+ myevents[i].Event.baseURL = NVRDataModel.getBaseURL(myevents[i].Event.MonitorId);
+ myevents[i].Event.imageMode = NVRDataModel.getImageMode(myevents[i].Event.MonitorId);
+ if (NVRDataModel.getLogin().url != myevents[i].Event.baseURL)
+ {
+ //NVRDataModel.debug ("Multi server, changing base");
+ myevents[i].Event.baseURL = NVRDataModel.getLogin().url;
+
+ }
+ // console.log ("***** MULTISERVER STREAMING URL FOR EVENTS " + myevents[i].Event.streamingURL);
+
+ // console.log ("***** MULTISERVER BASE URL FOR EVENTS " + myevents[i].Event.baseURL);
+
+ if (idfound)
+ {
+
+ if (typeof myevents[i].Event.DefaultVideo === 'undefined')
+ // console.log (JSON.stringify(myevents[i]));
+ myevents[i].Event.DefaultVideo = "";
+
+ //console.log ("ADDING "+myevents[i].Event.StartTime+"->"+myevents[i].Event.EndTime);
+
+ var tzs, tze;
+ if (NVRDataModel.getLogin().useLocalTimeZone)
+ {
+ tzs = moment.tz(myevents[i].Event.StartTime, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ tze = moment.tz(myevents[i].Event.EndTime, NVRDataModel.getTimeZoneNow()).tz(NVRDataModel.getLocalTimeZoneNow());
+ }
+ else
+ {
+ tzs = moment.tz(myevents[i].Event.StartTime, NVRDataModel.getTimeZoneNow());
+ tze = moment.tz(myevents[i].Event.EndTime, NVRDataModel.getTimeZoneNow());
+ }
+
+ //console.log ("ADDED "+tzs+" " +tze);
+ graphData.add(
+ {
+ //id: graphIndex,
+ id: myevents[i].Event.Id,
+ content: "<span class='my-vis-font'>" + "( <i class='ion-android-notifications'></i>" + myevents[i].Event.AlarmFrames + ") " + "(" + myevents[j].Event.Id + ") " + myevents[i].Event.Notes + "</span>",
+
+ start: tzs,
+ //start: myevents[i].Event.StartTime,
+ //end: myevents[i].Event.EndTime,
+ end: tze,
+ group: myevents[i].Event.MonitorId,
+ //type: "range",
+ style: "background-color:" + colors[parseInt(myevents[i].Event.MonitorId) % colors.length] +
+ ";border-color:" + colors[parseInt(myevents[i].Event.MonitorId) % colors.length],
+ myframes: myevents[i].Event.Frames,
+ mydur: myevents[i].Event.Length,
+ myeid: myevents[i].Event.Id,
+ myename: myevents[i].Event.Name,
+ myvideo: myevents[i].Event.DefaultVideo,
+ myevent: myevents[i]
+
+ });
+ graphIndex++;
+ }
+ else
+ {
+ //console.log ("SKIPPED GRAPH ID " + graphIndex);
+ }
+
+ if (graphIndex > count)
+ {
+ NVRDataModel.log("Exiting event graph - reached limit of " + count);
+ break;
+
+ }
+
+ }
+ }
+
+ console.log(">>>>> CREATING NEW TIMELINE with " + JSON.stringify(options));
+ timeline = new vis.Timeline(container[0], null, options);
+ // console.log ("GRAPH DATA");
+ timeline.setItems(graphData);
+ // console.log ("GROUPS");
+ timeline.setGroups(groups);
+
+ if (NVRDataModel.getLogin().timelineScale == -1)
+
+ {
+ // console.log ("SCALE NOT FOUND");
+
+ timeline.fit();
+ }
+ else
+ {
+ timeline.fit();
+
+ /*var d = NVRDataModel.getLogin().timelineScale;
+ console.log ("SCALE FOUND "+d+" SECONDS");
+ var w = timeline.getWindow();
+ console.log ("Original s="+w.start+" e="+w.end);
+
+
+ var s = moment.tz(w.end, NVRDataModel.getTimeZoneNow()).subtract(d,'seconds').tz(moment.tz.guess());
+
+ //var s = moment(w.start).format("YYYY-MM-DD HH:mm:ss");
+ //
+ //var e = moment(w.start).add(d,'seconds').format("YYYY-MM-DD HH:mm:ss");
+
+ var e = moment.tz(w.end, NVRDataModel.getTimeZoneNow()).tz(moment.tz.guess());
+
+ console.log ("Start="+s+" End="+e);
+ $timeout (function() {timeline.setWindow(s,e);},1000);*/
+
+ }
+
+ lastTimeForEvent = moment().tz(NVRDataModel.getLogin().useLocalTimeZone ? NVRDataModel.getLocalTimeZoneNow() : NVRDataModel.getTimeZoneNow());
+ updateInterval = $interval(function()
+ {
+ processNewEvents();
+ }.bind(this), 10 * 1000);
+
+ $ionicLoading.hide();
+ $scope.graphLoaded = true;
+ NVRDataModel.debug("graph loaded: " + $scope.graphLoaded);
+ $scope.navControls = false;
+ var dblclick = false;
+
+ // we don't really need this anymore - as we have an interval timer
+ // ticking away
+
+ // this is called for each tick the bar moves
+ // speed moves depending on zoom factor
+ //
+ /* timeline.on('currentTimeTick', function() {
+
+ if ($scope.follow.time) {
+
+ }
+
+
+ });*/
+
+ timeline.on('rangechanged', function(s)
+ {
+ ///console.log ("Range Changed:"+JSON.stringify(s));
+ if (s.byUser)
+ {
+
+ var w = timeline.getWindow();
+ //console.log ("start:"+w.start+" end:"+w.end);
+ var a = moment(w.start);
+ var b = moment(w.end);
+ var d = b.diff(a, 'seconds');
+ var ld = NVRDataModel.getLogin();
+ ld.timelineScale = d;
+ NVRDataModel.setLogin(ld);
+ //console.log ("Stored user scale of "+d+" seconds");
+ }
+
+ });
+
+ timeline.on('click', function(prop)
+ {
+
+ $timeout(function()
+ {
+ if (dblclick)
+ {
+ //console.log ("IGNORING CLICK AS DBL CLICK");
+ $timeout(function()
+ {
+ dblclick = false;
+ }, 400);
+ return;
+ }
+ //console.log ("CLICK");
+ //console.log ("I GOT " + JSON.stringify(prop));
+ // console.log ("EVENT IS " + JSON.stringify(properties.event));
+ //var properties = timeline.getEventProperties(prop);
+ // console.log ( "I GOT " + properties);
+ var itm = prop.item;
+ //console.log ("ITEM CLICKED " + itm);
+ if (itm && !isNaN(itm))
+ {
+ NVRDataModel.debug("TimelineCtrl/drawGraph:You clicked on item " + itm);
+ var item = graphData.get(itm);
+ NVRDataModel.debug("TimelineCtrl/drawGraph: clicked item details:" + JSON.stringify(item));
+ showEvent(item.myevent);
+
+ }
+ else
+ {
+ NVRDataModel.debug("exact match not found, guessing item with co-ordinates X=" + prop.x + " group=" + prop.group);
+ if (prop.group)
+ {
+ var visible = timeline.getVisibleItems();
+ NVRDataModel.debug("Visible items=" + JSON.stringify(visible));
+ var closestItem = null;
+ var minDist = 99999;
+ var _item;
+ for (var x = 0; x < visible.length; x++)
+ {
+ _item = timeline.itemSet.items[x];
+ if (_item.data.group == prop.group)
+ {
+ if (Math.abs(_item.left - prop.x) < minDist)
+ {
+ closestItem = _item;
+ minDist = Math.abs(_item.left - prop.x);
+ NVRDataModel.debug("Temporary closest " + _item.left);
+ //console.log (_item);
+ }
+ }
+
+ }
+
+ if (closestItem != null)
+ {
+ NVRDataModel.log("Closest item " + closestItem.left + " group: " + closestItem.data.group);
+ showEvent(closestItem.data.myevent);
+ }
+ else
+ {
+ NVRDataModel.log("Did not find a visible item match");
+ }
+ }
+ else // no group row tapped, do nothing
+ {
+
+ /*$ionicLoading.show({
+ template: "",
+ animation: 'fade-in',
+ showBackdrop: true,
+ maxWidth: 200,
+ showDelay: 0,
+ duration: 1500,
+ });*/
+ }
+ // console.log("Zoomed out too far to playback events");
+ }
+ }, 400);
+
+ });
+
+ timeline.on('doubleClick', function(prop)
+ {
+ //console.log ("DOUBLE");
+ dblclick = true;
+ var itm = prop.item;
+ //console.log ("ITEM CLICKED " + itm);
+ if (itm && !isNaN(itm))
+ {
+ NVRDataModel.debug("TimelineCtrl/drawGraph:You clicked on item " + itm);
+ var item = graphData.get(itm);
+ NVRDataModel.debug("TimelineCtrl/drawGraph: clicked item details:" + JSON.stringify(item));
+ eventDetails(item.myevent);
+
+ }
+ else
+ {
+
+ NVRDataModel.debug("exact match not found, guessing item with co-ordinates X=" + prop.x + " group=" + prop.group);
+ if (prop.group)
+ {
+ var visible = timeline.getVisibleItems();
+ NVRDataModel.debug("Visible items=" + JSON.stringify(visible));
+ var closestItem = null;
+ var minDist = 99999;
+ var _item;
+ for (var x = 0; x < visible.length; x++)
+ {
+ _item = timeline.itemSet.items[x];
+ if (_item.data.group == prop.group)
+ {
+ if (Math.abs(_item.left - prop.x) < minDist)
+ {
+ closestItem = _item;
+ minDist = Math.abs(_item.left - prop.x);
+ NVRDataModel.debug("Temporary closest " + _item.left);
+ //console.log (_item);
+ }
+ }
+
+ }
+ NVRDataModel.log("Closest item " + closestItem.left + " group: " + closestItem.data.group);
+ if (closestItem != null)
+ {
+ NVRDataModel.log("Closest item " + closestItem.left + " group: " + closestItem.data.group);
+ showEvent(closestItem.data.myevent);
+ }
+ else
+ {
+ NVRDataModel.log("Did not find a visible item match");
+ }
+ }
+
+ // console.log("Zoomed out too far to playback events");
+ }
+
+ });
+ },
+ function(error)
+ {
+ NVRDataModel.displayBanner('error', 'Timeline error', 'Please try again');
+
+ }
+
+ ); // get Events
+ });
+ }
+
+ $scope.radialMenuOptions = {
+ content: '',
+ //size: 'small',
+
+ background: '#982112',
+ isOpen: true,
+ toggleOnClick: false,
+ button:
+ {
+ cssClass: 'fa fa-compress fa-2x',
+ size: 'small',
+ onclick: function()
+ {
+ //console.log("fitting");
+ timeline.fit();
+ }
+ },
+ items: [
+ {
+ content: '',
+ cssClass: 'fa fa-minus-circle',
+ empty: false,
+ onclick: function()
+ {
+ //zoom(0.2);
+ timeline.zoomOut(0.2);
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: true,
+ onclick: function() {
+
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+
+ onclick: function()
+ {
+
+ move(0.2);
+ }
+ },
+ {
+ content: 'D',
+ empty: true,
+
+ onclick: function()
+ {
+ // console.log('About');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: true,
+ onclick: function() {
+
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-plus-circle',
+ empty: false,
+ onclick: function()
+ {
+
+ //zoom(-0.2);
+ timeline.zoomIn(0.2);
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: true,
+ onclick: function() {
+
+ }
+ },
+
+ {
+ content: 'H',
+ empty: true,
+ onclick: function()
+ {
+ // console.log('About');
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: false,
+ onclick: function()
+ {
+ move(-0.2);
+ }
+ },
+
+ {
+ content: '',
+ cssClass: 'fa fa-chevron-circle-up',
+ empty: true,
+ onclick: function() {
+
+ }
+ },
+
+ {
+ content: 'K',
+ empty: true,
+ onclick: function()
+ {
+ //console.log('About');
+ }
+ },
+ ]
+ };
+
+}]);
diff --git a/www/js/TimelineModalCtrl.js b/www/js/TimelineModalCtrl.js
new file mode 100644
index 00000000..506c6efa
--- /dev/null
+++ b/www/js/TimelineModalCtrl.js
@@ -0,0 +1,492 @@
+// Common Controller for the montage view
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global saveAs, cordova,StatusBar,angular,console,ionic, moment, vis , Chart, DJS*/
+
+angular.module('zmApp.controllers').controller('TimelineModalCtrl', ['$scope', '$rootScope', 'zm', 'NVRDataModel', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$q', '$sce', 'carouselUtils', '$ionicPopup', '$translate', function($scope, $rootScope, zm, NVRDataModel, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $q, $sce, carouselUtils, $ionicPopup, $translate)
+{
+
+ var Graph2d;
+ var tcGraph;
+ var items;
+ var groups;
+ var eventImageDigits = 5;
+ var cv;
+ var ctx;
+ var options;
+ var data;
+ var onlyalarm_data;
+ var current_data;
+ var current_options;
+ var btype;
+
+ $scope.graphType = NVRDataModel.getLogin().timelineModalGraphType;
+ //$scope.graphType = "all";
+ $scope.errorDetails = "";
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function()
+ {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm)
+ {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("events",
+ {
+ "id": 0,
+ "playEvent": false
+ },
+ {
+ reload: true
+ });
+ return;
+ }
+ };
+
+ //-------------------------------------------------------
+ // we use this to reload the connkey if authkey changed
+ //------------------------------------------------------
+
+ $rootScope.$on("auth-success", function()
+ {
+
+ NVRDataModel.debug("EventModalCtrl: Re-login detected, resetting everything & re-generating connkey");
+
+ });
+
+ $scope.scrollUp = function()
+ {
+ //console.log ("SWIPE UP");
+ $ionicScrollDelegate.$getByHandle("timeline-modal-delegate").scrollTop(true);
+ };
+
+ $scope.scrollDown = function()
+ {
+ //console.log ("SWIPE DOWN");
+ $ionicScrollDelegate.$getByHandle("timeline-modal-delegate").scrollBottom(true);
+ };
+
+ $scope.switchType = function()
+ {
+
+ if ($scope.graphType == $translate.instant('kGraphAll'))
+ {
+ current_data = onlyalarm_data;
+ $scope.graphType = $translate.instant('kGraphAlarmed');
+ NVRDataModel.debug("Alarm array has " + onlyalarm_data.labels.length + " frames");
+ btype = 'bar';
+ //console.log (JSON.stringify(onlyalarm_data));
+
+ }
+ else
+ {
+ current_data = data;
+ // tcGraph.data =
+ $scope.graphType = $translate.instant('kGraphAll');
+ btype = 'line';
+ }
+
+ NVRDataModel.log("Switching graph type to " + $scope.graphType);
+
+ var ld = NVRDataModel.getLogin();
+ ld.timelineModalGraphType = $scope.graphType;
+ NVRDataModel.setLogin(ld);
+
+ $timeout(function()
+ {
+
+ /*
+ if ($scope.graphType == 'alarmed')
+ tcGraph.data = data;
+ else
+ tcGraph.data = onlyalarm_data;
+ tcGraph.update();*/
+ tcGraph.destroy();
+ console.log("GRAPH TYPE IS " + btype);
+ tcGraph = new Chart(ctx,
+ {
+ type: btype,
+ data: current_data,
+ options: options
+ });
+ });
+
+ };
+
+ //-------------------------------------------------------
+ // Tapping on a frame shows this image
+ //------------------------------------------------------
+
+ $scope.showImage = function(p, r, f, fid, e, imode, id)
+ {
+ var img;
+ console.log("Image Mode " + imode);
+ if (imode == 'path')
+
+ img = "<img width='100%' ng-src='" + p + "/index.php?view=image&path=" + r + f + "'>";
+ else
+ {
+ img = "<img width='100%' ng-src='" + p + "/index.php?view=image&fid=" + id + "'>";
+ // console.log ("IS MULTISERVER SO IMAGE IS " + img);
+ }
+ $rootScope.zmPopup = $ionicPopup.alert(
+ {
+ title: 'frame:' + fid + '/Event:' + e,
+ template: img,
+ cssClass: 'popup95',
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+ };
+
+ $scope.$on('modal.removed', function(e, m)
+ {
+
+ if (m.id != 'analyze')
+ return;
+ //Graph2d.destroy();
+ tcGraph.destroy();
+ // Execute action
+ });
+
+ //-------------------------------------------------------
+ // init drawing here
+ //------------------------------------------------------
+
+ $scope.$on('modal.shown', function(e, m)
+ {
+
+ if (m.id != 'analyze')
+ return;
+
+ $scope.alarm_images = [];
+ $scope.graphWidth = $rootScope.devWidth - 30;
+ NVRDataModel.log("Setting init graph width to " + $scope.graphWidth);
+ $scope.dataReady = false;
+
+ NVRDataModel.getKeyConfigParams(0)
+ .then(function(data)
+ {
+ //console.log ("***GETKEY: " + JSON.stringify(data));
+ eventImageDigits = parseInt(data);
+ NVRDataModel.log("Image padding digits reported as " + eventImageDigits);
+ });
+
+ $scope.eventdetails = $translate.instant('kLoading') + "...";
+ $scope.mName = NVRDataModel.getMonitorName($scope.event.Event.MonitorId);
+ $scope.humanizeTime = humanizeTime($scope.event.Event.StartTime);
+ processEvent();
+ //$scope.eventdetails = JSON.stringify($scope.event);
+ });
+
+ //-------------------------------------------------------
+ // okay, really init drawing here
+ //------------------------------------------------------
+
+ function processEvent()
+ {
+ var eid = $scope.event.Event.Id;
+ //eid = 22302;
+ var ld = NVRDataModel.getLogin();
+ var apiurl = ld.apiurl + "/events/" + eid + ".json";
+ NVRDataModel.log("Getting " + apiurl);
+ $http.get(apiurl)
+ .then(function(success)
+ {
+ //$scope.eventdetails = JSON.stringify(success);
+ drawGraphTC(success.data);
+ },
+ function(error)
+ {
+ $scope.errorDetails = $translate.instant('kGraphError');
+ NVRDataModel.log("Error in timeline frames " + JSON.stringify(error));
+ });
+ }
+
+ //-------------------------------------------------------
+ // I was kidding, this is where it really is drawn
+ // scout's promise
+ //------------------------------------------------------
+
+ function drawGraphTC(event)
+ {
+
+ $scope.eid = event.event.Event.Id;
+
+ $scope.alarm_images = [];
+
+ data = {
+ labels: [],
+ datasets: [
+ {
+ label: 'Score',
+ fill: true,
+ backgroundColor: 'rgba(89, 171, 227, 1.0)',
+ borderColor: 'rgba(52, 152, 219, 1.0)',
+ borderCapStyle: 'butt',
+ borderJoinStyle: 'miter',
+ pointBorderColor: "#e74c3c",
+ pointBackgroundColor: "#e74c3c",
+
+ pointHoverRadius: 10,
+ pointHoverBackgroundColor: "rgba(249, 105, 14,1.0)",
+ pointHoverBorderWidth: 1,
+ tension: 0.1,
+
+ data: [],
+ frames: []
+ },
+
+ ]
+ };
+
+ onlyalarm_data = {
+ labels: [],
+ datasets: [
+ {
+ label: 'Score',
+ backgroundColor: 'rgba(52, 152, 219, 1.0)',
+ borderColor: 'rgba(52, 152, 219, 1.0)',
+ hoverBackgroundColor: 'rgba(249, 105, 14,1.0)',
+ hoverBorderColor: 'rgba(249, 105, 14,1.0)',
+ data: [],
+ frames: []
+ },
+
+ ]
+ };
+
+ // Chart.js Options
+ options = {
+ legend: false,
+ scales:
+ {
+ yAxes: [
+ {
+ ticks:
+ {
+ // beginAtZero:true,
+ min: -1,
+ },
+ }],
+ xAxes: [
+ {
+ display: false
+ }]
+ },
+
+ responsive: true,
+ scaleBeginAtZero: true,
+ scaleShowGridLines: true,
+ scaleGridLineColor: "rgba(0,0,0,.05)",
+ scaleGridLineWidth: 1,
+
+ hover:
+ {
+ mode: 'single',
+ onHover: function(obj)
+ {
+ if (obj.length > 0)
+ tapOrHover(obj[0]._index);
+ }
+ },
+
+ //String - A legend template
+ legendTemplate: '<ul class="tc-chart-js-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'
+ };
+
+ $scope.graphWidth = event.event.Frame.length * 10;
+ if ($scope.graphWidth < $rootScope.devWidth)
+ $scope.graphWidth = $rootScope.devWidth;
+
+ // NVRDataModel.log ("Changing graph width to " + $scope.graphWidth);
+
+ for (var i = 0; i < event.event.Frame.length; i++)
+ {
+
+ data.labels.push(event.event.Frame[i].TimeStamp);
+ //data.labels.push(' ');
+ data.datasets[0].data.push(event.event.Frame[i].Score);
+ data.datasets[0].frames.push(
+ {
+ x: event.event.Frame[i].TimeStamp,
+ y: event.event.Frame[i].Score,
+ eid: event.event.Event.Id,
+ fid: event.event.Frame[i].FrameId,
+ id: event.event.Frame[i].Id,
+ //group:i,
+ relativePath: computeRelativePath(event.event),
+ score: event.event.Frame[i].Score,
+ fname: padToN(event.event.Frame[i].FrameId, eventImageDigits) + "-capture.jpg",
+
+ });
+
+ if (event.event.Frame[i].Type == "Alarm")
+ {
+
+ onlyalarm_data.labels.push(event.event.Frame[i].TimeStamp);
+ //data.labels.push(' ');
+ onlyalarm_data.datasets[0].data.push(event.event.Frame[i].Score);
+ onlyalarm_data.datasets[0].frames.push(
+ {
+ x: event.event.Frame[i].TimeStamp,
+ y: event.event.Frame[i].Score,
+ eid: event.event.Event.Id,
+ fid: event.event.Frame[i].FrameId,
+ //group:i,
+ relativePath: computeRelativePath(event.event),
+ score: event.event.Frame[i].Score,
+ fname: padToN(event.event.Frame[i].FrameId, eventImageDigits) + "-capture.jpg",
+ id: event.event.Frame[i].Id,
+
+ });
+ }
+
+ }
+
+ $scope.dataReady = true;
+
+ cv = document.getElementById("tcchart");
+ ctx = cv.getContext("2d");
+
+ if (NVRDataModel.getLogin().timelineModalGraphType == $translate.instant('kGraphAll'))
+ {
+ btype = 'line';
+ current_data = data;
+ }
+ else
+ {
+ btype = 'bar';
+ current_data = onlyalarm_data;
+ }
+ $timeout(function()
+ {
+ tcGraph = new Chart(ctx,
+ {
+ type: btype,
+ data: current_data,
+ options: options
+ });
+ });
+
+ cv.onclick = function(e)
+ {
+ var b = tcGraph.getElementAtEvent(e);
+ if (b.length > 0)
+ {
+ tapOrHover(b[0]._index);
+ }
+ };
+ }
+
+ function tapOrHover(ndx)
+ {
+
+ $timeout(function()
+ {
+
+ //console.log ("You tapped " + ndx);
+ $scope.alarm_images = [];
+ $scope.playbackURL = $scope.event.Event.baseURL;
+ var items = current_data.datasets[0].frames[ndx];
+ $scope.alarm_images.push(
+ {
+ relativePath: items.relativePath,
+ fid: items.fid,
+ id: items.id,
+ fname: items.fname,
+ score: items.score,
+ time: moment(items.x).format("MMM D," + NVRDataModel.getTimeFormatSec()),
+ eid: items.eid
+ });
+ });
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeRelativePath(event)
+ {
+ var relativePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+ relativePath = event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return relativePath;
+
+ }
+
+ //--------------------------------------------------------
+ // utility function
+ //--------------------------------------------------------
+
+ function computeBasePath(event)
+ {
+ var basePath = "";
+ var loginData = NVRDataModel.getLogin();
+ var str = event.Event.StartTime;
+ var yy = moment(str).locale('en').format('YY');
+ var mm = moment(str).locale('en').format('MM');
+ var dd = moment(str).locale('en').format('DD');
+ var hh = moment(str).locale('en').format('HH');
+ var min = moment(str).locale('en').format('mm');
+ var sec = moment(str).locale('en').format('ss');
+
+ basePath = loginData.url + "/events/" +
+ event.Event.MonitorId + "/" +
+ yy + "/" +
+ mm + "/" +
+ dd + "/" +
+ hh + "/" +
+ min + "/" +
+ sec + "/";
+ return basePath;
+ }
+
+ function humanizeTime(str)
+ {
+ return moment.tz(str, NVRDataModel.getTimeZoneNow()).fromNow();
+
+ }
+
+ function padToN(number, digits)
+ {
+
+ var i;
+ var stringMax = "";
+ var stringLeading = "";
+ for (i = 1; i <= digits; i++)
+ {
+ stringMax = stringMax + "9";
+ if (i != digits) stringLeading = stringLeading + "0";
+ }
+ var numMax = parseInt(stringMax);
+
+ if (number <= numMax)
+ {
+ number = (stringLeading + number).slice(-digits);
+ }
+ //console.log ("PADTON: returning " + number);
+ return number;
+ }
+
+}]);
diff --git a/www/js/WizardCtrl.js b/www/js/WizardCtrl.js
new file mode 100644
index 00000000..8f481d2d
--- /dev/null
+++ b/www/js/WizardCtrl.js
@@ -0,0 +1,848 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console, Masonry, URI */
+
+angular.module('zmApp.controllers').controller('zmApp.WizardCtrl', ['$scope', '$rootScope', '$ionicModal', 'NVRDataModel', '$ionicSideMenuDelegate', '$ionicHistory', '$state', '$ionicPopup', 'SecuredPopups', '$http', '$q', 'zm', '$ionicLoading', 'WizardHandler', '$translate', function($scope, $rootScope, $ionicModal, NVRDataModel, $ionicSideMenuDelegate, $ionicHistory, $state, $ionicPopup, SecuredPopups, $http, $q, zm, $ionicLoading, WizardHandler, $translate)
+{
+ $scope.openMenu = function()
+ {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ //--------------------------------------------------------------------------
+ // logs into ZM
+ //--------------------------------------------------------------------------
+
+ function login(u, zmu, zmp)
+ {
+ var d = $q.defer();
+
+ $http(
+ {
+ method: 'POST',
+ //withCredentials: true,
+ url: u,
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': 'application/json',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var params = str.join("&");
+ return params;
+ },
+
+ data:
+ {
+ username: zmu,
+ password: zmp,
+ action: "login",
+ view: "console"
+ }
+ })
+ .success(function(data, status, headers)
+ {
+ //console.log("LOOKING FOR " + zm.loginScreenString);
+ //console.log("DATA RECEIVED " + JSON.stringify(data));
+ if (data.indexOf(zm.loginScreenString) == -1)
+ {
+
+ $scope.wizard.loginURL = $scope.wizard.fqportal;
+ $scope.wizard.portalValidText = $translate.instant('kPortal') + ": " + $scope.wizard.loginURL;
+ $scope.wizard.portalColor = "#16a085";
+ d.resolve(true);
+ return d.promise;
+ }
+ else
+ {
+ //console.log("************ERROR");
+ $scope.wizard.portalValidText = $translate.instant('kPortalDetectionFailed');
+ $scope.wizard.portalColor = "#e74c3c";
+ d.reject(false);
+ return d.promise;
+ }
+ })
+ .error(function(error)
+ {
+ //console.log("************ERROR");
+ $scope.wizard.portalValidText = $translate.instant('kPortalDetectionFailed');
+ $scope.wizard.portalColor = "#e74c3c";
+ d.reject(false);
+ return d.promise;
+
+ });
+
+ return d.promise;
+
+ }
+
+ //--------------------------------------------------------------------------
+ // we need a monitor ID to do cgi-bin detection - if you don't have
+ // monitors configured, cgi-bin won't work
+ //--------------------------------------------------------------------------
+
+ function getFirstMonitor()
+ {
+ var d = $q.defer();
+ $http.get($scope.wizard.apiURL + "/monitors.json")
+ .then(function(success)
+ {
+ // console.log("getfirst monitor success: " + JSON.stringify(success));
+ if (success.data.monitors.length > 0)
+ {
+ var foundMid = -1;
+ for (var i = 0; i < success.data.monitors.length; i++)
+ {
+ if (success.data.monitors[i].Monitor.Function != 'None' &&
+ success.data.monitors[i].Monitor.Enabled == '1')
+ {
+ foundMid = success.data.monitors[i].Monitor.Id;
+ break;
+ }
+ }
+
+ if (foundMid != -1)
+ {
+ NVRDataModel.debug("zmWizard - getFirstMonitor returned " + foundMid);
+ d.resolve(foundMid);
+ return d.promise;
+ }
+ else
+ {
+ d.reject(false);
+ return d.promise;
+ }
+
+ }
+ else
+ {
+ d.reject(false);
+ return d.promise;
+ }
+ },
+ function(error)
+ {
+ //console.log("getfirst monitor error: " + JSON.stringify(error));
+ d.reject(false);
+ return d.promise;
+ });
+ return d.promise;
+ }
+
+ //--------------------------------------------------------------------------
+ // Utility function - iterates through a list of URLs
+ // Don't put loginData.reachability here --> we are using this to iterate
+ // through multiple options - not the same as fallback
+ //--------------------------------------------------------------------------
+
+ function findFirstReachableUrl(urls, tail)
+ {
+ var d = $q.defer();
+ if (urls.length > 0)
+ {
+ var t = "";
+ if (tail) t = tail;
+ //$ionicLoading.show({template: 'trying ' + urls[0].server});
+ NVRDataModel.log("zmWizard test.." + urls[0] + t);
+ return $http.get(urls[0] + t).then(function()
+ {
+ NVRDataModel.log("Success: on " + urls[0] + t);
+ //$ionicLoading.hide();
+ d.resolve(urls[0]);
+ return d.promise;
+ //return urls[0];
+ }, function(err)
+ {
+ NVRDataModel.log("zmWizard:Failed on " + urls[0] + t + " with error " + JSON.stringify(err) );
+ // this is actually a success - I might get empty status
+ // or something
+ if (err.status < 300)
+ {
+ NVRDataModel.log ("I am taking this as a cgi-bin success - "+urls[0]);
+ d.resolve(urls[0]);
+ return d.promise;
+ }
+ return findFirstReachableUrl(urls.slice(1), tail);
+ });
+ }
+ else
+ {
+ // $ionicLoading.hide();
+ NVRDataModel.log("zmWizard: findFirst returned no success");
+ d.reject("No reachable URL");
+ return d.promise;
+
+ }
+
+ return d.promise;
+
+ }
+
+ //--------------------------------------------------------------------------
+ // removes proto scheme from string
+ //--------------------------------------------------------------------------
+
+ function stripProto(u)
+ {
+ if (u.indexOf('://') != -1)
+ return u.substr(u.indexOf('://') + 3);
+ else
+ return u;
+ }
+
+ //--------------------------------------------------------------------------
+ // tries to detect cgi-bin
+ //--------------------------------------------------------------------------
+
+ function detectcgi()
+ {
+ var d = $q.defer();
+ var c = URI.parse($scope.wizard.loginURL);
+ var p1, p2;
+ p1 = "";
+ p2 = "";
+
+ if (c.userinfo)
+ p1 = c.userinfo + "@";
+ if (c.port)
+ p2 = ":" + c.port;
+
+ var baseUri = c.scheme + "://" + p1 + c.host + p2;
+
+ NVRDataModel.log("zmWizard CGI: baseURL is " + baseUri);
+
+ var a5 = baseUri + "/zmcgi"; // mageia
+ var a4 = baseUri + "/cgi-bin/zm"; // another one I found with a CentOS 6 guy
+ var a3 = baseUri + "/zm/cgi-bin"; // ubuntu/debian
+ var a2 = baseUri + "/cgi-bin-zm"; //fedora/centos/rhel
+ var a1 = baseUri + "/cgi-bin"; // doofus
+
+ var urls = [a1, a2, a3, a4, a5];
+
+ NVRDataModel.getPathZms() // what does ZM have stored in PATH_ZMS?
+ .then(function(data)
+ {
+ // remove zms or nph-zms
+ var path = data.trim();
+ path = path.replace("/nph-zms", "");
+ path = path.replace("/zms", "");
+ urls.push(baseUri.trim() + path);
+ NVRDataModel.log("zmWizard: getPathZMS succeeded, adding " + baseUri + path + " to things to try");
+ continueCgi(urls);
+ },
+ function(error)
+ {
+ NVRDataModel.log("zmWizard: getPathZMS failed, but continuing...");
+ continueCgi(urls);
+ });
+
+ // Well, PATH_ZMS or not, lets call this function and brute force it
+ function continueCgi(urls)
+ {
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDiscovering') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ getFirstMonitor()
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ var tail = "/nph-zms?mode=single&monitor=" + success;
+ if ($scope.wizard.useauth && $scope.wizard.usezmauth)
+ {
+
+ var ck = Math.floor(Math.random() * (50000 - 10000 + 1)) + 10000;
+ NVRDataModel.getAuthKey(success, ck)
+ .then(function(success)
+ {
+ if (success == "")
+ {
+ NVRDataModel.log("getAuthKey returned null, so going user=&pwd= way");
+ tail += "&user=" + $scope.wizard.zmuser + "&pass=" + $scope.wizard.zmpassword;
+ }
+ else
+ {
+ tail += success;
+ }
+ NVRDataModel.log("auth computed is : " + tail);
+ proceedwithCgiAfterAuth(urls, tail);
+ },
+ function(error)
+ {
+ NVRDataModel.log("Should never come here, getAuthKey doesn't return error");
+
+ });
+
+ //console.log ("****CDING " + tail);
+ }
+ else // no auth case
+ {
+ proceedwithCgiAfterAuth(urls, tail);
+ }
+
+ function proceedwithCgiAfterAuth(urls, tail)
+ {
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDiscovering') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+
+ findFirstReachableUrl(urls, tail)
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("Valid cgi-bin found with: " + success);
+ $scope.wizard.streamingURL = success;
+ $scope.wizard.streamingValidText = "cgi-bin: " + $scope.wizard.streamingURL;
+ $scope.wizard.streamingColor = "#16a085";
+ d.resolve(true);
+ return d.promise;
+
+ },
+ function(error)
+ {
+ $ionicLoading.hide();
+ console.log("No cgi-bin found: " + JSON.stringify(error));
+ $scope.wizard.streamingValidText = $translate.instant('kPortalCgiBinFailed');
+ $scope.wizard.streamingColor = "#e74c3c";
+ d.reject(false);
+ return (d.promise);
+ });
+ }
+ },
+ function(error)
+ {
+ $ionicLoading.hide();
+ $scope.wizard.streamingValidText = $translate.instant('kPortalCgiBinFailed') + " -" + $translate.instant('kPortalNoMonitorFound');
+ $scope.wizard.streamingColor = "#e74c3c";
+ d.reject(false);
+ return (d.promise);
+
+ });
+ }
+
+ // https://server/zm/cgi-bin/nph-zms?mode=single&monitor=1&user=admin&pass=cc
+
+ return d.promise;
+
+ }
+
+ //--------------------------------------------------------------------------
+ // Finds an appropriate API to use
+ //--------------------------------------------------------------------------
+
+ function detectapi()
+ {
+ var u = $scope.wizard.loginURL;
+ var d = $q.defer();
+ var api1 = u + "/api";
+ var api3 = u + "/zm/api";
+ var c = URI.parse(u);
+
+ // lets also try without the path
+ var api2 = c.scheme + "://";
+ if (c.userinfo) api2 += c.userinfo + "@";
+ api2 += c.host;
+ if (c.port) api2 += ":" + c.port;
+ api2 += "/api";
+
+ // lets try both /zm/api and /api. What else is there?
+ var apilist = [api1, api2, api3];
+
+ findFirstReachableUrl(apilist, '/host/getVersion.json')
+ .then(function(success)
+ {
+ NVRDataModel.log("Valid API response found with:" + success);
+ $scope.wizard.apiURL = success;
+
+ $scope.wizard.apiValidText = "API: " + $scope.wizard.apiURL;
+ $scope.wizard.apiColor = "#16a085";
+ d.resolve(true);
+ return d.promise;
+ },
+ function(error)
+ {
+ //console.log("No APIs found: " + error);
+ $scope.wizard.apiValidText = $translate.instant('kPortalAPIFailed');
+ $scope.wizard.apiColor = "#e74c3c";
+ d.reject(false);
+ return (d.promise);
+ });
+
+ return d.promise;
+ }
+
+ //--------------------------------------------------------------------------
+ // logs out of ZM
+ //--------------------------------------------------------------------------
+
+ function logout(u)
+ {
+ var d = $q.defer();
+
+ $http(
+ {
+ method: 'POST',
+ url: u,
+ headers:
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': 'application/json',
+ },
+ transformRequest: function(obj)
+ {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var params = str.join("&");
+ return params;
+ },
+
+ data:
+ {
+ action: "logout",
+ view: "login"
+ }
+ })
+ .then(function(success)
+ {
+ $rootScope.zmCookie = "";
+ //console.log("ZMlogout success, cookie removed");
+ d.resolve(true);
+ return d.promise;
+ }, function(error)
+ {
+ //console.log("ZMlogout success");
+ d.resolve(true);
+ return d.promise;
+ });
+
+ return d.promise;
+
+ }
+
+ //--------------------------------------------------------------------------
+ // clears all status updates in the verify results page - if you
+ // get back to it
+ //--------------------------------------------------------------------------
+
+ $scope.enterResults = function()
+ {
+ $scope.portalValidText = "";
+ $scope.apiValidateText = "";
+ $scope.streamingValidateText = "";
+ $scope.wizard.fqportal = "";
+ return true;
+ };
+ //--------------------------------------------------------------------------
+ // tries to log into the portal and then discover api and cgi-bin
+ //--------------------------------------------------------------------------
+
+ function validateData()
+ {
+ $rootScope.authSession = 'undefined';
+ $rootScope.zmCookie = '';
+
+ $scope.wizard.portalValidText = "";
+ $scope.wizard.apiValidText = "";
+ $scope.wizard.streamingValidText = "";
+ $scope.wizard.fqportal = "";
+ $scope.wizard.loginURL = "";
+ $scope.wizard.apiURL = "";
+ $scope.wizard.streamingURL = "";
+ $scope.wizard.serverName = "";
+
+ var d = $q.defer();
+
+ var c = URI.parse($scope.wizard.portalurl);
+
+ $scope.wizard.serverName = c.host;
+ if (c.port)
+ $scope.wizard.serverName += "-" + c.port;
+
+ var b = "";
+ if ($scope.wizard.useauth && $scope.wizard.usebasicauth)
+ {
+ b = $scope.wizard.basicuser + ":" + $scope.wizard.basicpassword + "@";
+ //console.log("B=" + b);
+ }
+ var u = c.scheme + "://" + b + c.host;
+ if (c.port) u += ":" + c.port;
+ if (c.path) u += c.path;
+
+ if (u.slice(-1) == '/')
+ {
+ u = u.slice(0, -1);
+
+ }
+
+ $scope.wizard.fqportal = u;
+
+ u = u + '/index.php';
+ NVRDataModel.log("Wizard: login url is " + u);
+
+ // now lets login
+
+ var zmu = "x";
+ var zmp = "x";
+ if ($scope.wizard.usezmauth)
+ {
+ zmu = $scope.wizard.zmuser;
+ zmp = $scope.wizard.zmpassword;
+ }
+
+ // logout first for the adventurers amongst us who must
+ // use it even after logging in
+ NVRDataModel.log("zmWizard: logging out");
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kCleaningUp') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ logout(u)
+ .then(function(ans)
+ {
+ // login now
+ $ionicLoading.hide();
+ NVRDataModel.log("zmWizard: logging in with " + u + " " + zmu);
+
+ // The logic will be:
+ // Login then do an api detect and cgi-detect together
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDiscoveringPortal') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ login(u, zmu, zmp)
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("zmWizard: login succeeded");
+
+ // API Detection
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDiscoveringAPI') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ detectapi()
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("zmWizard: API succeeded");
+
+ $ionicLoading.show(
+ {
+ template: $translate.instant('kDiscoveringCGI') + "...",
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ // CGI detection
+ detectcgi()
+ .then(function(success)
+ {
+ $ionicLoading.hide();
+ // return true here because we want to progress
+ return d.resolve(true);
+ },
+ function(error)
+ {
+ $ionicLoading.hide();
+ // return true here because we want to progress
+ return d.resolve(true);
+ });
+ },
+ function(error)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("zmWizard: api failed");
+
+ // return true here because we want to progress
+ return d.resolve(true);
+ });
+
+ },
+
+ // if login failed, don't progress in the wizard
+ function(error)
+ {
+ $ionicLoading.hide();
+ NVRDataModel.log("zmWizard: login failed");
+ $scope.wizard.portalValidText = $translate.instant('kPortalLoginUnsuccessful');
+ $scope.wizard.portalColor = "#e74c3c";
+ return d.resolve(true);
+
+ });
+
+ }); //finally
+ return d.promise;
+ }
+
+ //--------------------------------------------------------------------------
+ // checks for a protocol
+ //--------------------------------------------------------------------------
+ function checkscheme(url)
+ {
+
+ if ((!/^(f|ht)tps?:\/\//i.test(url)) && (url != ""))
+ {
+ return false;
+ }
+ else
+ return true;
+ }
+
+ //--------------------------------------------------------------------------
+ // exit validator for auth wizard
+ //--------------------------------------------------------------------------
+
+ $scope.exitAuth = function()
+ {
+ NVRDataModel.log("Wizard: validating auth syntax");
+ if ($scope.wizard.useauth)
+ {
+ if (!$scope.wizard.usezmauth && !$scope.wizard.usebasicauth)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('show',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kOneAuth'),
+ buttons: [
+ {
+ text: $translate.instant('kButtonOk')
+ }]
+
+ });
+ return false;
+ }
+ if ($scope.wizard.usezmauth)
+ {
+ if ((!$scope.wizard.zmuser) || (!$scope.wizard.zmpassword))
+ {
+ $rootScope.zmPopup = SecuredPopups.show('show',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kValidNameZMAuth'),
+ buttons: [
+ {
+ text: $translate.instant('kButtonOk')
+ }]
+
+ });
+ return false;
+ }
+ }
+
+ if ($scope.wizard.usebasicauth)
+ {
+ if ((!$scope.wizard.basicuser) || (!$scope.wizard.basicpassword))
+ {
+ $rootScope.zmPopup = SecuredPopups.show('show',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kValidNameBasicAuth'),
+ buttons: [
+ {
+ text: $translate.instant('kButtonOk')
+ }]
+
+ });
+ return false;
+ }
+ }
+ }
+ // Coming here means we can go to the next step
+ // load the step
+ WizardHandler.wizard().next();
+ // start discovery;
+ validateData();
+
+ };
+
+ //--------------------------------------------------------------------------
+ // validator for portal url wizard
+ //--------------------------------------------------------------------------
+
+ $scope.exitPortal = function()
+ {
+ NVRDataModel.log("Wizard: validating portal url syntax");
+
+ if (!$scope.wizard.portalurl)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('show',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kPortalEmpty'),
+ buttons: [
+ {
+ text: $translate.instant('kButtonOk')
+ }]
+
+ });
+ return false;
+ }
+
+ if (!checkscheme($scope.wizard.portalurl))
+ {
+
+ $scope.portalproto = [
+ {
+ text: "http",
+ value: "http://"
+ },
+ {
+ text: "https",
+ value: "https://"
+ }];
+ $scope.myproto = {
+ proto: ""
+ };
+
+ $rootScope.zmPopup = $ionicPopup.show(
+ {
+ title: $translate.instant('kPortalNoProto'),
+ scope: $scope,
+ template: $translate.instant('kPortalPleaseSelect') + ': <ion-radio-fix ng-repeat="item in portalproto" ng-value="item.value" ng-model="myproto.proto">{{item.text}}</ion-radio-fix>',
+ buttons: [
+ {
+ text: $translate.instant('kButtonOk'),
+ onTap: function(e)
+ {
+ NVRDataModel.debug("Protocol selected:" + $scope.myproto.proto);
+ $scope.wizard.portalurl = $scope.myproto.proto + stripProto($scope.wizard.portalurl);
+ }
+
+ }]
+
+ });
+ return false;
+ }
+
+ $scope.wizard.portalurl = $scope.wizard.portalurl.toLowerCase().trim();
+
+ NVRDataModel.log("Wizard: stripped url:" + $scope.wizard.portalurl);
+
+ var c = URI.parse($scope.wizard.portalurl);
+
+ if (!c.scheme)
+ {
+ $rootScope.zmPopup = SecuredPopups.show('show',
+ {
+ title: $translate.instant('kError'),
+ template: $translate.instant('kPortalInvalidUrl'),
+ buttons: [
+ {
+ text: $translate.instant('kButtonOk')
+ }]
+
+ });
+ return false;
+ }
+
+ if (c.userinfo) // basic auth stuff in here, take it out and put it into the next screen
+ {
+ $scope.wizard.useauth = true;
+ $scope.wizard.usebasicauth = true;
+ var barray = c.userinfo.split(":", 2);
+ $scope.wizard.basicuser = barray[0];
+ $scope.wizard.basicpassword = barray[1];
+ }
+
+ $scope.wizard.portalurl = c.scheme + "://";
+ if (c.host) $scope.wizard.portalurl += c.host;
+ if (c.port) $scope.wizard.portalurl += ":" + c.port;
+ if (c.path) $scope.wizard.portalurl += c.path;
+ NVRDataModel.log("Wizard: normalized url:" + $scope.wizard.portalurl);
+ return true;
+ };
+
+ //--------------------------------------------------------------------------
+ // part of auth wizard - toggles display of auth components
+ //--------------------------------------------------------------------------
+ $scope.toggleAuth = function()
+ {
+
+ if (!$scope.wizard.useauth)
+ {
+ $scope.wizard.usebasicauth = false;
+ $scope.wizard.usezmauth = false;
+ }
+ };
+
+ //--------------------------------------------------------------------------
+ // global tip toggler for all wizard steps
+ //--------------------------------------------------------------------------
+ $scope.toggleTip = function()
+ {
+ $scope.wizard.tipshow = !$scope.wizard.tipshow;
+ if ($scope.wizard.tipshow)
+ $scope.wizard.tiptext = $translate.instant('kHideTip');
+ else
+ $scope.wizard.tiptext = $translate.instant('kShowTip');
+ };
+
+ $scope.gotoLoginState = function()
+ {
+ $rootScope.wizard = angular.copy($scope.wizard);
+ $ionicHistory.nextViewOptions(
+ {
+ disableBack: true
+ });
+ $state.go("login",
+ {
+ "wizard": true
+ });
+ return;
+ };
+
+ //--------------------------------------------------------------------------
+ // initial
+ //--------------------------------------------------------------------------
+ $scope.$on('$ionicView.beforeEnter', function()
+ {
+ //console.log("**VIEW ** Help Ctrl Entered");
+
+ var monId = -1;
+ $scope.wizard = {
+ tipshow: false,
+ tiptext: $translate.instant('kShowTip'),
+ useauth: false,
+ usebasicauth: false,
+ usezmauth: false,
+ portalurl: "",
+ basicuser: "",
+ basicpassword: "",
+ zmuser: "",
+ zmpassword: "",
+ ///////////////////////
+ loginURL: "",
+ apiURL: "",
+ streamingURL: "",
+ fqportal: "",
+ portalValidText: "",
+ portalColor: "",
+ apiValidText: "",
+ apiColor: "",
+ streamingValidText: "",
+ streamingColor: "",
+ serverName: "",
+
+ };
+
+ });
+
+}]);
diff --git a/www/js/app.js b/www/js/app.js
index 09d5d48b..c859efc3 100644..100755
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1,24 +1,2157 @@
-// Ionic Starter App
-
-// angular.module is a global place for creating, registering and retrieving Angular modules
-// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
-// the 2nd parameter is an array of 'requires'
-angular.module('starter', ['ionic'])
-
-.run(function($ionicPlatform) {
- $ionicPlatform.ready(function() {
- if(window.cordova && window.cordova.plugins.Keyboard) {
- // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
- // for form inputs)
- cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
-
- // Don't remove this line unless you know what you are doing. It stops the viewport
- // from snapping when text inputs are focused. Ionic handles this internally for
- // a much nicer keyboard experience.
- cordova.plugins.Keyboard.disableScroll(true);
- }
- if(window.StatusBar) {
- StatusBar.styleDefault();
- }
- });
-})
+/* jshint -W041, -W093 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,alert,PushNotification, moment ,ionic, URI,Packery, ConnectSDK, CryptoJS, ContactFindOptions, localforage,$, Connection, MobileAccessibility, hello */
+
+// For desktop versions, this is replaced
+// with actual app version from config.xml by the
+// ./make_desktop.sh script
+
+// For mobile versions, I use cordova app version plugin
+// to get it at run time
+
+var appVersion = "0.0.0";
+
+// core app start stuff
+angular.module('zmApp', [
+ 'ionic',
+ 'ion-datetime-picker',
+ 'ngIOS9UIWebViewPatch',
+ 'zmApp.controllers',
+ 'fileLogger',
+ 'angular-carousel',
+ 'angularAwesomeSlider',
+ 'com.2fdevs.videogular',
+ 'com.2fdevs.videogular.plugins.controls',
+ 'com.2fdevs.videogular.plugins.overlayplay',
+ 'ionic-native-transitions',
+ 'mgo-angular-wizard',
+ 'pascalprecht.translate',
+ 'jett.ionic.scroll.sista',
+ 'uk.ac.soton.ecs.videogular.plugins.cuepoints',
+ 'dcbImgFallback'
+
+ ])
+
+ // ------------------------------------------
+ // Various constants central repository
+ // Feel free to change them as you see fit
+ //------------------------------------------------
+
+ .constant('zm', {
+ minAppVersion: '1.28.107', // if ZM is less than this, the app won't work
+ recommendedAppVersion: '1.29',
+ minEventServerVersion: '0.9',
+ castAppId: 'BA30FB4C',
+ alarmFlashTimer: 20000, // time to flash alarm
+ gcmSenderId: '710936220256',
+ httpTimeout: 15000,
+ largeHttpTimeout: 60000,
+ logFile: 'zmNinjaLog.txt',
+ authoremail: 'pliablepixels+zmNinja@gmail.com',
+ logFileMaxSize: 20000, // after this limit log gets reset
+ //loginInterval: 300000, //5m*60s*1000 - ZM auto login after 5 mins
+ loginInterval: 1800000, //30m*60s*1000 - ZM auto login after 30 mins
+
+ //loginInterval: 30000,
+ updateCheckInterval: 86400000, // 24 hrs
+ loadingTimeout: 15000,
+ slowLoadingTimeout: 60000,
+ safeMontageLimit: 100,
+ safeImageQuality: 10,
+ maxFPS: 30,
+ defaultFPS: 3,
+ maxMontageQuality: 70,
+ defaultMontageQuality: 50,
+ progressIntervalCheck: 5000, // used for progress indicator on event playback
+ graphFillColor: 'rgba(151,187,205,0.5)',
+ graphStrokeColor: 'rgba(151,187,205,0.8)',
+ graphHighlightFill: 'rgba(0,163,124,0.5)',
+ graphItemMax: 2000,
+ graphDesktopItemMax: 2000,
+ monitorCheckingColor: '#03A9F4',
+ monitorNotRunningColor: '#F44336',
+ monitorPendingColor: '#FF9800',
+ monitorRunningColor: '#4CAF50',
+ monitorErrorColor: '#795548',
+ montageScaleFrequency: 300,
+ eventsListDetailsHeight: 230.0,
+ eventsListScrubHeight: 330,
+ loginScreenString: "var currentView = 'login'", // Isn't there a better way?
+ desktopUrl: "/zm",
+ desktopApiUrl: "/api/zm",
+ latestRelease: "https://api.github.com/repos/pliablepixels/zmNinja/releases/latest",
+ blogUrl: "https://medium.com/zmninja/latest?format=json",
+ nphSwitchTimer: 3000,
+ eventHistoryTimer: 5000,
+ eventPlaybackQuery: 3000,
+
+ packeryTimer: 500,
+ dbName: 'zmninja',
+ cipherKey: 'sdf#@#%FSXSA_AR',
+ minCycleTime: 5,
+
+ eventPlaybackQueryLowBW: 6000,
+ loginIntervalLowBW: 1800000, //30m login
+ eventSingleImageQualityLowBW: 70,
+ monSingleImageQualityLowBW: 70,
+ montageQualityLowBW: 50,
+ eventMontageQualityLowBW: 50,
+ maxGifCount: 60,
+ maxGifCount2: 100,
+ maxGifWidth: 800.0,
+ quantSample: 15,
+ hashSecret: 'unused at the moment'
+
+ })
+
+ //http://stackoverflow.com/a/24519069/1361529
+ .filter('trusted', ['$sce', function ($sce) {
+ return function (url) {
+ return $sce.trustAsResourceUrl(url);
+ };
+ }])
+
+
+ // for events view
+ .filter('eventListFilter', function (NVRDataModel) {
+ return function (input) {
+ var ld = NVRDataModel.getLogin();
+ var out = [];
+ angular.forEach(input, function (item) {
+ if (item.Event.Archived == '0' || !ld.hideArchived) {
+ out.push(item);
+ }
+ });
+ return out;
+ };
+
+ })
+
+ // filter for montage iteration
+ .filter('onlyEnabled', function () {
+
+ // Create the return function and set the required parameter name to **input**
+ return function (input) {
+
+ var out = [];
+
+ angular.forEach(input, function (item) {
+
+ if ((item.Monitor.Function != 'None') &&
+ (item.Monitor.Enabled != '0')
+ ) {
+ out.push(item);
+ }
+
+ });
+
+ return out;
+ };
+
+ })
+
+ // filter for EH iteration
+ .filter('onlyEnabledAndEventHas', function () {
+
+ // Create the return function and set the required parameter name to **input**
+ return function (input) {
+
+ var out = [];
+
+ angular.forEach(input, function (item) {
+
+ if ((item.Monitor.Function != 'None') && (item.Monitor.Enabled != '0') && (item.Monitor.eventUrl != 'img/noevent.png') && (item.Monitor.listDisplay != 'noshow')) {
+ out.push(item);
+ }
+
+ });
+
+ return out;
+ };
+
+ })
+
+
+ //credit: http://stackoverflow.com/a/23931217/1361529
+ .directive('hidepassword', function () {
+
+ var modelSet = function (str) {
+
+ return str;
+ };
+
+ var viewSet = function (str) {
+ //https://github.com/garycourt/uri-js
+ if (!str) return str;
+ var c = URI.parse(str);
+ //if (c.userinfo) c.userinfo="***:***";
+ if (c.userinfo) c.userinfo = "\u2022\u2022\u2022:\u2022\u2022\u2022";
+
+ var ostr = "";
+ if (c.scheme) ostr = ostr + c.scheme + "://";
+ if (c.userinfo) ostr = ostr + c.userinfo + "@";
+ if (c.host) ostr = ostr + c.host;
+ if (c.port) ostr = ostr + ":" + c.port;
+ if (c.path) ostr = ostr + c.path;
+ if (c.query) ostr = ostr + c.query;
+ if (c.fragment) ostr = ostr + c.fragment;
+
+ return ostr;
+ };
+
+ return {
+
+ restrict: 'A',
+ require: 'ngModel',
+ link: function (scope, element, attr, ngModel) {
+ ngModel.$parsers.push(modelSet);
+ ngModel.$formatters.push(viewSet);
+
+ element.bind('blur', function () {
+ element.val(viewSet(ngModel.$modelValue));
+ });
+ element.bind('focus', function () {
+ element.val(ngModel.$modelValue);
+ });
+
+ }
+ };
+ })
+
+
+
+ // credit https://gist.github.com/Zren/beaafd64f395e23f4604
+
+ .directive('mouseWheelScroll', function ($timeout) {
+ return {
+ restrict: 'A',
+ link: function ($scope, $element, $attrs) {
+ var onMouseWheel, scrollCtrl;
+ scrollCtrl = $element.controller('$ionicScroll');
+ //console.log(scrollCtrl);
+ if (!scrollCtrl) {
+ return console.error('mouseWheelScroll must be attached to a $ionicScroll controller.');
+ }
+ onMouseWheel = function (e) {
+ return scrollCtrl.scrollBy(0, -e.wheelDeltaY, false);
+ };
+ return scrollCtrl.element.addEventListener('wheel', onMouseWheel);
+ }
+ };
+ })
+
+ // this can be used to route img-src through interceptors. Works well, but when
+ // nph-zms streams images it doesn't work as success is never received
+ // (keeps reading data). Hence not using it now
+ //credit: http://stackoverflow.com/questions/34958575/intercepting-img-src-via-http-interceptor-as-well-as-not-lose-the-ability-to-kee
+ .directive('httpSrc', [
+ '$http', 'imageLoadingDataShare', 'NVRDataModel',
+ function ($http, imageLoadingDataShare, NVRDataModel) {
+ var directive = {
+ link: postLink,
+ restrict: 'A'
+ };
+ return directive;
+
+ function postLink(scope, element, attrs) {
+ //console.log ("HELLO NEW");
+ var requestConfig = {
+ method: 'GET',
+ //url: attrs.httpSrc,
+ responseType: 'arraybuffer',
+ cache: 'true'
+ };
+
+ function base64Img(data) {
+ var arr = new Uint8Array(data);
+ var raw = '';
+ var i, j, subArray, chunk = 5000;
+ for (i = 0, j = arr.length; i < j; i += chunk) {
+ subArray = arr.subarray(i, i + chunk);
+ raw += String.fromCharCode.apply(null, subArray);
+ }
+ return btoa(raw);
+ }
+ attrs.$observe('httpSrc', function (newValue) {
+ requestConfig.url = newValue;
+ //console.log ("requestConfig is " + JSON.stringify(requestConfig));
+ imageLoadingDataShare.set(1);
+ $http(requestConfig)
+ .success(function (data) {
+ //console.log ("Inside HTTP after Calling " + requestConfig.url);
+ //console.log ("data got " + JSON.stringify(data));
+
+ var b64 = base64Img(data);
+ attrs.$set('src', "data:image/jpeg;base64," + b64);
+ imageLoadingDataShare.set(0);
+ });
+ });
+
+ }
+ }
+ ])
+
+ //------------------------------------------------------------------
+ // switch between collection repeat or ng-repeat
+ //-------------------------------------------------------------------
+ .directive('repeatsmart', function ($compile, $rootScope) {
+ return {
+ restrict: 'A',
+ priority: 2000,
+ terminal: true,
+ link: function (scope, element) {
+ var repeatDirective = ($rootScope.platformOS == 'desktop') ? 'ng-repeat' : 'collection-repeat';
+ //console.log("*********** REPEAT SCROLL IS " + repeatDirective);
+
+ element.attr(repeatDirective, element.attr('repeatsmart'));
+ element.removeAttr('repeatsmart');
+ $compile(element)(scope);
+ }
+ };
+ })
+
+ //------------------------------------------------------------------
+ // I use this factory to share data between carousel and lazy load
+ // carousel will not progress autoslide till imageLoading is 0 or -1
+ //-------------------------------------------------------------------
+ .factory('imageLoadingDataShare', function () {
+ var imageLoading = 0; // 0 = not loading, 1 = loading, -1 = error;
+ return {
+ 'set': function (val) {
+ imageLoading = val;
+ //console.log ("** IMAGE LOADING **"+val);
+ },
+ 'get': function () {
+
+ return imageLoading;
+ }
+ };
+ })
+
+ /*.factory('qHttp', function($q, $http) {
+ //credit: http://stackoverflow.com/a/29719693
+ var queue = $q.when();
+
+ return function queuedHttp(httpConf) {
+ var f = function(data) {
+ return $http(httpConf);
+ };
+ return queue = queue.then(f, f);
+ };
+ })*/
+
+ //credit: http://stackoverflow.com/a/14468276
+ .factory('qHttp', function ($q, $http) {
+
+ var queue = [];
+ var execNext = function () {
+ var task = queue[0];
+ //console.log ("qHTTP>>> Executing:"+JSON.stringify(task.c)+">>> pending:"+queue.length);
+
+ $http(task.c).then(function (data) {
+ queue.shift();
+ task.d.resolve(data);
+ if (queue.length > 0) execNext();
+ }, function (err) {
+ queue.shift();
+ task.d.reject(err);
+ if (queue.length > 0) execNext();
+ });
+ };
+ return function (config) {
+ var d = $q.defer();
+ //config.headers.push({'X-qHttp':'enabled'});
+ queue.push({
+ c: config,
+ d: d
+ });
+ if (queue.length === 1) {
+ execNext();
+ }
+ //else
+ //console.log ("qHTTP>>> Queuing:"+JSON.stringify(config));
+ return d.promise;
+ };
+ })
+
+ //credit: https://github.com/driftyco/ionic/issues/3131
+ .factory('SecuredPopups', [
+ '$ionicPopup',
+ '$q',
+ function ($ionicPopup, $q) {
+
+ var firstDeferred = $q.defer();
+ firstDeferred.resolve();
+
+ var lastPopupPromise = firstDeferred.promise;
+
+ // Change this var to true if you want that popups will automaticly close before opening another
+ var closeAndOpen = false;
+
+ return {
+ 'show': function (method, object) {
+ var deferred = $q.defer();
+ var closeMethod = null;
+ deferred.promise.isOpen = false;
+ deferred.promise.close = function () {
+ if (deferred.promise.isOpen && angular.isFunction(closeMethod)) {
+ closeMethod();
+ }
+ };
+
+ if (closeAndOpen && lastPopupPromise.isOpen) {
+ lastPopupPromise.close();
+ }
+
+ lastPopupPromise.then(function () {
+ deferred.promise.isOpen = true;
+ var popupInstance = $ionicPopup[method](object);
+
+ closeMethod = popupInstance.close;
+ popupInstance.then(function (res) {
+ deferred.promise.isOpen = false;
+ deferred.resolve(res);
+ });
+ });
+
+ lastPopupPromise = deferred.promise;
+
+ return deferred.promise;
+ }
+ };
+ }
+ ])
+
+ //------------------------------------------------------------------
+ // this directive will be called any time an image completes loading
+ // via img tags where this directive is added (I am using this in
+ // events and monitor view to show a loader while the image is
+ // downloading from ZM
+ //------------------------------------------------------------------
+
+ .directive('imageonload', function () {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attrs) {
+ element.bind('load', function () {
+ //call the function that was passed
+ scope.$apply(attrs.imageonload);
+ });
+ }
+
+ };
+ })
+
+ //--------------------------------------------------------------------------------------------
+ // This directive is adapted from https://github.com/paveisistemas/ionic-image-lazy-load
+ // I've removed lazyLoad and only made it show a spinner when an image is loading
+ //--------------------------------------------------------------------------------------------
+ .directive('imageSpinnerSrc', ['$document', '$compile', 'imageLoadingDataShare', '$timeout',
+ function ($document, $compile, imageLoadingDataShare, $timeout) {
+ return {
+ restrict: 'A',
+ scope: {
+ imageSpinnerBackgroundImage: "@imageSpinnerBackgroundImage"
+ },
+ link: function ($scope, $element, $attributes) {
+
+ /*if ($attributes.imageSpinnerLoader) {
+ var loader = $compile('<div class="image-loader-container"><ion-spinner class="image-loader" icon="' + $attributes.imageSpinnerLoader + '"></ion-spinner></div>')($scope);
+ $element.after(loader);
+ }*/
+
+ if ($attributes.imageSpinnerLoader) {
+ var loader = $compile('<div class="image-loader-container"><ion-spinner class="image-loader" icon="' + 'bubbles' + '"></ion-spinner></div>')($scope);
+ $element.after(loader);
+ }
+ imageLoadingDataShare.set(1);
+ loadImage();
+
+ $attributes.$observe('imageSpinnerSrc', function (value) {
+ //console.log ("DIRECTIVE SOURCE CHANGED");
+ imageLoadingDataShare.set(1);
+ loadImage();
+ //deregistration();
+
+ });
+
+ // show an image-missing image
+ $element.bind('error', function () {
+ // console.log ("DIRECTIVE: IMAGE ERROR");
+ loader.remove();
+
+ var url = 'img/novideo.png';
+ $element.prop('src', url);
+ imageLoadingDataShare.set(0);
+ });
+
+ function waitForFrame1() {
+ ionic.DomUtil.requestAnimationFrame(
+ function () {
+ imageLoadingDataShare.set(0);
+ //console.log ("IMAGE LOADED");
+ });
+
+ }
+
+ function loadImage() {
+ $element.bind("load", function (e) {
+ if ($attributes.imageSpinnerLoader) {
+ //console.log ("DIRECTIVE: IMAGE LOADED");
+ loader.remove();
+ //imageLoadingDataShare.set(0);
+ //console.log ("rendered");
+
+ // lets wait for 2 frames for animation
+ // to render - hoping this will improve tear
+ // of images
+ ionic.DomUtil.requestAnimationFrame(
+ function () {
+ waitForFrame1();
+ });
+
+ }
+ });
+
+ if ($scope.imageSpinnerBackgroundImage == "true") {
+ var bgImg = new Image();
+ bgImg.onload = function () {
+ if ($attributes.imageSpinnerLoader) {
+ loader.remove();
+ }
+ // set style attribute on element (it will load image)
+ if (imageLoadingDataShare.get() != 1)
+
+ $element[0].style.backgroundImage = 'url(' + $attributes.imageSpinnerSrc + ')';
+
+ //$element[0].style.backgroundImage = 'url(' + 'img/novideo.png'+ ')';
+
+ };
+
+ bgImg.src = $attributes.imageSpinnerSrc;
+
+ } else {
+ $element[0].src = $attributes.imageSpinnerSrc; // set src attribute on element (it will load image)
+
+ }
+ }
+
+ function isInView() {
+ return true;
+ }
+
+ $element.on('$destroy', function () {
+
+ });
+
+ }
+ };
+ }
+ ])
+
+ //------------------------------------------------------------------
+ // In Android, HTTP requests seem to get stuck once in a while
+ // It may be a crosswalk issue.
+ // To tackle this gracefully, I've set up a global interceptor
+ // If the HTTP request does not complete in 15 seconds, it cancels
+ // That way the user can try again, and won't get stuck
+ // Also remember you need to add it to .config
+ //------------------------------------------------------------------
+ .factory('timeoutHttpIntercept', ['$rootScope', '$q', 'zm', '$injector', function ($rootScope, $q, zm, $injector) {
+ $rootScope.zmCookie = "";
+ //console.log ("HHHHHHHHHHHHHH**************************");
+
+ return {
+
+ 'request': function (config) {
+ if (!config) return config;
+ if (!config.url) return config;
+
+ // NOTE ON TIMEOUTS: As of Oct 10 2016, it seems
+ // the Http queue often gets messed up when there is a timeout
+ // and the # of requests are plentiful. I'm going to disable it and see
+
+ // console.log (">>>>"+config.url);
+ // handle basic auth properly
+ if (config.url.indexOf("@") > -1) {
+ //console.log ("HTTP basic auth INTERCEPTOR URL IS " + config.url);
+ var components = URI.parse(config.url);
+ // console.log ("Parsed data is " + JSON.stringify(components));
+ var credentials = btoa(components.userinfo);
+ //var authorization = {'Authorization': 'Basic ' + credentials};
+ //config.headers.Authorization = 'Basic ' + credentials;
+
+ // console.log ("Full headers: " + JSON.stringify(config.headers));
+
+ }
+
+ //console.log (">>>>>>>>>>>>> INTERCEPT OBJECT " + JSON.stringify(config));
+
+ if ($rootScope.zmCookie) {
+ config.headers.Cookie = "ZMSESSID=" + $rootScope.zmCookie;
+ } else {
+ // console.log ("No cookie present in " + config.url);
+ }
+
+ if ($rootScope.apiAuth)
+ {
+ console.log ("********** API AUTH");
+ if (config.url.indexOf("/api/") > -1 )
+ {
+ config.url = config.url + "&auth="+$rootScope.authSession;
+ console.log ("********** API AUTH muggled to:"+config.url);
+
+ }
+ }
+
+ if ((config.url.indexOf("/api/states/change/") > -1) ||
+ (config.url.indexOf("getDiskPercent.json") > -1) ||
+ (config.url.indexOf("daemonCheck.json") > -1) ||
+ (config.url.indexOf("getLoad.json") > -1))
+
+ {
+
+ // these can take time, so lets bump up timeout
+ config.timeout = zm.largeHttpTimeout;
+
+ } else if ((config.url.indexOf("view=view_video") > -1) ||
+ config.url.indexOf(".mp4") > -1) {
+ // console.log(">>> skipping timers for MP4");
+ // put a timeout for zms urls
+ } else if (config.url.indexOf("zms?") > -1) {
+ // config.timeout = zm.httpTimeout;
+
+ }
+
+ return config;
+ },
+
+ 'response': function (response) {
+ var cookies = response.headers("Set-Cookie");
+ if (cookies != null) {
+
+ var zmSess = cookies.match("ZMSESSID=(.*?);");
+
+ if (zmSess) {
+ if (zmSess[1]) {
+
+ // console.log ("***** SETTING COOKIE TO " + zmCookie);
+ $rootScope.zmCookie = zmSess[1];
+ }
+ }
+ }
+
+ //console.log ("HTTP response");
+ return response;
+ }
+
+ };
+ }])
+
+ //-----------------------------------------------------------------
+ // This service automatically checks for new versions every 24 hrs
+ //------------------------------------------------------------------
+ .factory('zmCheckUpdates', function ($interval, $http, zm, $timeout, $localstorage, NVRDataModel, $rootScope, $translate) {
+ var zmUpdateHandle;
+ var zmUpdateVersion = "";
+
+ function start() {
+ checkUpdate();
+ $interval.cancel(zmUpdateHandle);
+ zmUpdateHandle = $interval(function () {
+ checkUpdate();
+
+ }, zm.updateCheckInterval);
+
+ function checkUpdate() {
+ var lastdateString = NVRDataModel.getLastUpdateCheck();
+ var lastdate;
+ if (!lastdateString) {
+
+ lastdate = moment().subtract(2, 'day');
+
+ } else {
+ lastdate = moment(lastdateString);
+ }
+ var timdiff = moment().diff(lastdate, 'hours');
+ if (timdiff < 24) {
+ NVRDataModel.log("Checked for update " + timdiff + " hours ago. Not checking again");
+
+ return;
+ }
+ NVRDataModel.log("Checking for new version updates...");
+
+ $http.get(zm.latestRelease)
+ .then(function (success) {
+
+ NVRDataModel.setLastUpdateCheck(moment().toISOString());
+ // $localstorage.set("lastUpdateCheck", moment().toISOString());
+ //console.log ("FULL STRING " + success.data.tag_name);
+ var res = success.data.tag_name.match("v(.*)");
+ zmUpdateVersion = res[1];
+ var currentVersion = NVRDataModel.getAppVersion();
+ if ($rootScope.platformOS == "desktop") {
+ zmUpdateVersion = zmUpdateVersion + "D";
+ }
+ //if (NVRDataModel.getAppVersion() != zmUpdateVersion) {
+ if (NVRDataModel.versionCompare(NVRDataModel.getAppVersion(), zmUpdateVersion) == -1) {
+ $rootScope.newVersionAvailable = "v" + zmUpdateVersion + " available";
+ } else {
+ $rootScope.newVersionAvailable = "";
+ }
+ NVRDataModel.debug("current version: " + currentVersion + " & available version " + zmUpdateVersion);
+ //console.log ("Version compare returned: " + NVRDataModel.versionCompare(currentVersion, //zmUpdateVersion));
+ // console.log ("Version compare returned: " + NVRDataModel.versionCompare(zmUpdateVersion, currentVersion));
+ //console.log ("UPDATE " + zmVersion);
+ });
+
+ NVRDataModel.log("Checking for news updates");
+ $http.get(zm.blogUrl, {
+ transformResponse: function (d, h) {
+ var trunc = "])}while(1);</x>";
+ d = d.substr(trunc.length);
+ return d;
+ }
+ })
+
+ .success(function (datastr) {
+
+ var data = JSON.parse(datastr);
+ $rootScope.newBlogPost = "";
+ if (data.payload.posts.length <= 0) {
+ $rootScope.newBlogPost = "";
+ return;
+ }
+
+ var lastDate = NVRDataModel.getLatestBlogPostChecked();
+ //console.log ("************ BLOG LAST DATE " + lastDate);
+ if (!lastDate) {
+
+ $rootScope.newBlogPost = "(" + $translate.instant('kNewPost') + ")";
+ NVRDataModel.setLatestBlogPostChecked(moment().format("YYYY-MM-DD HH:mm:ss"));
+ return;
+
+ }
+ var mLastDate = moment(lastDate);
+ var mItemDate = moment(data.payload.posts[0].createdAt);
+
+ if (mItemDate.diff(mLastDate, 'seconds') > 0) {
+ /*console.log ("DIFF IS "+mItemDate.diff(mLastDate, 'seconds'));
+ console.log ("DIFF mLastDate="+mLastDate);
+ console.log ("DIFF mItemDate="+mItemDate);
+ console.log ("FORMAT DIFF mLastDate="+mLastDate.format("YYYY-MM-DD HH:mm:ss") );
+ console.log ("FORMAT DIFF mItemDate="+mItemDate.format("YYYY-MM-DD HH:mm:ss") );*/
+
+ NVRDataModel.debug("New post dated " + mItemDate.format("YYYY-MM-DD HH:mm:ss") + " found, last date checked was " + mLastDate.format("YYYY-MM-DD HH:mm:ss"));
+
+ $rootScope.newBlogPost = "(" + $translate.instant('kNewPost') + ")";
+ NVRDataModel.setLatestBlogPostChecked(mItemDate.format("YYYY-MM-DD HH:mm:ss"));
+
+
+
+ } else {
+ NVRDataModel.debug("Latest post dated " + mItemDate.format("YYYY-MM-DD HH:mm:ss") + " but you read " + lastDate);
+ }
+
+ });
+
+ }
+ }
+
+ function getLatestUpdateVersion() {
+ return (zmUpdateVersion == "") ? "(unknown)" : zmUpdateVersion;
+ }
+
+ return {
+ start: start,
+ getLatestUpdateVersion: getLatestUpdateVersion
+ //stop: stop,
+
+ };
+
+ })
+
+ //-----------------------------------------------------------------
+ // This service automatically logs into ZM at periodic intervals
+ //------------------------------------------------------------------
+
+ .factory('zmAutoLogin', function ($interval, NVRDataModel, $http, zm, $browser, $timeout, $q, $rootScope, $ionicLoading, $ionicPopup, $state, $ionicContentBanner, EventServer, $ionicHistory, $translate) {
+ var zmAutoLoginHandle;
+
+ //------------------------------------------------------------------
+ // doLogin() emits this when there is an auth error in the portal
+ //------------------------------------------------------------------
+
+ $rootScope.$on("auth-error", function () {
+
+ NVRDataModel.debug("zmAutoLogin: Inside auth-error emit");
+ NVRDataModel.displayBanner('error', ['ZoneMinder authentication failed', 'Please check settings']);
+
+ });
+
+ //------------------------------------------------------------------
+ // broadcasted after :
+ // a) device is ready
+ // b) language loaded
+ // c) localforage data loaded
+ //------------------------------------------------------------------
+
+ $rootScope.$on("init-complete", function () {
+ NVRDataModel.log(">>>>>>>>>>>>>>> All init over, going to portal login");
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true
+ });
+ $state.go("zm-portal-login");
+ return;
+ });
+
+ //------------------------------------------------------------------
+ // doLogin() emits this when our auth credentials work
+ //------------------------------------------------------------------
+
+ $rootScope.$on("auth-success", function () {
+ var contentBannerInstance = $ionicContentBanner.show({
+ text: ['ZoneMinder' + $translate.instant('kAuthSuccess')],
+ interval: 2000,
+ type: 'info',
+ transition: 'vertical'
+ });
+
+ $timeout(function () {
+ contentBannerInstance();
+ }, 2000);
+ NVRDataModel.debug("auth-success emit:Successful");
+ });
+
+ $rootScope.getProfileName = function () {
+ var ld = NVRDataModel.getLogin();
+ return (ld.serverName || '(none)');
+ };
+
+ $rootScope.getLocalTimeZone = function () {
+ return moment.tz.guess();
+ };
+
+ $rootScope.getServerTimeZoneNow = function () {
+
+ return NVRDataModel.getTimeZoneNow();
+
+ };
+
+ $rootScope.isTzSupported = function () {
+ return NVRDataModel.isTzSupported();
+ };
+
+ //------------------------------------------------------------------
+ // doLogin() is the function that tries to login to ZM
+ // it also makes sure we are not back to the same page
+ // which actually means auth failed, but ZM treats it as a success
+ //------------------------------------------------------------------
+
+ function doLogin(str) {
+
+
+ var d = $q.defer();
+ var ld = NVRDataModel.getLogin();
+
+
+ /*$rootScope.authSession = 'Test';
+ $rootScope.apiAuth = true;
+ d.resolve ("Login Success");
+ $rootScope.loggedIntoZm = 1;
+ $rootScope.$emit('auth-success', 'hash API mode');
+
+ console.log(">>>>>>>>>>> DO LOGIN");
+ if (1) {return (d.promise);}*/
+
+
+
+
+ NVRDataModel.processFastLogin()
+ // coming here means login not needed, old login is valid
+ .then(function (success) {
+ d.resolve("Login Success due to fast login");
+ $rootScope.$emit('auth-success', "fast login mode");
+ return d.promise;
+ },
+
+ // coming here means login is needed
+ function (error) {
+ console.log(">>>>>>>>>>>> FAST FAILED - THIS IS OK");
+
+ var statename = $ionicHistory.currentStateName();
+
+ if (statename == "montage-history") {
+ NVRDataModel.log("Skipping login process as we are in montage history. Re-logging will mess up the stream");
+ d.resolve("success");
+ return d.promise;
+
+ }
+
+ if ($rootScope.isDownloading) {
+ NVRDataModel.log("Skipping login process as we are downloading...");
+ d.resolve("success");
+ return d.promise;
+ }
+
+ NVRDataModel.debug("Resetting zmCookie...");
+ $rootScope.zmCookie = '';
+ // first try to login, if it works, good
+ // else try to do reachability
+
+ console.log(">>>>>>>>>>>> CALLING DO LOGIN");
+ proceedWithLogin()
+ .then(function (success) {
+
+ NVRDataModel.debug("Storing login time as " + moment().toString());
+ localforage.setItem("lastLogin", moment().toString());
+ d.resolve(success);
+ return d.promise;
+ },
+ function (error)
+ // login to main failed, so try others
+ {
+ console.log(">>>>>>>>>>>> Failed first login, trying reachability");
+ NVRDataModel.getReachableConfig(true)
+ .then(function (data) {
+ proceedWithLogin()
+ .then(function (success) {
+ d.resolve(success);
+ return d.promise;
+ },
+ function (error) {
+ d.reject(error);
+ return d.promise;
+ });
+
+ },
+ function (error) {
+ d.reject(error);
+ return d.promise;
+ });
+
+ });
+
+ return d.promise;
+
+ function proceedWithLogin() {
+ // recompute rand anyway so even if you don't have auth
+ // your stream should not get frozen
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1);
+
+ // console.log ("***** STATENAME IS " + statename);
+
+ var d = $q.defer();
+ var ld = NVRDataModel.getLogin();
+ NVRDataModel.log("zmAutologin called");
+
+
+ // This is a good time to check if auth is used :-p
+ if (!ld.isUseAuth) {
+ NVRDataModel.log("Auth is disabled!");
+ d.resolve("Login Success");
+
+ $rootScope.$emit('auth-success', 'no auth');
+ return (d.promise);
+
+ }
+
+ if (str) {
+ $ionicLoading.show({
+ template: str,
+ noBackdrop: true,
+ duration: zm.httpTimeout
+ });
+ }
+
+
+
+
+
+
+ console.log(">>>>>>>>>>>>>> ISRECAPTCHA");
+
+ NVRDataModel.isReCaptcha()
+ .then(function (result) {
+ if (result == true) {
+ $ionicLoading.hide();
+ NVRDataModel.displayBanner('error', ['reCaptcha must be disabled', ], "", 8000);
+ var alertPopup = $ionicPopup.alert({
+ title: 'reCaptcha enabled',
+ template: $translate.instant('kRecaptcha'),
+ okText: $translate.instant('kButtonOk'),
+ cancelText: $translate.instant('kButtonCancel'),
+ });
+
+ // close it after 5 seconds
+ $timeout(function () {
+
+ alertPopup.close();
+ }, 5000);
+
+ d.reject("Error-disable recaptcha");
+ return (d.promise);
+ }
+
+ });
+
+ var loginData = NVRDataModel.getLogin();
+ console.log(">>>>>>>>>>>>>> PARALLEL POST WITH RECAPTCHA TO " + loginData.url);
+
+ /* console.log ("-----------------------SECRET IS "+zm.hashSecret);
+ $http.get (ld.apiurl+'/host/remoteIp.json')
+ .then (function (data) {
+ $ionicLoading.hide();
+ var ip = (data.data.auth_hash_ip) ? data.data.remote_ip: "";
+ var composite = zm.hashSecret + ld.username + ld.password + ip + data.data.time_frag;
+ var hash = CryptoJS.MD5(composite);
+ console.log ("MD5 HASH IS "+hash);
+ $rootScope.authSession = hash;
+ d.resolve ("Login Success");
+ $rootScope.loggedIntoZm = 1;
+ $rootScope.$emit('auth-success', data);
+
+
+ //ZM_AUTH_HASH_SECRET.$user['Username'].$user['Password'].$remoteAddr.$time[2].$time[3].$time[4].$time[5]
+ //$rootScope.authSession
+ // data.data.remote_ip
+ // data.data.is_auth
+
+ console.log (JSON.stringify(data));
+ },
+ function (error) {
+ $ionicLoading.hide();
+ console.log (JSON.stringify(error));
+ $rootScope.authSession = "";
+ d.reject ("Login Error");
+ $rootScope.loggedIntoZm = 1;
+ $rootScope.$emit('auth-error', "incorrect credentials");
+ }
+
+ );
+
+ return (d.promise);
+
+ console.log ("*****************NEVER HERE***********");
+ */
+ var hDelay = loginData.enableSlowLoading ? zm.largeHttpTimeout : zm.httpTimeout;
+ //NVRDataModel.debug ("*** AUTH LOGIN URL IS " + loginData.url);
+ $http({
+
+ method: 'POST',
+ timeout: hDelay,
+ //withCredentials: true,
+ url: loginData.url + '/index.php',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': 'application/json',
+ },
+ transformRequest: function (obj) {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var params = str.join("&");
+ return params;
+ },
+
+ data: {
+ username: loginData.username,
+ password: loginData.password,
+ action: "login",
+ view: "console"
+ }
+ })
+ .success(function (data, status, headers) {
+ console.log(">>>>>>>>>>>>>> PARALLEL POST SUCCESS");
+ $ionicLoading.hide();
+
+ // Coming here does not mean success
+ // it could also be a bad login, but
+ // ZM returns you to login.php and returns 200 OK
+ // so we will check if the data has
+ // <title>ZM - Login</title> -- it it does then its the login page
+
+ if (data.indexOf(zm.loginScreenString) == -1) {
+ //eventServer.start();
+ $rootScope.loggedIntoZm = 1;
+
+ NVRDataModel.log("zmAutologin successfully logged into Zoneminder");
+
+ d.resolve("Login Success");
+
+ $rootScope.$emit('auth-success', data);
+
+ } else // this means login error
+ {
+ $rootScope.loggedIntoZm = -1;
+ //console.log("**** ZM Login FAILED");
+ NVRDataModel.log("zmAutologin Error: Bad Credentials ", "error");
+ $rootScope.$emit('auth-error', "incorrect credentials");
+
+ d.reject("Login Error");
+ return (d.promise);
+ }
+
+ // Now go ahead and re-get auth key
+ // if login was a success
+ $rootScope.authSession = "undefined";
+ var ld = NVRDataModel.getLogin();
+ NVRDataModel.getAuthKey($rootScope.validMonitorId)
+ .then(function (success) {
+
+ //console.log(success);
+ $rootScope.authSession = success;
+ NVRDataModel.log("Stream authentication construction: " +
+ $rootScope.authSession);
+
+ },
+ function (error) {
+ //console.log(error);
+
+ NVRDataModel.log("Modal: Error returned Stream authentication construction. Retaining old value of: " + $rootScope.authSession);
+ NVRDataModel.debug("Error was: " + JSON.stringify(error));
+ });
+
+ return (d.promise);
+
+ })
+ .error(function (error, status) {
+
+ console.log(">>>>>>>>>>>>>> PARALLEL POST ERROR");
+ $ionicLoading.hide();
+
+ //console.log("**** ZM Login FAILED");
+
+ // FIXME: Is this sometimes results in null
+
+ NVRDataModel.log("zmAutologin Error " + JSON.stringify(error) + " and status " + status);
+ // bad urls etc come here
+ $rootScope.loggedIntoZm = -1;
+ $rootScope.$emit('auth-error', error);
+
+ d.reject("Login Error");
+ return d.promise;
+ });
+ return d.promise;
+ }
+ });
+ return d.promise;
+
+ }
+
+ function start() {
+ var ld = NVRDataModel.getLogin();
+ // lets keep this timer irrespective of auth or no auth
+ $rootScope.loggedIntoZm = 0;
+ $interval.cancel(zmAutoLoginHandle);
+ //doLogin();
+ zmAutoLoginHandle = $interval(function () {
+ doLogin("");
+
+ }, zm.loginInterval); // Auto login every 5 minutes
+ // PHP timeout is around 10 minutes
+ // should be ok?
+
+ }
+
+ function stop() {
+ var ld = NVRDataModel.getLogin();
+
+ $interval.cancel(zmAutoLoginHandle);
+ $rootScope.loggedIntoZm = 0;
+ NVRDataModel.log("Cancelling zmAutologin timer");
+
+ }
+
+ return {
+ start: start,
+ stop: stop,
+ doLogin: doLogin
+ };
+ })
+
+ //====================================================================
+ // First run in ionic
+ //====================================================================
+
+ .run(function ($ionicPlatform, $ionicPopup, $rootScope, zm, $state, $stateParams, NVRDataModel, $cordovaSplashscreen, $http, $interval, zmAutoLogin, zmCheckUpdates, $fileLogger, $timeout, $ionicHistory, $window, $ionicSideMenuDelegate, EventServer, $ionicContentBanner, $ionicLoading, $ionicNativeTransitions, $translate, $localstorage) {
+
+ $rootScope.appName = "zmNinja";
+ $rootScope.zmGlobalCookie = "";
+ $rootScope.isEventFilterOn = false;
+ $rootScope.fromDate = "";
+ $rootScope.fromTime = "";
+ $rootScope.toDate = "";
+ $rootScope.toTime = "";
+ $rootScope.fromString = "";
+ $rootScope.toString = "";
+ $rootScope.loggedIntoZm = 0;
+ $rootScope.apnsToken = '';
+ $rootScope.tappedNotification = 0;
+ $rootScope.tappedMid = 0;
+ //var eventsToDisplay=[];
+ $rootScope.alarmCount = "0";
+ $rootScope.platformOS = "desktop";
+ $rootScope.currentServerGroup = "defaultServer";
+ $rootScope.validMonitorId = "";
+ $rootScope.newVersionAvailable = "";
+ $rootScope.userCancelledAuth = false;
+ $rootScope.online = true;
+ $rootScope.showBlog = false;
+ $rootScope.newBlogPost = "";
+ $rootScope.apiVersion = "";
+
+ // only for android
+ $rootScope.exitApp = function () {
+ NVRDataModel.log("user exited app");
+
+ ionic.Platform.exitApp();
+ };
+
+ // This is a global exception interceptor
+ $rootScope.exceptionMessage = function (error) {
+ NVRDataModel.debug("**EXCEPTION**" + error.reason + " caused by " + error.cause);
+ };
+
+ window.addEventListener('beforeunload', function (ev) {
+
+ if ($rootScope.platformOS != 'desktop') {
+ ev.returnValue = "true";
+ return;
+ }
+
+ localforage.setItem('last-desktop-state', {
+ 'name': $ionicHistory.currentView().stateName,
+ 'params': $ionicHistory.currentView().stateParams
+ }).then(function () {
+ return localforage.getItem('last-desktop-state');
+ }).then(function (value) {
+ ev.returnValue = "true";
+ }).catch(function (err) {
+ ev.returnValue = "true";
+ });
+
+ });
+
+ // register callbacks for online/offline
+ // lets see if it really works
+ $rootScope.online = navigator.onLine;
+
+ $window.addEventListener("offline", function () {
+ $rootScope.$apply(function () {
+ $rootScope.online = false;
+ NVRDataModel.log("Your network went offline");
+
+ //$rootScope.$emit('network-change', "offline");
+
+ });
+ }, false);
+
+ $window.addEventListener("online", function () {
+ $rootScope.$apply(function () {
+ $rootScope.online = true;
+
+ $timeout(function () {
+ var networkState = navigator.connection.type;
+ NVRDataModel.debug("Detected network type as: " + networkState);
+ var strState = NVRDataModel.getBandwidth();
+ NVRDataModel.debug("getBandwidth() normalized it as: " + strState);
+ $rootScope.runMode = strState;
+ if ((NVRDataModel.getLogin().autoSwitchBandwidth == true) &&
+ (NVRDataModel.getLogin().enableLowBandwidth == true)) {
+ NVRDataModel.debug("Setting app state to: " + strState);
+ $rootScope.$emit('bandwidth-change', strState);
+ } else {
+ NVRDataModel.debug("Not changing bandwidth state, as auto change is not on");
+ }
+
+ }, 1000); // need a time gap, seems network type registers late
+
+ NVRDataModel.log("Your network is online, re-authenticating");
+ zmAutoLogin.doLogin($translate.instant('kReAuthenticating'));
+
+ });
+ }, false);
+
+ // This code takes care of trapping the Android back button
+ // and takes it to the menu.
+ //console.log (">>>>>>>>>>>>>>>>>>BACK BUTTON REGISTERED");
+ $ionicPlatform.registerBackButtonAction(function (e) {
+ e.preventDefault();
+ //console.log ("******** back called with isOpenLeft: " + $ionicSideMenuDelegate.isOpenLeft());
+ if (!$ionicSideMenuDelegate.isOpenLeft()) {
+ $ionicSideMenuDelegate.toggleLeft();
+ //console.log("Status of SIDE MENU IS : " + $ionicSideMenuDelegate.isOpen());
+ } else {
+ navigator.app.exitApp();
+ }
+ }, 501);
+
+ // this works reliably on both Android and iOS. The "onorientation" seems to reverse w/h in Android. Go figure.
+ // http://stackoverflow.com/questions/1649086/detect-rotation-of-android-phone-in-the-browser-with-javascript
+
+ var checkOrientation = function () {
+ var pixelRatio = window.devicePixelRatio || 1;
+ $rootScope.devWidth = ((window.innerWidth > 0) ? window.innerWidth : screen.width);
+ $rootScope.devHeight = ((window.innerHeight > 0) ? window.innerHeight : screen.height);
+ //console.log("********NEW Computed Dev Width & Height as" + $rootScope.devWidth + "*" + $rootScope.devHeight);
+
+ };
+
+ window.addEventListener("resize", checkOrientation, false);
+
+ // we come here when a user forcibly cancels portal auth
+ // useful when you know your auth won't succeed and you need to
+ // switch to another server
+ $rootScope.cancelAuth = function () {
+ $ionicLoading.hide();
+ NVRDataModel.log("User cancelled login");
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true,
+ disableBack: true
+ });
+ $rootScope.userCancelledAuth = true;
+ window.stop();
+
+ //console.log ("inside cancelAuth , calling wizard");
+ $state.go("login", {
+ "wizard": false
+ });
+ return;
+ };
+
+ //---------------------------------------------------------------------------
+ // authorize state transitions
+ //----------------------------------------------------------------------------
+
+ $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
+ var requireLogin = toState.data.requireLogin;
+
+ if ($rootScope.apiValid == false && toState.name != 'invalidapi' && toState.data.requireLogin == true) {
+ event.preventDefault();
+ $state.transitionTo('invalidapi');
+ return;
+
+ }
+
+ if (NVRDataModel.isLoggedIn() || toState.data.requireLogin == false) {
+ //console.log("State transition is authorized");
+
+ return;
+ } else {
+ NVRDataModel.log("In Auth State trans: Not logged in, requested to go to " + JSON.stringify(toState));
+ // event.preventDefault();
+ //
+
+ $state.transitionTo('login');
+
+ }
+
+ if (requireLogin) {
+
+ $ionicPopup.alert({
+ title: $translate.instant('kCredentialsTitle'),
+ template: $translate.instant('kCredentialsBody')
+ });
+ // for whatever reason, .go was resulting in digest loops.
+ // if you don't prevent, states will stack
+ event.preventDefault();
+ $state.transitionTo('login');
+ return;
+ }
+
+ return;
+
+ });
+
+ // credit http://stackoverflow.com/a/2091331/1361529
+ function getQueryVariable(query, variable) {
+ var vars = query.split('&');
+ for (var i = 0; i < vars.length; i++) {
+ var pair = vars[i].split('=');
+ if (decodeURIComponent(pair[0]) == variable) {
+ return decodeURIComponent(pair[1]);
+ }
+ }
+ return "";
+ //console.log('Query variable %s not found', variable);
+ }
+
+ //---------------------------------------------------------------------
+ // called when device is ready
+ //---------------------------------------------------------------------
+
+ function getTextZoomCallback(tz) {
+ $rootScope.textScaleFactor = parseFloat(tz + "%") / 100.0;
+ NVRDataModel.debug("text zoom factor is " + $rootScope.textScaleFactor);
+ }
+
+ $ionicPlatform.ready(function () {
+
+
+
+ // handles URL launches
+ // if you just launch zmninja:// then it will honor the settings in "tap screen" -> events or montage
+ // if you launch with zmninja://<mid> it will take you to live view for that mid
+ window.handleOpenURL = function (url) {
+ $rootScope.tappedNotification = 1;
+ $rootScope.tappedMid = 0;
+ var c = URI.parse(url);
+ //NVRDataModel.log ("***********launched with "+ JSON.stringify(c));
+ if (c.query) {
+ var qm = getQueryVariable(c.query, "mid");
+ if (qm) $rootScope.tappedMid = parseInt(qm);
+ NVRDataModel.log("external URL called with MID=" + $rootScope.tappedMid);
+ //console.log (">>>>>>>>> EID="+getQueryVariable(c.query, "eid"));
+
+ }
+
+
+
+ };
+
+ $rootScope.textScaleFactor = 1.0;
+ $rootScope.apiValid = false;
+
+ $rootScope.db = null;
+ $rootScope.runMode = NVRDataModel.getBandwidth();
+
+ $rootScope.platformOS = "desktop";
+ NVRDataModel.log("Device is ready");
+
+
+ // var ld = NVRDataModel.getLogin();
+ if ($ionicPlatform.is('ios'))
+ $rootScope.platformOS = "ios";
+ if ($ionicPlatform.is('android'))
+ $rootScope.platformOS = "android";
+
+ NVRDataModel.log("You are running on " + $rootScope.platformOS);
+
+ console.log ("Mobile acc");
+ if (window.cordova)
+ MobileAccessibility.getTextZoom(getTextZoomCallback);
+
+ // $rootScope.lastState = "events";
+ //$rootScope.lastStateParam = "0";
+
+console.log ("localforage config");
+ localforage.config({
+ name: zm.dbName
+
+ });
+
+ var order = [];
+
+ if ($rootScope.platformOS == 'ios') {
+ order = [window.cordovaSQLiteDriver._driver,
+ localforage.INDEXEDDB,
+ localforage.WEBSQL,
+ localforage.LOCALSTORAGE
+ ];
+ } else
+
+ {
+ // don't do SQL for non IOS - seems to hang?
+ order = [
+
+ localforage.INDEXEDDB,
+ localforage.WEBSQL,
+ localforage.LOCALSTORAGE,
+ ];
+
+ }
+
+console.log ("forage driver");
+ localforage.defineDriver(window.cordovaSQLiteDriver).then(function () {
+ return localforage.setDriver(
+ // Try setting cordovaSQLiteDriver if available,
+ order
+
+ );
+ }).then(function () {
+ // this should alert "cordovaSQLiteDriver" when in an emulator or a device
+ NVRDataModel.log("localforage driver for storage:" + localforage.driver());
+
+ // Now lets import old data if it exists:
+ var defaultServerName = $localstorage.get("defaultServerName");
+
+ localforage.getItem("defaultServerName")
+ .then(function (val) {
+ // console.log (">>>> localforage reported defaultServerName as " + val);
+ // if neither, we are in first use, mates!
+ if (!val && !defaultServerName) {
+ continueInitialInit();
+ /* NVRDataModel.debug ("Neither localstorage or forage - First use, showing warm and fuzzy...");
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true,
+ disableBack: true
+ });
+ $state.go('first-use');*/
+ } else if (!val && defaultServerName) {
+ NVRDataModel.log(">>>>Importing data from localstorage....");
+
+ var dsn = defaultServerName;
+ var dl = $localstorage.get('defaultLang') || 'en';
+ var ifu = ($localstorage.get('isFirstUse') == '0' ? false : true);
+ var luc = $localstorage.get('lastUpdateCheck');
+ var lbpc = $localstorage.get('latestBlogPostChecked');
+ var sgl = $localstorage.getObject('serverGroupList');
+
+ NVRDataModel.log(">>>Localstorage data found as below:");
+ NVRDataModel.log("server name:" + dsn);
+ NVRDataModel.log("default lang :" + dl);
+ NVRDataModel.log("is first use:" + ifu);
+ NVRDataModel.log("last update check:" + luc);
+ NVRDataModel.log("latest blog post check:" + lbpc);
+ NVRDataModel.log("server group list:" + JSON.stringify(sgl));
+
+ localforage.setItem('defaultLang', dl)
+ .then(function () {
+
+ NVRDataModel.log(">>>>migrated defaultLang...");
+ NVRDataModel.setFirstUse(ifu);
+ return localforage.setItem('isFirstUse', ifu);
+ })
+ .then(function () {
+ NVRDataModel.log(">>>>migrated isFirstUse...");
+ return localforage.setItem('lastUpdateCheck', ifu);
+ })
+ .then(function () {
+ NVRDataModel.log(">>>>migrated lastUpdateCheck...");
+ return localforage.setItem('latestBlogPostChecked', lbpc);
+ })
+ .then(function () {
+ NVRDataModel.log(">>>>migrated latestBlogPostChecked...");
+ // lets encrypt serverGroupList
+ NVRDataModel.log("server group list is " + JSON.stringify(sgl));
+ var ct = CryptoJS.AES.encrypt(JSON.stringify(sgl), zm.cipherKey);
+ NVRDataModel.log("encrypted server group list is " + ct);
+ ct = sgl;
+ return localforage.setItem('serverGroupList', ct);
+ })
+ .then(function () {
+ NVRDataModel.log(">>>>migrated serverGroupList...");
+ return localforage.setItem('defaultServerName', dsn);
+ })
+ .then(function () {
+ NVRDataModel.log(">>>>migrated defaultServerName...");
+ NVRDataModel.log(">>>>Migrated all values, continuing...");
+ //NVRDataModel.migrationComplete();
+ continueInitialInit();
+ })
+ .catch(function (err) {
+ NVRDataModel.log("Migration error : " + JSON.stringify(err));
+ continueInitialInit();
+ });
+
+ } else {
+ NVRDataModel.log(">>>>No data to import....");
+ //NVRDataModel.migrationComplete();
+ continueInitialInit();
+ }
+
+ });
+
+ });
+
+ function continueInitialInit() {
+ console.log ("continueinit");
+ var pixelRatio = window.devicePixelRatio || 1;
+ $rootScope.devWidth = ((window.innerWidth > 0) ? window.innerWidth : screen.width);
+ $rootScope.devHeight = ((window.innerHeight > 0) ? window.innerHeight : screen.height);
+ // for making sure we canuse $state.go with ng-click
+ // needed for views that use popovers
+ $rootScope.$state = $state;
+ $rootScope.$stateParams = $stateParams;
+
+ if (window.cordova && window.cordova.plugins.Keyboard) {
+ console.log ("no keyboard");
+ // cordova.plugins.Keyboard.disableScroll(true);
+ }
+ if (window.StatusBar) {
+ // org.apache.cordova.statusbar required
+ console.log ("statusbar");
+ NVRDataModel.log("Updating statusbar");
+ StatusBar.styleDefault();
+ //StatusBar.overlaysWebView(false);
+ StatusBar.backgroundColorByHexString("#2980b9");
+ }
+
+ if (window.cordova) {
+ console.log ("Hiding splash");
+ $cordovaSplashscreen.hide();
+
+
+
+ console.log ("app version");
+ cordova.getAppVersion.getVersionNumber().then(function (version) {
+ appVersion = version;
+ NVRDataModel.log("App Version: " + appVersion);
+ NVRDataModel.setAppVersion(appVersion);
+ });
+ }
+
+ console.log ("file logger");
+ $fileLogger.checkFile().then(function (resp) {
+ if (parseInt(resp.size) > zm.logFileMaxSize) {
+ console.log ("inside file logger");
+
+ $fileLogger.deleteLogfile().then(function () {
+ NVRDataModel.log("Deleting old log file as it exceeds " + zm.logFileMaxSize + " bytes");
+
+ });
+ }
+ });
+
+ $fileLogger.setStorageFilename(zm.logFile);
+ $fileLogger.setTimestampFormat('MMM d, y ' + NVRDataModel.getTimeFormat());
+
+ if (NVRDataModel.getLogin().disableNative) {
+ NVRDataModel.log("Disabling native transitions...");
+ $ionicNativeTransitions.enable(false);
+ } else {
+ NVRDataModel.log("Enabling native transitions...");
+ $ionicNativeTransitions.enable(true);
+ }
+ // At this stage, DataModel.init is not called yet
+ // but I do need to know the language
+
+ NVRDataModel.log("Retrieving language before init is called...");
+ localforage.getItem("defaultLang")
+ .then(function (val) {
+
+ var lang = val;
+ //console.log (">>>>>>>>>>>>>> LANG IS " + val);
+
+ if (lang == undefined || lang == null) {
+ NVRDataModel.log("No language set, switching to en");
+ lang = "en";
+
+ } else {
+ NVRDataModel.log("Language stored as:" + lang);
+
+ }
+
+ NVRDataModel.setDefaultLanguage(lang, false)
+ .then(function (success) {
+ NVRDataModel.log(">>>>Language to be used:" + $translate.proposedLanguage());
+ moment.locale($translate.proposedLanguage());
+
+ // Remember this is before data Init
+ // so I need to do a direct forage fetch
+ localforage.getItem("isFirstUse")
+ .then(function (val) {
+ //console.log ("isFirstUse is " + val);
+ if (val == null || val == true) {
+ NVRDataModel.log("First time detected");
+ $state.go("first-use");
+ return;
+ } else {
+ continueRestOfInit();
+ }
+
+ });
+
+ });
+ });
+ }
+
+ function continueRestOfInit() {
+
+ if ($rootScope.platformOS == 'desktop') {
+ $rootScope.lastState = "";
+ $rootScope.lastStateParam = {};
+
+ localforage.getItem('last-desktop-state')
+ .then(function (succ) {
+ // console.log ("FOUND " + JSON.stringify(succ) + ":"+succ);
+ if (succ) {
+ $rootScope.lastState = succ.name;
+ $rootScope.lastStateParam = succ.params;
+
+ }
+ loadServices();
+ }, function (err) {
+ console.log("ERR " + JSON.stringify(err));
+ loadServices();
+ });
+ } else
+
+ {
+
+ loadServices();
+ }
+
+ function loadServices() {
+ NVRDataModel.log("Language file loaded, continuing with rest");
+ NVRDataModel.init();
+
+ // now do SSL check
+ //setSSLCerts();
+
+ EventServer.init();
+ zmCheckUpdates.start();
+ NVRDataModel.log("Setting up POST LOGIN timer");
+ zmAutoLogin.start();
+ setupPauseAndResume();
+
+
+
+ }
+
+ }
+
+
+ function setupPauseAndResume() {
+ NVRDataModel.log("Setting up pause and resume handler AFTER language is loaded...");
+ //---------------------------------------------------------------------------
+ // resume handler
+ //----------------------------------------------------------------------------
+ document.addEventListener("resume", function () {
+ NVRDataModel.log("App is resuming from background");
+ $rootScope.isDownloading = false;
+ var forceDelay = NVRDataModel.getLogin().resumeDelay;
+ NVRDataModel.log(">>> Resume delayed for " + forceDelay + " ms, to wait for network stack...");
+
+ $timeout(function () {
+ var ld = NVRDataModel.getLogin();
+
+ NVRDataModel.setBackground(false);
+ // don't animate
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true,
+ disableBack: true
+ });
+
+ // remember the last state so we can
+ // go back there after auth
+ if ($ionicHistory.currentView()) {
+ $rootScope.lastState = $ionicHistory.currentView().stateName;
+ $rootScope.lastStateParam =
+ $ionicHistory.currentView().stateParams;
+ NVRDataModel.debug("Last State recorded:" +
+ JSON.stringify($ionicHistory.currentView()));
+
+ if ($rootScope.lastState == "zm-portal-login") {
+ NVRDataModel.debug("Last state was portal-login, so forcing montage");
+ $rootScope.lastState = "montage";
+ }
+
+ NVRDataModel.debug("going to portal login");
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true
+ });
+ $state.go("zm-portal-login");
+ return;
+ } else {
+ $rootScope.lastState = "";
+ $rootScope.lastStateParam = "";
+ NVRDataModel.debug("reset lastState to null");
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true
+ });
+ $state.go("zm-portal-login");
+ return;
+ }
+
+ }, forceDelay);
+
+ }, false);
+
+ //---------------------------------------------------------------------------
+ // background handler
+ //----------------------------------------------------------------------------
+ document.addEventListener("pause", function () {
+ NVRDataModel.setBackground(true);
+ NVRDataModel.setJustResumed(true); // used for window stop
+
+ NVRDataModel.log("ROOT APP:App is going into background");
+
+ $interval.cancel($rootScope.eventQueryInterval);
+ $interval.cancel($rootScope.intervalHandle);
+
+ NVRDataModel.log("ROOT APP: Stopping network pull...");
+ window.stop(); // dont call stopNetwork - we need to stop here
+
+ var ld = NVRDataModel.getLogin();
+
+ if (ld.exitOnSleep && $rootScope.platformOS == "android") {
+ NVRDataModel.log("user exited app");
+ ionic.Platform.exitApp();
+ }
+
+ zmAutoLogin.stop();
+ if ($rootScope.zmPopup)
+ $rootScope.zmPopup.close();
+
+ }, false);
+
+ }
+
+ // URL interceptor
+
+
+ }); //platformReady
+
+ }) //run
+
+ //------------------------------------------------------------------
+ // Route configuration
+ //------------------------------------------------------------------
+
+ // My route map connecting menu options to their respective templates and controllers
+ .config(function ($stateProvider, $urlRouterProvider, $httpProvider, $ionicConfigProvider, $provide, $compileProvider, $ionicNativeTransitionsProvider, $logProvider, $translateProvider) {
+
+ //$logProvider.debugEnabled(false);
+ //$compileProvider.debugInfoEnabled(false);
+
+ // This is an exception interceptor so it can show up in app logs
+ // if they occur. I suspect digest and other errors will be useful
+ // for me to see
+ //$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|cdvphotolibrary):/);
+
+ $provide.decorator("$exceptionHandler", ['$delegate', '$injector', function ($delegate, $injector) {
+ return function (exception, cause) {
+
+ var $rootScope = $injector.get("$rootScope");
+ $rootScope.exceptionMessage({
+ reason: exception,
+ cause: cause
+ });
+
+ $delegate(exception, cause);
+
+ };
+ }]);
+
+ // If you do this, Allow Origin can't be *
+ //$httpProvider.defaults.withCredentials = true;
+ $httpProvider.interceptors.push('timeoutHttpIntercept');
+ $ionicConfigProvider.navBar.alignTitle('center');
+ //$ionicConfigProvider.backButton.text('').icon('ion-chevron-left');
+ //$ionicConfigProvider.backButton.text('').icon('ion-chevron-left').previousTitleText(false);
+ // use overflow-scroll=false in ion-content
+ // removing it here doesn't allow you to enable it per view
+ // so it messes up scrolldelegate zoom and possibly others
+ //$ionicConfigProvider.scrolling.jsScrolling(false);
+ $compileProvider.debugInfoEnabled(false);
+
+ $ionicNativeTransitionsProvider.setDefaultOptions({
+ duration: 250,
+ });
+
+ $translateProvider.useStaticFilesLoader({
+ prefix: 'lang/locale-',
+ suffix: '.json'
+ });
+
+ //$translateProvider.useLocalStorage();
+
+ $translateProvider.registerAvailableLanguageKeys(['en', 'de', 'es', 'fr', 'it', 'ru', 'ja', 'ko', 'nl', 'pl', 'zh', 'zh_CN', 'zh_TW', 'pt', 'ar', 'hi', 'hu'], {
+ 'en_*': 'en',
+ 'de_*': 'de',
+ 'es_*': 'es',
+ 'fr_*': 'fr',
+ 'it_*': 'it',
+ 'ru_*': 'ru',
+ 'ja_*': 'ja',
+ 'ko_*': 'ko',
+ 'nl_*': 'nl',
+ 'pt_*': 'pt',
+ 'pl_*': 'pl',
+ 'ar_*': 'ar',
+ 'hi_*': 'hi',
+ 'hu_*':'hu',
+ '*': 'en' // must be last
+ });
+
+ //$translateProvider.determinePreferredLanguage();
+ //$translateProvider.preferredLanguage("en");
+ $translateProvider.fallbackLanguage("en");
+ $translateProvider.useSanitizeValueStrategy('escape');
+
+ $stateProvider
+ .state('app', {
+ url: '/',
+ abstract: true,
+ templateUrl: 'index.html',
+ cache: false,
+
+ //controller: 'AppCtrl'
+ })
+
+ .state('login', {
+ data: {
+ requireLogin: false
+ },
+ url: "/login/:wizard",
+ cache: false,
+ templateUrl: "templates/login.html",
+ controller: 'zmApp.LoginCtrl',
+
+ })
+
+ .state('help', {
+ data: {
+ requireLogin: false
+ },
+ url: "/help",
+ cache: false,
+ templateUrl: "templates/help.html",
+ controller: 'zmApp.HelpCtrl',
+
+ })
+
+ .state('news', {
+ data: {
+ requireLogin: false
+ },
+ url: "/news",
+ cache: false,
+ templateUrl: "templates/news.html",
+ controller: 'zmApp.NewsCtrl',
+
+ })
+
+ .state('monitors', {
+ data: {
+ requireLogin: true
+ },
+ resolve: {
+ message: function (NVRDataModel) {
+ // console.log("Inside app.montage resolve");
+ return NVRDataModel.getMonitors(0);
+ }
+ },
+ url: "/monitors",
+ cache: false,
+ templateUrl: "templates/monitors.html",
+ controller: 'zmApp.MonitorCtrl',
+
+ })
+
+ .state('events', {
+ data: {
+ requireLogin: true
+ },
+ resolve: {
+ message: function (NVRDataModel) {
+ //console.log("Inside app.events resolve");
+ return NVRDataModel.getMonitors(0);
+ }
+ },
+ cache: false,
+ url: "/events/:id/:playEvent",
+ templateUrl: "templates/events.html",
+ controller: 'zmApp.EventCtrl',
+
+ })
+
+ .state('lowversion', {
+ data: {
+ requireLogin: false
+ },
+
+ url: "/lowversion/:ver",
+ cache: false,
+ templateUrl: "templates/lowversion.html",
+ controller: 'zmApp.LowVersionCtrl',
+
+ })
+
+ .state('importantmessage', {
+ data: {
+ requireLogin: false
+ },
+
+ cache: false,
+ url: "/importantmessage/:ver",
+ templateUrl: "templates/important_message.html",
+ controller: 'zmApp.ImportantMessageCtrl',
+
+ })
+
+ .state('invalidapi', {
+ data: {
+ requireLogin: false
+ },
+
+ cache: false,
+ url: "/invalidapi",
+ templateUrl: "templates/invalidapi.html",
+ controller: 'zmApp.InvalidApiCtrl',
+
+ })
+
+ .state('events-graphs', {
+ data: {
+ requireLogin: true
+ },
+ cache: false,
+ url: "/events-graphs",
+ templateUrl: "templates/events-graphs.html",
+ controller: 'zmApp.EventsGraphsCtrl',
+
+ })
+
+ .state('events-date-time-filter', {
+ data: {
+ requireLogin: true
+ },
+ cache: false,
+ url: "/events-date-time-filter",
+ templateUrl: "templates/events-date-time-filter.html",
+ controller: 'zmApp.EventDateTimeFilterCtrl',
+
+ })
+
+ .state('state', {
+ data: {
+ requireLogin: true
+ },
+ cache: false,
+ url: "/state",
+ templateUrl: "templates/state.html",
+ controller: 'zmApp.StateCtrl',
+
+ })
+
+ .state('devoptions', {
+ data: {
+ requireLogin: false
+ },
+ url: "/devoptions",
+ cache: false,
+ templateUrl: "templates/devoptions.html",
+ controller: 'zmApp.DevOptionsCtrl',
+ })
+
+ .state('timeline', {
+ data: {
+ requireLogin: true
+ },
+ resolve: {
+ message: function (NVRDataModel) {
+ //console.log("Inside app.events resolve");
+ return NVRDataModel.getMonitors(0);
+ }
+ },
+ url: "/timeline",
+ cache: false,
+ templateUrl: "templates/timeline.html",
+ controller: 'zmApp.TimelineCtrl',
+
+ })
+
+ .state('eventserversettings', {
+ data: {
+ requireLogin: true
+ },
+ resolve: {
+ message: function (NVRDataModel) {
+ return NVRDataModel.getMonitors(0);
+ }
+ },
+ url: "/eventserversettings",
+ cache: false,
+ templateUrl: "templates/eventserversettings.html",
+ controller: 'zmApp.EventServerSettingsCtrl',
+
+ })
+
+ .state('log', {
+ data: {
+ requireLogin: false
+ },
+ url: "/log",
+ cache: false,
+ templateUrl: "templates/log.html",
+ controller: 'zmApp.LogCtrl',
+
+ })
+
+ .state('wizard', {
+ data: {
+ requireLogin: false
+ },
+ url: "/wizard",
+ cache: false,
+ templateUrl: "templates/wizard.html",
+ controller: 'zmApp.WizardCtrl',
+
+ })
+
+ .state('zm-portal-login', {
+ data: {
+ requireLogin: false
+ },
+ url: "/zm-portal-login",
+ cache: false,
+ templateUrl: "templates/zm-portal-login.html",
+ controller: 'zmApp.PortalLoginCtrl',
+ nativeTransitions: null // disable for speed
+
+ })
+
+ .state('first-use', {
+ data: {
+ requireLogin: false
+ },
+ url: "/first-use",
+ cache: false,
+ templateUrl: "templates/first-use.html",
+ controller: 'zmApp.FirstUseCtrl',
+
+ })
+
+ .state('montage-history', {
+ data: {
+ requireLogin: true
+ },
+ resolve: {
+ message: function (NVRDataModel) {
+ //console.log("Inside app.events resolve");
+ return NVRDataModel.getMonitors(0);
+ }
+
+ },
+ cache: false,
+ url: "/montage-history",
+ templateUrl: "templates/montage-history.html",
+ controller: 'zmApp.MontageHistoryCtrl',
+ params: {
+ minimal: false,
+ isRefresh: false
+ },
+
+ })
+
+ .state('montage', {
+ data: {
+ requireLogin: true
+ },
+ resolve: {
+ message: function (NVRDataModel) {
+ //console.log("Inside app.events resolve");
+ return NVRDataModel.getMonitors(0);
+ }
+
+ },
+ url: "/montage",
+ cache: false,
+ templateUrl: "templates/montage.html",
+ controller: 'zmApp.MontageCtrl',
+ params: {
+ minimal: false,
+ isRefresh: false
+ },
+
+ });
+
+ // We are NOT going to default route. Routing to a view will start on
+ // a broadcast of "init-complete"
+
+ }); //config \ No newline at end of file
diff --git a/www/js/controllers.js b/www/js/controllers.js
new file mode 100644
index 00000000..5321384b
--- /dev/null
+++ b/www/js/controllers.js
@@ -0,0 +1,29 @@
+/* jshint -W041 */
+/* jshint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+
+
+angular.module('zmApp.controllers', ['ionic', 'ionic.utils', 'ngCordova', 'ng-mfb', 'angularCircularNavigation', 'jett.ionic.content.banner', 'ionic-pullup', 'ngWebsocket'])
+
+.controller('zmApp.BaseController', function ($scope, $ionicSideMenuDelegate, $ionicPlatform, $timeout, $rootScope) {
+ $scope.openMenu = function () {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $ionicPlatform.registerBackButtonAction(function (event) {
+
+ $ionicSideMenuDelegate.toggleLeft();
+ $timeout(function () {
+ $rootScope.stateofSlide = $ionicSideMenuDelegate.isOpen() + new Date();
+ }, 500);
+
+
+ }, 100);
+
+ // Added for electron build to stop title propagation
+ $scope.$on('$ionicView.afterEnter', function (ev, data) {
+ ev.stopPropagation();
+ });
+
+}); \ No newline at end of file
diff --git a/www/js/ionicUtils.js b/www/js/ionicUtils.js
new file mode 100644
index 00000000..c593624c
--- /dev/null
+++ b/www/js/ionicUtils.js
@@ -0,0 +1,32 @@
+/* jshint -W041 */
+/* jshint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+//http://learn.ionicframework.com/formulas/localstorage/
+
+angular.module('ionic.utils', [])
+
+.factory('$localstorage', ['$window', function($window)
+{
+ return {
+
+ init: function() {},
+
+ set: function(key, value)
+ {
+ $window.localStorage[key] = value;
+ },
+ get: function(key, defaultValue)
+ {
+ return $window.localStorage[key] || defaultValue;
+ },
+ setObject: function(key, value)
+ {
+ $window.localStorage[key] = JSON.stringify(value);
+ },
+ getObject: function(key)
+ {
+ return JSON.parse($window.localStorage[key] || '{}');
+ }
+ };
+}]);