/* jshint -W041 */
/* jslint browser: true*/
/* global cordova,StatusBar,angular,console,alert,URI */
angular.module('zmApp.controllers').controller('zmApp.LoginCtrl', ['$scope', '$rootScope', 'zm', '$ionicModal', 'ZMDataModel', '$ionicSideMenuDelegate', '$ionicPopup', '$http', '$q', '$ionicLoading', 'zmAutoLogin', '$cordovaPinDialog', 'EventServer', '$ionicHistory', '$state', '$ionicActionSheet', 'SecuredPopups', '$localstorage', function ($scope, $rootScope, zm, $ionicModal, ZMDataModel, $ionicSideMenuDelegate, $ionicPopup, $http, $q, $ionicLoading, zmAutoLogin, $cordovaPinDialog, EventServer, $ionicHistory, $state, $ionicActionSheet, SecuredPopups, $localstorage) {
$scope.openMenu = function () {
$ionicSideMenuDelegate.toggleLeft();
};
var oldName;
var serverbuttons = [];
var availableServers;
$scope.loginData = ZMDataModel.getLogin();
$scope.check = {
isUseAuth: "",
isUseEventServer: ""
};
$scope.check.isUseAuth = ($scope.loginData.isUseAuth == '1') ? true : false;
$scope.check.isUseEventServer = ($scope.loginData.isUseEventServer == true) ? true : false;
//----------------------------------------------------------------
// 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
}, {
reload: true
});
}
};
//----------------------------------------------------------------
// Specifies a linked profile to try if this profile fails
//----------------------------------------------------------------
$scope.selectFallback = function ()
{
var as = Object.keys(ZMDataModel.getServerGroups());
if (as.length < 2)
{
$rootScope.zmPopup= SecuredPopups.show('alert',{
title: 'Error',
template: 'You need to have at least 2 distinct configurations created for a fallback'
});
return;
}
var ab = [{text:'Clear'}];
var ld = ZMDataModel.getLogin();
as.forEach(function(item) { if (item != ld.serverName) ab.push({text:item});});
var sheet = $ionicActionSheet.show({
buttons: ab,
titleText: 'Select fallback',
cancelText: 'Cancel',
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;
ZMDataModel.setLogin($scope.loginData);
return true;
}
});
};
//----------------------------------------------------------------
// This is called when the user changes profiles
//----------------------------------------------------------------
$scope.serverActionSheet = function () {
var hideSheet = $ionicActionSheet.show({
buttons: serverbuttons,
destructiveText: 'Delete',
titleText: 'Manage Server Groups',
cancelText: 'Cancel',
cancel: function () {
// add cancel code..
},
buttonClicked: function (index) {
//console.log ("YOU WANT " + serverbuttons[index].text + " INDEX " + index);
if (serverbuttons[index].text == 'Add...')
{
$scope.loginData = angular.copy(ZMDataModel.getDefaultLoginObject());
return true;
}
var zmServers = ZMDataModel.getServerGroups();
$scope.loginData = zmServers[serverbuttons[index].text];
console.log ("NEW LOOGIN OBJECT IS " + JSON.stringify($scope.loginData));
$scope.check.isUseAuth = ($scope.loginData.isUseAuth == '1') ? true : false;
$scope.check.isUseEventServer = ($scope.loginData.isUseEventServer == true) ? true : false;
ZMDataModel.zmDebug("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
ZMDataModel.setLogin($scope.loginData);
return true;
},
destructiveButtonClicked: function () {
if (!$scope.loginData.serverName)
{
ZMDataModel.zmDebug("cannot delete empty entry");
return true;
}
var zmServers = ZMDataModel.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) {
ZMDataModel.zmLog("Deleting " + $scope.loginData.serverName);
delete zmServers[$scope.loginData.serverName];
ZMDataModel.setServerGroups(zmServers);
// point to first element
// better than nothing
// note this is actually unordered
$scope.loginData = zmServers[Object.keys(zmServers)[0]];
ZMDataModel.setLogin($scope.loginData);
availableServers = Object.keys(ZMDataModel.getServerGroups());
serverbuttons = [{text:'Add...'}];
for (var servIter = 0; servIter < availableServers.length; servIter++) {
serverbuttons.push({
text: availableServers[servIter]
});
//console.log("ADDING : " + availableServers[servIter]);
}
} else {
ZMDataModel.displayBanner('error', ['Cannot delete, need at least one']);
}
return true;
}
});
};
//----------------------------------------------------------------
// This is when you tap on event server settings
//----------------------------------------------------------------
$scope.eventServerSettings = function () {
ZMDataModel.zmDebug("Saving settings before going to Event Server settings");
//console.log ( "My loginData saved " + JSON.stringify($scope.loginData));
ZMDataModel.setLogin($scope.loginData);
$state.go("eventserversettings");
};
//-------------------------------------------------------------------------
// 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");
ZMDataModel.setAwake(false);
var ld = ZMDataModel.getLogin();
oldName = ld.serverName;
availableServers = Object.keys(ZMDataModel.getServerGroups());
serverbuttons = [{text:"Add..."}];
for (var servIter = 0; servIter < availableServers.length; servIter++) {
serverbuttons.push({
text: availableServers[servIter]
});
}
});
$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
$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams)
{
ZMDataModel.setAwake(false);
var ld = ZMDataModel.getLogin();
if(ld.serverName != oldName )
{
event.preventDefault();
$rootScope.zmPopup = SecuredPopups.show('alert',{
title: 'Please Save',
template: 'You have changed from ' + oldName + ' to ' + ld.serverName + '. Please save this profile first.'
});
}
});
$rootScope.$on('$stateChangeSuccess', function(){
$scope.ignoreDirty = false;
});
// Make a noble attempt at deciphering
$scope.detectCgi = function()
{
var text = "Typical values: ubuntu: http://server/zm/cgi-bin centos/fedora: http://server/zm/cgi-bin-zm/";
if ($scope.loginData.url.slice(-1) == '/') {
$scope.loginData.url = $scope.loginData.url.slice(0, -1);
}
ZMDataModel.getPathZms()
.then (function (data)
{
var c=URI.parse($scope.loginData.url);
var p1,p2,p3;
p1 ="";
p2 ="";
if (c.userinfo)
p1 = c.userinfo+"@";
if (c.port)
p2 = ":"+c.port;
var baseUri = c.scheme+"://"+p1+c.host+p2;
var dtext = baseUri + data.toLowerCase().trim();
dtext = dtext.substr(0, dtext.lastIndexOf("/"));
text = "Your cgi-bin path may be " + dtext + "
" + text;
$rootScope.zmPopup = SecuredPopups.show('show',{
title: 'cgi-bin settings',
cssClass: 'popup90',
template: text,
buttons: [{text: 'Ok'},{text:'Use suggestion', onTap:function(e) {$scope.loginData.streamingurl=dtext;}}]
});
},
function (error)
{
ZMDataModel.zmDebug ("Could not get PATH_ZMS:"+JSON.stringify(error));
text = "(could not detect cgi-path, try coming back here after setting up your portal url and credentials correctly)
"+text;
$rootScope.zmPopup = SecuredPopups.show('alert',{
title: 'cgi-bin settings',
cssClass: 'popup90',
template: text
});
});
};
//--------------------------------------------------------------------------
// 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) {
ZMDataModel.zmLog("Password prompt");
if ($scope.loginData.usePin) {
$scope.loginData.pinCode = "";
$cordovaPinDialog.prompt('Enter PIN', 'PIN Protect').then(
function (result1) {
// console.log (JSON.stringify(result1));
if (result1.input1 && result1.buttonIndex == 1) {
$cordovaPinDialog.prompt('Reconfirm PIN', 'PIN Protect')
.then(function (result2) {
if (result1.input1 == result2.input1) {
ZMDataModel.zmLog("Pin code match");
$scope.loginData.pinCode = result1.input1;
} else {
ZMDataModel.zmLog("Pin code mismatch");
$scope.loginData.usePin = false;
ZMDataModel.displayBanner('error', ['Pin code mismatch']);
}
},
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 {
ZMDataModel.zmDebug("Password disabled");
}
};
//-------------------------------------------------------------------------------
// Makes input easier
//-------------------------------------------------------------------------------
$scope.portalKeypress = function (evt) {
if (/^https:\/\//i.test($scope.loginData.url)) {
$scope.loginData.useSSL = true;
}
$scope.loginData.streamingurl = $scope.loginData.url;
$scope.loginData.apiurl = $scope.loginData.url + "/api";
};
//-------------------------------------------------------------------------------
// 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');
ZMDataModel.setFirstUse(false);
// 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) ? "1" : "0";
$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";
//ZMDataModel.zmLog("Authentication is disabled, setting dummy user & pass");
}
if (parseInt($scope.loginData.maxMontage) <= 0) {
$scope.loginData.maxMontage = "10";
}
// do this before setLogin so message is sent
if (!$scope.check.isUseEventServer) {
$rootScope.isAlarm = 0;
if ($rootScope.apnsToken) {
ZMDataModel.zmLog("Making sure we don't get push notifications");
EventServer.sendMessage('push', {
type: 'token',
platform: $rootScope.platformOS,
token: $rootScope.apnsToken,
state: "disabled"
});
}
}
ZMDataModel.setLogin($scope.loginData);
oldName = $scope.loginData.serverName;
if ($scope.check.isUseEventServer) {
EventServer.init();
if ($rootScope.apnsToken && $scope.loginData.disablePush != true) {
ZMDataModel.zmLog("Making sure we get push notifications");
EventServer.sendMessage('push', {
type: 'token',
platform: $rootScope.platformOS,
token: $rootScope.apnsToken,
state: "enabled"
});
}
EventServer.sendMessage("control", {
type: 'filter',
monlist: $scope.loginData.eventServerMonitors,
intlist: $scope.loginData.eventServerInterval
});
}
// lets logout
ZMDataModel.zmDebug ("Logging out of current session...");
$rootScope.authSession = "undefined";
$http({
method: 'POST',
//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("")
// 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 != ZMDataModel.getLogin().serverName)
{
ZMDataModel.zmDebug (">>> Server information has changed, likely a fallback took over!");
$scope.loginData = ZMDataModel.getLogin();
apiurl = $scope.loginData.apiurl + '/host/getVersion.json';
portalurl = $scope.loginData.url + '/index.php';
}
ZMDataModel.zmLog("Validating APIs at " + apiurl);
$http.get(apiurl)
.success(function (data) {
var loginStatus = "Please explore the menu and enjoy zmNinja!";
EventServer.refresh();
// now grab and report PATH_ZMS
ZMDataModel.getPathZms()
.then(function (data) {
var ld = ZMDataModel.getLogin();
var zm_cgi = data.toLowerCase();
var user_cgi = (ld.streamingurl).toLowerCase();
ZMDataModel.zmLog("ZM relative cgi-path: " + zm_cgi + ", you entered: " + user_cgi);
$http.get(ld.streamingurl + "/zms")
.success(function (data) {
ZMDataModel.zmDebug("Urk! cgi-path returned success, but it should not have come here");
loginStatus = "Login validated, but could not validate cgi-path. If live streams don't work please check your cgi-bin path";
var refresh = ZMDataModel.getMonitors(1);
if (showalert)
{
$rootScope.zmPopup = SecuredPopups.show('alert',{
title: 'Login validated',
template: loginStatus
}).then(function (res) {
$ionicSideMenuDelegate.toggleLeft();
ZMDataModel.zmDebug("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 = "The cgi-bin path you entered may be wrong. I can't make sure, but if your live views don't work, please review your cgi path.";
}
if (showalert)
{
$rootScope.zmPopup = SecuredPopups.show('alert',{
title: 'Login validated',
template: loginStatus
}).then(function (res) {
$ionicSideMenuDelegate.toggleLeft();
ZMDataModel.zmDebug("Force reloading monitors...");
});
}
var refresh = ZMDataModel.getMonitors(1);
});
});
})
.error(function (error) {
ZMDataModel.displayBanner('error', ['ZoneMinder API check failed', 'Please check API settings']);
ZMDataModel.zmLog("API login error " + JSON.stringify(error));
$rootScope.zmPopup= SecuredPopups.show('alert',{
title: 'Login validated but API failed',
template: 'Please check your API settings'
});
});
});
});
}
// ----------------------------------------------
// Saves the current profile. Note that
// calling saveItems also updates the defaultServer
//-----------------------------------------------
$scope.saveItems = function () {
if (!$scope.loginData.serverName) {
$rootScope.zmPopup = $ionicPopup.alert({
title: 'Error',
template: 'Server Name cannot be empty',
})
.then(function (res) {
return;
});
} else {
saveItems(true);
availableServers = Object.keys(ZMDataModel.getServerGroups());
serverbuttons = [];
for (var servIter = 0; servIter < availableServers.length; servIter++) {
serverbuttons.push({
text: availableServers[servIter]
});
}
}
};
}]);