summaryrefslogtreecommitdiff
path: root/www
diff options
context:
space:
mode:
Diffstat (limited to 'www')
-rw-r--r--www/index.html14
-rw-r--r--www/js/DataModel.js42
-rw-r--r--www/js/EventCtrl.js3
-rw-r--r--www/js/EventsGraphsCtrl.js2
-rw-r--r--www/js/HelpCtrl.js3
-rw-r--r--www/js/LogCtrl.js61
-rw-r--r--www/js/LoginCtrl.js2
-rw-r--r--www/js/ModalCtrl.js1
-rw-r--r--www/js/MonitorCtrl.js3
-rw-r--r--www/js/MontageCtrl.js2
-rw-r--r--www/js/StateCtrl.js4
-rw-r--r--www/js/app.js22
-rw-r--r--www/lib/filelogger/.bower.json40
-rw-r--r--www/lib/filelogger/LICENSE21
-rw-r--r--www/lib/filelogger/README.md123
-rw-r--r--www/lib/filelogger/bower.json30
-rw-r--r--www/lib/filelogger/dist/filelogger.js274
-rw-r--r--www/lib/filelogger/dist/filelogger.min.js6
-rw-r--r--www/lib/filelogger/package.json26
-rw-r--r--www/templates/log.html16
20 files changed, 688 insertions, 7 deletions
diff --git a/www/index.html b/www/index.html
index 65f0e1cd..374862fd 100644
--- a/www/index.html
+++ b/www/index.html
@@ -27,15 +27,16 @@
error interceptor in app.js -->
<script src="lib/ionic/js/ionic.bundle.min.js"></script>
+ <script src="lib/ngCordova/dist/ng-cordova.min.js"></script>
+ <script src="lib/filelogger/dist/filelogger.min.js"></script>
<!-- cordova script (this will be a 404 during development) -->
<script src="cordova.js"></script>
- <script src="lib/ngCordova/dist/ng-cordova.min.js"></script>
+
<!-- your app's js -->
- <script src="js/templates.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
@@ -49,6 +50,7 @@
<script src="js/HelpCtrl.js"></script>
<script src="js/StateCtrl.js"></script>
<script src="js/DevOptionsCtrl.js"></script>
+ <script src="js/LogCtrl.js"></script>
<script src="lib/angular-circular-navigation/angular-circular-navigation.js"></script>
@@ -134,6 +136,14 @@
<i class="icon ion-help"></i>
</span> Help
</ion-item>
+
+ <ion-item nav-clear menu-close href="#/log">
+ <span class=" item-icon-left">
+ <i class="icon ion-clipboard"></i>
+ </span> Logs
+ </ion-item>
+
+
</ion-list>
</ion-content>
</ion-side-menu>
diff --git a/www/js/DataModel.js b/www/js/DataModel.js
index d1470685..bfd81b74 100644
--- a/www/js/DataModel.js
+++ b/www/js/DataModel.js
@@ -7,7 +7,7 @@
// 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('ZMDataModel', ['$http', '$q', '$ionicLoading', '$ionicBackdrop', function ($http, $q, $ionicLoading, $ionicBackdrop) {
+angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ionicLoading', '$ionicBackdrop', '$fileLogger', function ($http, $q, $ionicLoading, $ionicBackdrop,$fileLogger) {
var monitorsLoaded = 0;
var montageSize = 3;
@@ -26,8 +26,28 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
'keepAwake':true // don't dim/dim during live view
};
+ //--------------------------------------------------------------------------
+ // uses fileLogger to write logs to file for later investigation
+ //--------------------------------------------------------------------------
+ function zmLog(val,logtype)
+ {
+ $fileLogger.log(logtype, val);
+ }
+
return {
+ //-------------------------------------------------------------
+ // used by various controllers to log messages to file
+ //-------------------------------------------------------------
+
+ zmLog: function (val,type) {
+ var logtype = 'info';
+ if (type != undefined)
+ logtype = type ;
+ zmLog(val,logtype);
+
+ },
+
// 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
@@ -36,10 +56,17 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
// 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
-
init: function () {
console.log("****** DATAMODEL INIT SERVICE CALLED ********");
+ zmLog("ZMData init: checking for stored variables & setting up log file");
+
+ $fileLogger.setStorageFilename('zmNinjaLog.txt');
+
+ $fileLogger.deleteLogfile().then(function() {
+ console.log('Logfile deleted');
+ });
+
if (window.localStorage.getItem("username") != undefined) {
loginData.username =
window.localStorage.getItem("username");
@@ -140,6 +167,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
console.log ("**** setAwake called with:" + val);
+ zmLog("Switching screen always on to " + val);
if (val)
{
@@ -170,7 +198,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
setLogin: function (newLogin) {
loginData = newLogin;
-
+ zmLog("Saving all parameters to storage");
window.localStorage.setItem("username", loginData.username);
window.localStorage.setItem("password", loginData.password);
window.localStorage.setItem("url", loginData.url);
@@ -222,6 +250,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
if ((monitorsLoaded == 0) || (forceReload == 1)) // monitors are empty or force reload
{
console.log("ZMDataModel: Invoking HTTP get to load monitors");
+ zmLog ( (forceReload==1)? "getMonitors:Force reloading all monitors" : "getMonitors:Loading all monitors");
var apiurl = loginData.apiurl;
var myurl = apiurl + "/monitors.json";
$http.get(myurl /*,{timeout:15000}*/ )
@@ -232,9 +261,11 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
monitorsLoaded = 1;
d.resolve(monitors);
$ionicLoading.hide();
+ zmLog ("Monitor load was successful, loaded " + monitors.length + " monitors");
})
.error(function (err) {
console.log("HTTP Error " + err);
+ zmLog ("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 = [];
@@ -248,6 +279,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
} else // monitors are loaded
{
console.log("Returning pre-loaded list of " + monitors.length + " monitors");
+ zmLog("Returning pre-loaded list of " + monitors.length + " monitors");
d.resolve(monitors);
$ionicLoading.hide();
return d.promise;
@@ -285,6 +317,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
})
.error(function (error) {
console.log("*** ERROR GETTING TOTAL PAGES ***");
+ zmLog ("Error retrieving page count of events " + JSON.stringify(error), "error");
d.reject(error);
return d.promise;
});
@@ -349,6 +382,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
.error(function (err) {
if (loadingStr != 'none') $ionicLoading.hide();
console.log("HTTP Events error " + err);
+ zmLog("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
@@ -379,6 +413,8 @@ angular.module('zmApp.controllers').service('ZMDataModel', ['$http', '$q', '$ion
montageSize = montage;
},
+
+
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
diff --git a/www/js/EventCtrl.js b/www/js/EventCtrl.js
index 284c0147..e4ac3516 100644
--- a/www/js/EventCtrl.js
+++ b/www/js/EventCtrl.js
@@ -208,7 +208,7 @@ angular.module('zmApp.controllers').controller('zmApp.EventCtrl', ['$ionicPlatfo
//--------------------------------------------------------
this.pollEventsProgress = function()
{
- console.log ("**************");
+ // console.log ("**************");
};
//--------------------------------------------------------
@@ -322,6 +322,7 @@ angular.module('zmApp.controllers').controller('zmApp.EventCtrl', ['$ionicPlatfo
req.error(function (resp) {
console.log("ERROR: " + JSON.stringify(resp));
+ ZMDataModel.zmLog("Error sending event command " +JSON.stringify(resp), "error");
});
};
diff --git a/www/js/EventsGraphsCtrl.js b/www/js/EventsGraphsCtrl.js
index 92bc870a..ca2653d3 100644
--- a/www/js/EventsGraphsCtrl.js
+++ b/www/js/EventsGraphsCtrl.js
@@ -81,6 +81,7 @@ angular.module('zmApp.controllers').controller('zmApp.EventsGraphsCtrl', ['$ioni
endDate = cur.format("YYYY-MM-DD hh:mm:ss");
startDate = cur.subtract(hrs, 'hours').format("YYYY-MM-DD hh:mm:ss");
console.log("Start and End " + startDate + "==" + endDate);
+ ZMDataModel.zmLog("Generating graph for " + startDate + " to " + endDate);
}
@@ -139,6 +140,7 @@ angular.module('zmApp.controllers').controller('zmApp.EventsGraphsCtrl', ['$ioni
// but what I am really doing now is treating it like no events
// works but TBD: make this into a proper error handler
$scope.data.datasets[0].data[j] = 0;
+ ZMDataModel.zmLog ("Error retrieving events for graph " + JSON.stringify(data), "error");
});
})(i); // j
} //for
diff --git a/www/js/HelpCtrl.js b/www/js/HelpCtrl.js
index 89c2171b..270cbf33 100644
--- a/www/js/HelpCtrl.js
+++ b/www/js/HelpCtrl.js
@@ -8,6 +8,8 @@ $scope.openMenu = function () {
};
+
+
//-------------------------------------------------------------------------
// Lets make sure we set screen dim properly as we enter
// The problem is we enter other states before we leave previous states
@@ -18,6 +20,7 @@ $scope.openMenu = function () {
$scope.$on('$ionicView.enter', function () {
console.log("**VIEW ** Help Ctrl Entered");
ZMDataModel.setAwake(false);
+
});
}]);
diff --git a/www/js/LogCtrl.js b/www/js/LogCtrl.js
new file mode 100644
index 00000000..1ac903a7
--- /dev/null
+++ b/www/js/LogCtrl.js
@@ -0,0 +1,61 @@
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console */
+
+angular.module('zmApp.controllers').controller('zmApp.LogCtrl', ['$scope', '$rootScope', '$ionicModal', 'ZMDataModel', '$ionicSideMenuDelegate', '$fileLogger', '$cordovaEmailComposer', function ($scope, $rootScope, $ionicModal, ZMDataModel, $ionicSideMenuDelegate, $fileLogger, $cordovaEmailComposer) {
+ $scope.openMenu = function () {
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+
+ //--------------------------------------------------------------------------
+ // Convenience function to send logs via email
+ //--------------------------------------------------------------------------
+ $scope.sendEmail = function (logstring) {
+ if (window.cordova) {
+ // pass= password= should be replaced
+ //logstring = logstring.replace(/password=*?/g, 'password=xxxx'
+ $cordovaEmailComposer.isAvailable().then(function () {
+ var email = {
+ to: '',
+ subject: 'zmNinja Logs',
+ body: logstring,
+ isHtml: false
+ };
+ $cordovaEmailComposer.open(email);
+ }, function () {
+ ZMDataModel.zmLog("Email plugin not found", "error");
+ });
+ } else {
+ console.log("Skipping email module as cordova does not exist");
+ }
+
+ };
+
+ //-------------------------------------------------------------------------
+ // 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");
+ ZMDataModel.setAwake(false);
+
+ $scope.zmLog = {
+ logString: ""
+ };
+ $fileLogger.getLogfile().then(function (l) {
+
+ $scope.zmLog.logString = l;
+ //console.log ("LOGS" + logstring);
+ },
+ function (error) {
+ $scope.zmLog.logString = "Error getting log: " + JSON.stringify(error);
+ });
+
+
+ });
+
+}]);
diff --git a/www/js/LoginCtrl.js b/www/js/LoginCtrl.js
index 1da2f83d..4bf04285 100644
--- a/www/js/LoginCtrl.js
+++ b/www/js/LoginCtrl.js
@@ -138,6 +138,7 @@ function addhttp(url) {
},
function (error) {
$ionicLoading.hide();
+ ZMDataModel.zmLog("Settings check error: " +JSON.stringify(error),"error");
//alert("Error string" + JSON.stringify(error));
$ionicPopup.show({
@@ -155,6 +156,7 @@ function addhttp(url) {
title: 'Error Details',
template: JSON.stringify(error)
});
+
}
}
]
diff --git a/www/js/ModalCtrl.js b/www/js/ModalCtrl.js
index d8f61b03..a9c45c34 100644
--- a/www/js/ModalCtrl.js
+++ b/www/js/ModalCtrl.js
@@ -203,6 +203,7 @@ angular.module('zmApp.controllers').controller('ModalCtrl', ['$scope', '$rootSco
req.error(function (resp) {
$ionicLoading.hide();
console.log("ERROR: " + JSON.stringify(resp));
+ ZMDataModel.zmLog("Error sending PTZ:" + JSON.stringify(resp), "error");
});
}
diff --git a/www/js/MonitorCtrl.js b/www/js/MonitorCtrl.js
index ee9605fc..bf94f48f 100644
--- a/www/js/MonitorCtrl.js
+++ b/www/js/MonitorCtrl.js
@@ -231,10 +231,13 @@ angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', ['$ionicPopu
.success(function(data) {
$scope.ptzMoveCommand = (data.control.Control.CanMoveCon == '1')? 'moveCon':'move';
console.log("***moveCommand: " +$scope.ptzMoveCommand );
+ ZMDataModel.zmLog ("ControlDB reports PTZ command to be " + $scope.ptzMoveCommand );
})
.error(function(data) {
console.log ("** Error retrieving move PTZ command");
+ ZMDataModel.zmLog ("Error retrieving PTZ command " + JSON.stringify(data),"error");
});
+
}
diff --git a/www/js/MontageCtrl.js b/www/js/MontageCtrl.js
index bd2ab1ef..b95d2553 100644
--- a/www/js/MontageCtrl.js
+++ b/www/js/MontageCtrl.js
@@ -146,9 +146,11 @@ angular.module('zmApp.controllers').controller('zmApp.MontageCtrl', ['$scope', '
.success(function(data) {
$scope.ptzMoveCommand = (data.control.Control.CanMoveCon == '1')? 'moveCon':'move';
console.log("***moveCommand: " +$scope.ptzMoveCommand );
+ ZMDataModel.zmLog ("ControlDB reports PTZ command to be " + $scope.ptzMoveCommand );
})
.error(function(data) {
console.log ("** Error retrieving move PTZ command");
+ ZMDataModel.zmLog ("Error retrieving PTZ command " + JSON.stringify(data),"error");
});
}
diff --git a/www/js/StateCtrl.js b/www/js/StateCtrl.js
index ec3a771a..cf6d97d9 100644
--- a/www/js/StateCtrl.js
+++ b/www/js/StateCtrl.js
@@ -64,6 +64,7 @@ angular.module('zmApp.controllers').controller('zmApp.StateCtrl', ['$ionicPopup'
function (error) {
$scope.zmDisk = "unknown";
console.log("ERROR:" + JSON.stringify(error));
+ ZMDataModel.zmLog("Error retrieving DiskStatus: " + JSON.stringify(error),"error");
}
);
}
@@ -95,6 +96,7 @@ angular.module('zmApp.controllers').controller('zmApp.StateCtrl', ['$ionicPopup'
},
function (error) {
console.log("ERROR in getRun: " + JSON.stringify(error));
+ ZMDataModel.zmLog("Error getting RunStatus " + JSON.stringify(error),"error");
$scope.color = 'color:red;';
$scope.zmRun = 'undetermined';
}
@@ -119,6 +121,7 @@ angular.module('zmApp.controllers').controller('zmApp.StateCtrl', ['$ionicPopup'
},
function (error) {
console.log("ERROR in getLoad: " + JSON.stringify(error));
+ ZMDataModel.zmLog("Error retrieving loadStatus " + JSON.stringify(error),"error");
$scope.zmLoad = 'undetermined';
}
);
@@ -176,6 +179,7 @@ angular.module('zmApp.controllers').controller('zmApp.StateCtrl', ['$ionicPopup'
//if (error.status) // it seems to return error with status 0 if ok
// {
console.log("ERROR in Change State:" + JSON.stringify(error));
+ ZMDataModel.zmLog("Error in change run state:"+JSON.stringify(error),"error");
$scope.zmRun = 'undetermined';
$scope.color = 'color:orange;';
inProgress = 0;
diff --git a/www/js/app.js b/www/js/app.js
index ffff0c68..bd21fabe 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -7,6 +7,7 @@
angular.module('zmApp', [
'ionic',
'zmApp.controllers',
+ 'fileLogger'
])
//------------------------------------------------------------------
@@ -51,7 +52,7 @@ angular.module('zmApp', [
}
else
{
- console.log ("HTTP INTERCEPT:Skipping HTTP timeout for "+config.url);
+ //console.log ("HTTP INTERCEPT:Skipping HTTP timeout for "+config.url);
}
//console.log("*** HTTP URL INTERCEPTOR CALLED with "+config.url+" ***");
return config;
@@ -70,6 +71,7 @@ angular.module('zmApp', [
function doLogin()
{
console.log ("**** ZM AUTO LOGIN CALLED");
+ ZMDataModel.zmLog("zmAutologin timer started");
var loginData = ZMDataModel.getLogin();
$http({
method:'POST',
@@ -98,10 +100,12 @@ angular.module('zmApp', [
.success(function(data)
{
console.log ("**** ZM Login OK");
+ ZMDataModel.zmLog("zmAutologin successfully logged into Zoneminder");
})
.error(function(error)
{
console.log ("**** ZM Login FAILED");
+ ZMDataModel.zmLog ("zmAutologin Error " + JSON.stringify(error), "error");
});
}
@@ -120,6 +124,7 @@ angular.module('zmApp', [
function stop()
{
$interval.cancel(zmAutoLoginHandle);
+ ZMDataModel.zmLog("Cancelling zmAutologin timer");
}
@@ -166,6 +171,7 @@ angular.module('zmApp', [
var loginData = ZMDataModel.getLogin();
if (ZMDataModel.isLoggedIn()) {
+ ZMDataModel.zmLog ("User is logged in");
console.log("VALID CREDENTIALS. Grabbing Monitors");
ZMDataModel.getMonitors(0);
@@ -179,6 +185,7 @@ angular.module('zmApp', [
$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);
+ ZMDataModel.zmLog("Device orientation change: "+$rootScope.devWidth + "*" + $rootScope.devHeight);
};
window.addEventListener("resize", checkOrientation, false);
@@ -211,9 +218,11 @@ angular.module('zmApp', [
$ionicPlatform.ready(function () {
// generates and error in desktops but works fine
+ ZMDataModel.zmLog("Device is ready");
console.log("**** DEVICE READY ***");
+
setTimeout(function () {
if (window.cordova)
{
@@ -234,6 +243,7 @@ angular.module('zmApp', [
// from foreground to background and back
document.addEventListener("resume", function () {
console.log("****The application is resuming from the background");
+ ZMDataModel.zmLog("App is resuming from background");
$rootScope.rand = Math.floor((Math.random() * 100000) + 1);
console.log("** generated Random of " + $rootScope.rand);
$state.go($state.current, {}, {
@@ -246,6 +256,7 @@ angular.module('zmApp', [
document.addEventListener("pause", function () {
console.log("****The application is going into background");
+ ZMDataModel.zmLog("App is going into background");
zmAutoLogin.stop();
}, false);
@@ -361,6 +372,15 @@ angular.module('zmApp', [
controller: 'zmApp.DevOptionsCtrl',
})
+ .state('log', {
+ data: {
+ requireLogin: false
+ },
+ url: "/log",
+ templateUrl: "templates/log.html",
+ controller: 'zmApp.LogCtrl',
+ })
+
.state('montage', {
data: {
requireLogin: true
diff --git a/www/lib/filelogger/.bower.json b/www/lib/filelogger/.bower.json
new file mode 100644
index 00000000..c063767b
--- /dev/null
+++ b/www/lib/filelogger/.bower.json
@@ -0,0 +1,40 @@
+{
+ "name": "filelogger",
+ "version": "1.1.0",
+ "homepage": "https://github.com/pbakondy/filelogger",
+ "authors": [
+ "Peter Bakondy <pbakondy@gmail.com>"
+ ],
+ "description": "Cordova library to log messages to local filesystem",
+ "main": "./dist/filelogger.min.js",
+ "ignore": [
+ "**/.*",
+ "gulpfile.js",
+ "src",
+ "config"
+ ],
+ "dependencies": {
+ "ngCordova": ">= 0.1.14-alpha"
+ },
+ "keywords": [
+ "cordova",
+ "cordova plugin",
+ "ionic",
+ "ionic plugin",
+ "angular module",
+ "log",
+ "logger",
+ "file logger"
+ ],
+ "license": "MIT",
+ "_release": "1.1.0",
+ "_resolution": {
+ "type": "version",
+ "tag": "v1.1.0",
+ "commit": "4b5f765cf1737a51cc29bcaeca035d72eb6e8272"
+ },
+ "_source": "git://github.com/pbakondy/filelogger.git",
+ "_target": "~1.1.0",
+ "_originalSource": "filelogger",
+ "_direct": true
+}
diff --git a/www/lib/filelogger/LICENSE b/www/lib/filelogger/LICENSE
new file mode 100644
index 00000000..ec5a3987
--- /dev/null
+++ b/www/lib/filelogger/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Peter Bakondy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/www/lib/filelogger/README.md b/www/lib/filelogger/README.md
new file mode 100644
index 00000000..1d6c6456
--- /dev/null
+++ b/www/lib/filelogger/README.md
@@ -0,0 +1,123 @@
+Cordova File Logger
+==========
+
+[![Bower](http://img.shields.io/badge/bower-filelogger-FFCC2F.svg?style=flat)](http://bower.io/search/?q=filelogger)
+
+Logger module for Cordova/Ionic projects.
+
+When you run your application in device the Logger writes in the local filesystem (with cordova-plugin-file) and the system logs (with console.log).
+
+When you run your application in browser with „ionic serve” the Logger uses browsers localStorage and the browser console (with console.log).
+
+## Dependencies
+
+- [ngCordova](http://ngcordova.com/) ( required version v0.1.14-alpha )
+- [org.apache.cordova.file](https://github.com/apache/cordova-plugin-file)
+
+## Installation
+
+Install manually, or from bower:
+
+```bash
+$ bower install filelogger
+```
+
+Include *filelogger.min.js* and ng-cordova.js or *ng-cordova.min.js* in your index.html file before cordova.js and after your AngularJS / Ionic file (since ngCordova depends on AngularJS).
+
+```html
+<script src="lib/ngCordova/dist/ng-cordova.min.js"></script>
+<script src="lib/filelogger/dist/filelogger.min.js"></script>
+<script src="cordova.js"></script>
+```
+
+Comment: you don't have to use the complete ngCordova package. I suggest to create a [Custom Build](http://ngcordova.com/build/) with file module.
+
+
+## Usage
+
+### $fileLogger.log()
+
+General logger method. The first parameter is the log level (debug, info, warn, error). The following parameters are the message parts.
+You can put here any javascript type (string, number, boolean, object, array).
+
+In the logfile every item starts with the current UTC timestamp, followed by the log level and the message.
+
+### $fileLogger.debug()
+
+Wrapper for $fileLogger.log('debug', ...)
+
+### $fileLogger.info()
+
+Wrapper for $fileLogger.log('info', ...)
+
+### $fileLogger.warn()
+
+Wrapper for $fileLogger.log('warn', ...)
+
+### $fileLogger.error()
+
+Wrapper for $fileLogger.log('error', ...)
+
+### $fileLogger.setStorageFilename()
+
+You can set the local filename (default messages.log). It requests one parameter, the filename (type string).
+
+### $fileLogger.getLogfile()
+
+You can read the whole logfile from the filestore. This method returns a promise.
+
+### $fileLogger.deleteLogfile()
+
+You can delete the logfile from the filestore. This method returns a promise.
+
+
+
+### Example use
+
+```js
+angular.module('starter', ['ionic', 'fileLogger'])
+ .controller('mainCtrl', ['$scope', '$fileLogger', function($scope, $fileLogger) {
+
+ function testing() {
+
+ $fileLogger.setStorageFilename('myLog.txt');
+
+ $fileLogger.log('debug', 'message');
+ $fileLogger.log('info', 'message');
+ $fileLogger.log('warn', 'message');
+ $fileLogger.log('error', 'message');
+
+ $fileLogger.debug('message');
+ $fileLogger.info('message');
+ $fileLogger.warn('message');
+ $fileLogger.error('message');
+
+ $fileLogger.log('error', 'error message', { code: 1, meaning: 'general' });
+
+ $fileLogger.log('info', 'message', 123, [1, 2, 3], { a: 1, b: '2' });
+
+ $fileLogger.getLogfile().then(function(l) {
+ console.log('Logfile content');
+ console.log(l);
+ });
+
+ $fileLogger.deleteLogfile().then(function() {
+ console.log('Logfile deleted');
+ });
+
+ }
+
+}]);
+```
+
+
+## Author
+
+#### Peter Bakondy
+
+- https://github.com/pbakondy
+
+
+## LICENSE
+
+Cordova File Logger is licensed under the MIT Open Source license. For more information, see the LICENSE file in this repository.
diff --git a/www/lib/filelogger/bower.json b/www/lib/filelogger/bower.json
new file mode 100644
index 00000000..d6a109bd
--- /dev/null
+++ b/www/lib/filelogger/bower.json
@@ -0,0 +1,30 @@
+{
+ "name": "filelogger",
+ "version": "1.1.0",
+ "homepage": "https://github.com/pbakondy/filelogger",
+ "authors": [
+ "Peter Bakondy <pbakondy@gmail.com>"
+ ],
+ "description": "Cordova library to log messages to local filesystem",
+ "main": "./dist/filelogger.min.js",
+ "ignore": [
+ "**/.*",
+ "gulpfile.js",
+ "src",
+ "config"
+ ],
+ "dependencies": {
+ "ngCordova": ">= 0.1.14-alpha"
+ },
+ "keywords": [
+ "cordova",
+ "cordova plugin",
+ "ionic",
+ "ionic plugin",
+ "angular module",
+ "log",
+ "logger",
+ "file logger"
+ ],
+ "license": "MIT"
+}
diff --git a/www/lib/filelogger/dist/filelogger.js b/www/lib/filelogger/dist/filelogger.js
new file mode 100644
index 00000000..c266f22a
--- /dev/null
+++ b/www/lib/filelogger/dist/filelogger.js
@@ -0,0 +1,274 @@
+/*!
+ * fileLogger
+ * Copyright 2015 Peter Bakondy https://github.com/pbakondy
+ * See LICENSE in this repository for license information
+ */
+(function(){
+/* global angular, console, cordova */
+
+// install : cordova plugin add org.apache.cordova.file
+
+angular.module('fileLogger', ['ngCordova.plugins.file'])
+
+ .factory('$fileLogger', ['$q', '$window', '$cordovaFile', '$timeout', function ($q, $window, $cordovaFile, $timeout) {
+ 'use strict';
+
+
+ var queue = [];
+ var ongoing = false;
+ var levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
+
+ var storageFilename = 'messages.log';
+
+
+ function isBrowser() {
+ return (!$window.cordova && !$window.PhoneGap && !$window.phonegap);
+ }
+
+
+ function log(level) {
+ if (angular.isString(level)) {
+ level = level.toUpperCase();
+
+ if (levels.indexOf(level) === -1) {
+ level = 'INFO';
+ }
+ } else {
+ level = 'INFO';
+ }
+
+ var timestamp = (new Date()).toJSON();
+
+ var messages = Array.prototype.slice.call(arguments, 1);
+ var message = [ timestamp, level ];
+
+ for (var i = 0; i < messages.length; i++ ) {
+ if (angular.isArray(messages[i])) {
+ message.push(JSON.stringify(messages[i]));
+ }
+ else if (angular.isObject(messages[i])) {
+ message.push(JSON.stringify(messages[i]));
+ }
+ else {
+ message.push(messages[i]);
+ }
+ }
+
+ if (isBrowser()) {
+ // log to browser console
+
+ messages.unshift(timestamp);
+
+ if (angular.isObject(console) && angular.isFunction(console.log)) {
+ switch (level) {
+ case 'DEBUG':
+ if (angular.isFunction(console.debug)) {
+ console.debug.apply(console, messages);
+ } else {
+ console.log.apply(console, messages);
+ }
+ break;
+ case 'INFO':
+ if (angular.isFunction(console.debug)) {
+ console.info.apply(console, messages);
+ } else {
+ console.log.apply(console, messages);
+ }
+ break;
+ case 'WARN':
+ if (angular.isFunction(console.debug)) {
+ console.warn.apply(console, messages);
+ } else {
+ console.log.apply(console, messages);
+ }
+ break;
+ case 'ERROR':
+ if (angular.isFunction(console.debug)) {
+ console.error.apply(console, messages);
+ } else {
+ console.log.apply(console, messages);
+ }
+ break;
+ default:
+ console.log.apply(console, messages);
+ }
+ }
+
+ } else {
+ // log to logcat
+ console.log(message.join(' '));
+ }
+
+ queue.push({ message: message.join(' ') + '\n' });
+
+ if (!ongoing) {
+ process();
+ }
+ }
+
+
+ function process() {
+
+ if (!queue.length) {
+ ongoing = false;
+ return;
+ }
+
+ ongoing = true;
+ var m = queue.shift();
+
+ writeLog(m.message).then(
+ function() {
+ $timeout(function() {
+ process();
+ });
+ },
+ function() {
+ $timeout(function() {
+ process();
+ });
+ }
+ );
+
+ }
+
+
+ function writeLog(message) {
+ var q = $q.defer();
+
+ if (isBrowser()) {
+ // running in browser with 'ionic serve'
+
+ if (!$window.localStorage[storageFilename]) {
+ $window.localStorage[storageFilename] = '';
+ }
+
+ $window.localStorage[storageFilename] += message;
+ q.resolve();
+
+ } else {
+
+ $cordovaFile.checkFile(cordova.file.dataDirectory, storageFilename).then(
+ function() {
+ // writeExistingFile(path, fileName, text)
+ $cordovaFile.writeExistingFile(cordova.file.dataDirectory, storageFilename, message).then(
+ function() {
+ q.resolve();
+ },
+ function(error) {
+ q.reject(error);
+ }
+ );
+ },
+ function() {
+ // writeFile(path, fileName, text, replaceBool)
+ $cordovaFile.writeFile(cordova.file.dataDirectory, storageFilename, message, true).then(
+ function() {
+ q.resolve();
+ },
+ function(error) {
+ q.reject(error);
+ }
+ );
+ }
+ );
+
+ }
+
+ return q.promise;
+ }
+
+
+ function getLogfile() {
+ var q = $q.defer();
+
+ if (isBrowser()) {
+ q.resolve($window.localStorage[storageFilename]);
+ } else {
+ $cordovaFile.readAsText(cordova.file.dataDirectory, storageFilename).then(
+ function(result) {
+ q.resolve(result);
+ },
+ function(error) {
+ q.reject(error);
+ }
+ );
+ }
+
+ return q.promise;
+ }
+
+
+ function deleteLogfile() {
+ var q = $q.defer();
+
+ if (isBrowser()) {
+ $window.localStorage.removeItem(storageFilename);
+ q.resolve();
+ } else {
+ $cordovaFile.removeFile(cordova.file.dataDirectory, storageFilename).then(
+ function(result) {
+ q.resolve(result);
+ },
+ function(error) {
+ q.reject(error);
+ }
+ );
+ }
+
+ return q.promise;
+ }
+
+
+ function setStorageFilename(filename) {
+ if (angular.isString(filename) && filename.length > 0) {
+ storageFilename = filename;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ function debug() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ args.unshift('DEBUG');
+ log.apply(undefined, args);
+ }
+
+
+ function info() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ args.unshift('INFO');
+ log.apply(undefined, args);
+ }
+
+
+ function warn() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ args.unshift('WARN');
+ log.apply(undefined, args);
+ }
+
+
+ function error() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ args.unshift('ERROR');
+ log.apply(undefined, args);
+ }
+
+
+ return {
+ log: log,
+ getLogfile: getLogfile,
+ deleteLogfile: deleteLogfile,
+ setStorageFilename: setStorageFilename,
+ debug: debug,
+ info: info,
+ warn: warn,
+ error: error
+ };
+
+ }]);
+
+})();
diff --git a/www/lib/filelogger/dist/filelogger.min.js b/www/lib/filelogger/dist/filelogger.min.js
new file mode 100644
index 00000000..366521a2
--- /dev/null
+++ b/www/lib/filelogger/dist/filelogger.min.js
@@ -0,0 +1,6 @@
+/*!
+ * fileLogger
+ * Copyright 2015 Peter Bakondy https://github.com/pbakondy
+ * See LICENSE in this repository for license information
+ */
+!function(){angular.module("fileLogger",["ngCordova.plugins.file"]).factory("$fileLogger",["$q","$window","$cordovaFile","$timeout",function(e,o,n,r){"use strict";function l(){return!o.cordova&&!o.PhoneGap&&!o.phonegap}function t(e){angular.isString(e)?(e=e.toUpperCase(),-1===h.indexOf(e)&&(e="INFO")):e="INFO";for(var o=(new Date).toJSON(),n=Array.prototype.slice.call(arguments,1),r=[o,e],t=0;t<n.length;t++)r.push(angular.isArray(n[t])?JSON.stringify(n[t]):angular.isObject(n[t])?JSON.stringify(n[t]):n[t]);if(l()){if(n.unshift(o),angular.isObject(console)&&angular.isFunction(console.log))switch(e){case"DEBUG":angular.isFunction(console.debug)?console.debug.apply(console,n):console.log.apply(console,n);break;case"INFO":angular.isFunction(console.debug)?console.info.apply(console,n):console.log.apply(console,n);break;case"WARN":angular.isFunction(console.debug)?console.warn.apply(console,n):console.log.apply(console,n);break;case"ERROR":angular.isFunction(console.debug)?console.error.apply(console,n):console.log.apply(console,n);break;default:console.log.apply(console,n)}}else console.log(r.join(" "));v.push({message:r.join(" ")+"\n"}),y||a()}function a(){if(!v.length)return void(y=!1);y=!0;var e=v.shift();i(e.message).then(function(){r(function(){a()})},function(){r(function(){a()})})}function i(r){var t=e.defer();return l()?(o.localStorage[m]||(o.localStorage[m]=""),o.localStorage[m]+=r,t.resolve()):n.checkFile(cordova.file.dataDirectory,m).then(function(){n.writeExistingFile(cordova.file.dataDirectory,m,r).then(function(){t.resolve()},function(e){t.reject(e)})},function(){n.writeFile(cordova.file.dataDirectory,m,r,!0).then(function(){t.resolve()},function(e){t.reject(e)})}),t.promise}function c(){var r=e.defer();return l()?r.resolve(o.localStorage[m]):n.readAsText(cordova.file.dataDirectory,m).then(function(e){r.resolve(e)},function(e){r.reject(e)}),r.promise}function s(){var r=e.defer();return l()?(o.localStorage.removeItem(m),r.resolve()):n.removeFile(cordova.file.dataDirectory,m).then(function(e){r.resolve(e)},function(e){r.reject(e)}),r.promise}function u(e){return angular.isString(e)&&e.length>0?(m=e,!0):!1}function g(){var e=Array.prototype.slice.call(arguments,0);e.unshift("DEBUG"),t.apply(void 0,e)}function f(){var e=Array.prototype.slice.call(arguments,0);e.unshift("INFO"),t.apply(void 0,e)}function p(){var e=Array.prototype.slice.call(arguments,0);e.unshift("WARN"),t.apply(void 0,e)}function d(){var e=Array.prototype.slice.call(arguments,0);e.unshift("ERROR"),t.apply(void 0,e)}var v=[],y=!1,h=["DEBUG","INFO","WARN","ERROR"],m="messages.log";return{log:t,getLogfile:c,deleteLogfile:s,setStorageFilename:u,debug:g,info:f,warn:p,error:d}}])}();
diff --git a/www/lib/filelogger/package.json b/www/lib/filelogger/package.json
new file mode 100644
index 00000000..0f1619a0
--- /dev/null
+++ b/www/lib/filelogger/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "filelogger",
+ "private": false,
+ "main": "dist/filelogger",
+ "version": "1.1.0",
+ "repository": {
+ "url": "git://github.com/pbakondy/filelogger.git"
+ },
+ "devDependencies": {
+ "gulp": "^3.7.0",
+ "gulp-footer": "^1.0.4",
+ "gulp-header": "^1.0.2",
+ "gulp-jshint": "^1.6.1",
+ "gulp-rename": "^1.2.0",
+ "gulp-uglify": "^0.2.1",
+ "jshint-stylish": "^0.4.0",
+ "minimist": "^0.1.0"
+ },
+ "licenses": [
+ {
+ "type": "MIT"
+ }
+ ],
+ "dependencies": {
+ }
+}
diff --git a/www/templates/log.html b/www/templates/log.html
new file mode 100644
index 00000000..102877ae
--- /dev/null
+++ b/www/templates/log.html
@@ -0,0 +1,16 @@
+<ion-view view-title="Logs">
+
+ <ion-nav-buttons side="left">
+ <button class="button button-icon button-clear ion-navicon" ng-click="openMenu()"></button>
+ </ion-nav-buttons>
+
+ <ion-nav-buttons side="right">
+ <a style="" class="button button-icon icon ion-email" ng-href="" ng-click="sendEmail(zmLog.logString)" > </a>
+ </ion-nav-buttons>
+
+ <ion-content class="padding">
+ <pre>
+ {{zmLog.logString}}
+ </pre>
+
+ </ion-content>