summaryrefslogtreecommitdiffstats
path: root/afm-client/app
diff options
context:
space:
mode:
authorFulup Ar Foll <fulup@iot.bzh>2016-01-25 14:37:32 +0100
committerFulup Ar Foll <fulup@iot.bzh>2016-01-25 14:37:32 +0100
commit3ebdce373e134b70b129154d8033c1c628847a6c (patch)
tree76d84a1b1e6e6b474c7eecad2ea098ba0de69300 /afm-client/app
parent8d03b8c581ce64192e9d265597d262b59826cffd (diff)
First version
Diffstat (limited to 'afm-client/app')
-rw-r--r--afm-client/app/Backend/RestApis/AfmMainMockApi.js52
-rw-r--r--afm-client/app/Backend/RestApis/PostMockApi.js53
-rw-r--r--afm-client/app/Backend/RestApis/TokenMockApi.js114
-rw-r--r--afm-client/app/Backend/RestApis/_all.js29
-rw-r--r--afm-client/app/Backend/server.js58
-rw-r--r--afm-client/app/Frontend/app.js64
-rw-r--r--afm-client/app/Frontend/etc/AppConfig.js55
-rw-r--r--afm-client/app/Frontend/etc/routes.js1
-rw-r--r--afm-client/app/Frontend/favicon.icobin0 -> 1150 bytes
-rw-r--r--afm-client/app/Frontend/images/appli/isnotvalid.pngbin0 -> 11124 bytes
-rw-r--r--afm-client/app/Frontend/images/appli/istoobig.pngbin0 -> 35678 bytes
-rw-r--r--afm-client/app/Frontend/images/appli/upload-appli.pngbin0 -> 24483 bytes
-rw-r--r--afm-client/app/Frontend/images/appli/w3c-widget.pngbin0 -> 26269 bytes
-rw-r--r--afm-client/app/Frontend/images/audio/istoobig.pngbin0 -> 35678 bytes
-rw-r--r--afm-client/app/Frontend/images/audio/upload-music.pngbin0 -> 69741 bytes
-rw-r--r--afm-client/app/Frontend/images/avatars/istoobig.jpgbin0 -> 7204 bytes
-rw-r--r--afm-client/app/Frontend/images/avatars/istoobig.pngbin0 -> 35678 bytes
-rw-r--r--afm-client/app/Frontend/images/avatars/tux-admin.pngbin0 -> 58558 bytes
-rw-r--r--afm-client/app/Frontend/images/avatars/tux-bzh.pngbin0 -> 65172 bytes
-rw-r--r--afm-client/app/Frontend/images/avatars/tux-visitor.pngbin0 -> 84573 bytes
-rw-r--r--afm-client/app/Frontend/images/icons/annex-ico.pngbin0 -> 12580 bytes
-rw-r--r--afm-client/app/Frontend/images/icons/memorymatch-ico.pngbin0 -> 19384 bytes
-rw-r--r--afm-client/app/Frontend/images/icons/rabbit-ico.pngbin0 -> 20118 bytes
-rw-r--r--afm-client/app/Frontend/images/icons/w3c-ico.pngbin0 -> 15330 bytes
-rw-r--r--afm-client/app/Frontend/images/login/fb-logo.pngbin0 -> 2482 bytes
-rw-r--r--afm-client/app/Frontend/images/login/gg-logo.pngbin0 -> 3554 bytes
-rw-r--r--afm-client/app/Frontend/images/login/gh-logo.pngbin0 -> 4946 bytes
-rw-r--r--afm-client/app/Frontend/images/login/iot-logo.pngbin0 -> 3471 bytes
-rw-r--r--afm-client/app/Frontend/images/login/lk-logo.pngbin0 -> 4579 bytes
-rw-r--r--afm-client/app/Frontend/images/login/ms-logo.pngbin0 -> 6036 bytes
-rw-r--r--afm-client/app/Frontend/images/login/og-logo.pngbin0 -> 2706 bytes
-rw-r--r--afm-client/app/Frontend/images/login/pp-logo.pngbin0 -> 3222 bytes
-rw-r--r--afm-client/app/Frontend/images/login/yh-logo.pngbin0 -> 5491 bytes
-rw-r--r--afm-client/app/Frontend/images/logo/logo_iot_bzh.svg139
-rw-r--r--afm-client/app/Frontend/images/logo/logo_iot_bzhx350.pngbin0 -> 14449 bytes
-rw-r--r--afm-client/app/Frontend/images/logo/tampon-iot-bzhx450.pngbin0 -> 44996 bytes
-rw-r--r--afm-client/app/Frontend/images/logo/triskel_iot_bzh.pngbin0 -> 55940 bytes
-rw-r--r--afm-client/app/Frontend/images/logo/triskel_iot_bzh.svg110
-rw-r--r--afm-client/app/Frontend/images/logo/triskel_iot_bzhx250.pngbin0 -> 18787 bytes
-rw-r--r--afm-client/app/Frontend/index.html41
-rw-r--r--afm-client/app/Frontend/pages/Dashboard/Dashboard.html33
-rw-r--r--afm-client/app/Frontend/pages/Dashboard/DashboardModule.js75
-rw-r--r--afm-client/app/Frontend/pages/Dashboard/DashboardModule.scss71
-rw-r--r--afm-client/app/Frontend/pages/Home/Dashboard.html40
-rw-r--r--afm-client/app/Frontend/pages/Home/DashboardModule.js118
-rw-r--r--afm-client/app/Frontend/pages/Home/DashboardModule.scss71
-rw-r--r--afm-client/app/Frontend/pages/Sample/Sample.html35
-rw-r--r--afm-client/app/Frontend/pages/Sample/SampleModule.js18
-rw-r--r--afm-client/app/Frontend/pages/Sample/SampleModule.scss41
-rw-r--r--afm-client/app/Frontend/services/JQueryEmu.js79
-rw-r--r--afm-client/app/Frontend/styles/README.md28
-rw-r--r--afm-client/app/Frontend/styles/app/_ibz-mixins.scss52
-rw-r--r--afm-client/app/Frontend/styles/app/ibz-global.scss51
-rw-r--r--afm-client/app/Frontend/styles/foundation/_foundation-icons.scss591
-rw-r--r--afm-client/app/Frontend/styles/foundation/_foundation-settings.scss605
-rw-r--r--afm-client/app/Frontend/styles/foundation/foundation-conf.scss19
-rw-r--r--afm-client/app/Frontend/widgets/ActionButtons/ActionButtons.scss27
-rw-r--r--afm-client/app/Frontend/widgets/ActionButtons/AppliButton.js130
-rw-r--r--afm-client/app/Frontend/widgets/ActionButtons/SubmitButton.js52
-rw-r--r--afm-client/app/Frontend/widgets/FormInput/FormInput.scss90
-rw-r--r--afm-client/app/Frontend/widgets/FormInput/InputPassword.js79
-rw-r--r--afm-client/app/Frontend/widgets/FormInput/InputText.js179
-rw-r--r--afm-client/app/Frontend/widgets/FormInput/UploadAppli.js230
-rw-r--r--afm-client/app/Frontend/widgets/Navigation/LinkButton.js57
-rw-r--r--afm-client/app/Frontend/widgets/Navigation/Navigation.scss26
-rw-r--r--afm-client/app/Frontend/widgets/Notifications/ModalNotification.js85
-rw-r--r--afm-client/app/Frontend/widgets/Notifications/Notifications.scss63
-rw-r--r--afm-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js131
-rw-r--r--afm-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js631
-rw-r--r--afm-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss67
-rw-r--r--afm-client/app/etc/AppDefaults.js43
-rw-r--r--afm-client/app/etc/_Config.js44
-rw-r--r--afm-client/app/etc/_Trace.js55
73 files changed, 4632 insertions, 0 deletions
diff --git a/afm-client/app/Backend/RestApis/AfmMainMockApi.js b/afm-client/app/Backend/RestApis/AfmMainMockApi.js
new file mode 100644
index 0000000..0d8cd87
--- /dev/null
+++ b/afm-client/app/Backend/RestApis/AfmMainMockApi.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* ----------------------------------------------------------------------
+ * This module simulate Application Framework Binder
+ *
+ * /api/afm-main/runnables // no params
+ * /api/afm-main/details &id="xxxx"
+ * /api/afm-main/start &id="xxxx"
+ * /api/afm-main/terminate &id="xxxx"
+ * /api/afm-main/stop &id="xxxx"
+ * /api/afm-main/continue &id="xxxx"
+ * /api/afm-main/runners &id="xxxx"
+ * /api/afm-main/state &id="xxxx"
+ * ----------------------------------------------------------------------*/
+
+
+function NewApi(handle, prefix) {
+ var scope=this; // I hate JavaScript
+ scope.connected=false;
+
+ // Simulate Client Context Session Creation
+ handle.app.get (prefix +'/runnables', function (req, res) {
+ var Response= { jtype: "AJB_reply",
+ request: { "prefix": "afm-main", "api": "runnables", "uuid": "e4ef5e66-xxxx", "token": "123456789-xxxxx", "status": "processed" },
+ response: [
+ {id: "webapps-rabbit@0.0", version: "0.0.8", name: "Rabbit", description: "Fun grid game where the rabbit finds and eats the carrots dodging the foxes.", shortname: "", author: "Todd Brandt <todd.e.brandt@intel.com>" },
+ {id: "webapps-annex@0.0", version: "0.0.10", name: "Annex", description: "Reversi/Othello", shortname: "", author: "Todd Brandt <todd.e.brandt@intel.com>" },
+ {id: "webapps-memory-match@1.1", version: "1.1.7", name: "MemoryMatch", description: "Memory match", shortname: "", author: "Todd Brandt <todd.e.brandt@intel.com>" }
+ ]};
+
+ res.send(Response);
+ });
+}
+
+// Export Class
+module.exports = NewApi; \ No newline at end of file
diff --git a/afm-client/app/Backend/RestApis/PostMockApi.js b/afm-client/app/Backend/RestApis/PostMockApi.js
new file mode 100644
index 0000000..022f774
--- /dev/null
+++ b/afm-client/app/Backend/RestApis/PostMockApi.js
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * References: https://github.com/expressjs/multer
+ */
+
+var fs = require('fs');
+var multer = require('multer');
+
+function NewApi(handle, prefix) {
+ var scope=this; // make sure not to loose object context in async callback
+
+ // defined upload directory and check it's a valid one
+ var upload = multer({ dest: handle.config.UPLOAD_DIR});
+ // WARNING: single('avatar') should match with <upload-image name="avatar">
+ handle.app.post(prefix +'/upload-image', upload.single('avatar'), function (req, res) {
+
+ handle.trace (scope, 1, "%s/upload file=%s dest=%s/%s", prefix, req.file.originalname, req.file.destination, req.file.filename);
+ res.send({"jtype": "TEST_message", "status": "success", "info": "done"});
+ });
+
+ // WARNING: single('music') should match with <upload-audio name="music">
+ handle.app.post(prefix +'/upload-music', upload.single('music'), function (req, res) {
+
+ handle.trace (scope, 1, "%s/upload file=%s dest=%s/%s", prefix, req.file.originalname, req.file.destination, req.file.filename);
+ res.send({"jtype": "TEST_message", "status": "success", "info": "done"});
+ });
+
+ // WARNING: single('appli') should match with <upload-audio name="appli">
+ handle.app.post(prefix +'/upload-appli', upload.single('appli'), function (req, res) {
+
+ handle.trace (scope, 1, "%s/upload file=%s dest=%s/%s", prefix, req.file.originalname, req.file.destination, req.file.filename);
+ res.send({"jtype": "TEST_message", "status": "success", "info": "done"});
+ });
+
+}
+
+// Export Class
+module.exports = NewApi; \ No newline at end of file
diff --git a/afm-client/app/Backend/RestApis/TokenMockApi.js b/afm-client/app/Backend/RestApis/TokenMockApi.js
new file mode 100644
index 0000000..5ef1cfa
--- /dev/null
+++ b/afm-client/app/Backend/RestApis/TokenMockApi.js
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* ----------------------------------------------------------------------
+ * This module simulate Application Framework Binder
+ *
+ * /api/afbs/create
+ * /api/afbs/check?token=123456789
+ * /api/afbs/refresh?token=123456789-xxxxx
+ * /api/afbs/reset?123456789-xxxxx
+ *
+ * Note: this MOCK api does not handle any session login. It only returns
+ * a fake valid or false message depending on call order.
+ * Its goal is to get a quick way to check you HTML5 client rendering & behaviour.
+ *
+ * When you're happy with you HTML5 client OnePageApp check it with afb-daemon
+ * ----------------------------------------------------------------------*/
+
+
+function NewApi(handle, prefix) {
+ var scope=this; // I hate JavaScript
+ scope.connected=false;
+
+ // Simulate Client Context Session Creation
+ handle.app.get(prefix +'/create', function (req, res) {
+ handle.trace (scope, 1, "%s/create", prefix);
+ var okResponse= '{ "jtype": "AJB_reply"' +
+ ', "request": { "prefix": "afbs", "api": "create", "uuid": "e4ef5e66-xxxx", "token": "123456789-xxxxx", "status": "processed" }'+
+ ', "response": { "token": "Token was refreshed" }'+
+ '}';
+
+ var fxResponse= '{ "jtype": "AJB_reply" ' +
+ ', "request": { "prefix": "afbs", "api": "create", "status": "fail", "info": "AFB_SESSION_REFRESH Not Initial Token Chain" }'+
+ '}';
+
+ //if (scope.connected) res.status(401).send(fxResponse);
+ //else {
+ res.send(okResponse);
+ scope.connected=true;
+ //}
+ });
+
+
+ // Simulate Client Context Check
+ handle.app.get(prefix +'/check', function (req, res) {
+ handle.trace (scope, 1, "%s/check query=%s", prefix, req.query.token);
+ var okResponse= '{"jtype":"AJB_reply"'+
+ ',"request":{"prefix":"afbs","api":"check", "status":"processed"}'+
+ ',"response":{"isvalid":true}'+
+ '}';
+
+ var fxResponse= '{"jtype":"AJB_reply",'+
+ '"request":{"prefix":"afbs","api":"check","status":"empty","info":"AFB_SESSION_CHECK Not a Valid Active Token"}'+
+ '}';
+
+ if (!scope.connected) res.status(401).send(fxResponse);
+ else res.send(okResponse);
+ });
+
+ // Simulate Client Context Check
+ handle.app.get(prefix +'/refresh', function (req, res) {
+ handle.trace (scope, 1, "%s/refresh query=%s", prefix, req.query.token);
+ var okResponse= '{"jtype":"AJB_reply"'+
+ ',"request":{"prefix":"afbs","api":"refresh","uuid": "e4ef5e66-xxxx", "token": "123456789-xxxxx","status":"processed"}'+
+ ',"response":{"isvalid":true}'+
+ '}';
+
+ var fxResponse= '{"jtype":"AJB_reply",'+
+ '"request":{"prefix":"afbs","api":"refresh","status":"empty","info":"AFB_SESSION_REFRESH Not a Valid Active Token"}'+
+ '}';
+
+ if (!scope.connected) res.status(401).send(fxResponse);
+ else res.send(okResponse);
+ });
+
+ // Simulate Client Context Session Closing
+ handle.app.get(prefix +'/reset', function (req, res) {
+ handle.trace (scope, 1, "%s/reset query=%s", prefix, req.query.token);
+ var okResponse= '{"jtype":"AJB_reply"'+
+ ',"request":{"prefix":"afbs","api":"reset","uuid": "e4ef5e66-xxxx","status":"processed"}'+
+ ',"response":{"uuid":"b028b883-8b47-4c6d-9c6e-e79b9e2b81b9"}'+
+ '}';
+
+ var fxResponse= '{"jtype":"AJB_reply",'+
+ '"request":{"prefix":"afbs","api":"reset","status":"empty","info":"AFB_SESSION_CLOSE Not a Valid Access Token"}'+
+ '}';
+
+ if (!scope.connected) res.status(401).send(fxResponse);
+ else {
+ res.send(okResponse);
+ scope.connected=false;
+ }
+ });
+
+
+}
+
+// Export Class
+module.exports = NewApi; \ No newline at end of file
diff --git a/afm-client/app/Backend/RestApis/_all.js b/afm-client/app/Backend/RestApis/_all.js
new file mode 100644
index 0000000..8fab76a
--- /dev/null
+++ b/afm-client/app/Backend/RestApis/_all.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// Include here every application APIs routes modules.
+function Initialise (handle) {
+
+ new require ('./TokenMockApi') (handle, config.APIBASE + 'token');
+ new require ('./PostMockApi') (handle, config.APIBASE + 'post');
+ new require ('./AfmMainMockApi') (handle, config.APIBASE + 'afm-main');
+}
+
+module.exports = Initialise;
+
diff --git a/afm-client/app/Backend/server.js b/afm-client/app/Backend/server.js
new file mode 100644
index 0000000..11c5486
--- /dev/null
+++ b/afm-client/app/Backend/server.js
@@ -0,0 +1,58 @@
+var config = require('../etc/_Config');
+var trace = require('../etc/_Trace');
+var RestAPI = require('./RestApis/_all');
+var fs = require('fs');
+
+var express = require('express');
+var session = require('express-session');
+var bodyParser = require('body-parser');
+var methodOverride = require('method-override');
+
+// instanciate express HTTP server
+var app = express();
+
+// chose dev or prod rootdir
+var staticdir = 'dist.dev';
+if (process.env.MODE) staticdir = process.env.MODE === 'prod' ? 'dist.prod' : 'dist.dev';
+else staticdir = config.MODE === 'prod' ? 'dist.prod' : 'dist.dev';
+
+var rootdir = __dirname + '/../../' + staticdir;
+if (!fs.existsSync(rootdir)) {
+ console.log("### HOOPS Rootdir not found rootdir=%s\n", rootdir);
+ process.exit();
+}
+
+// get all data/stuff of the body (POST) parameters
+app.use(bodyParser.json()); // parse application/json
+app.use(methodOverride('X-HTTP-Method-Override')); // override with the X-HTTP-Method-Override header in the request. simulate DELETE/PUT
+
+// This handle should contain enough for application logic
+var serverHandle = {
+ app : app, // Express server
+ config: config,
+ trace: config.DBG_LVL > 0 ? trace : function(){/*empty function */}
+};
+
+// set the static files location /public/img will be /img for users
+app.use(express.static(rootdir));
+
+// Load Mock APIs
+var apirest = new RestAPI(serverHandle);
+
+app.get(config.URLBASE, function (req, res) {
+ console.log ("Angular OPA %s", req.originalUrl);
+ res.sendfile(config.URLBASE +"index.html", {root: rootdir});
+});
+
+// rewrite requested URL to include Angular hashPrompt and set session flag for RestAPI
+app.get(config.URLBASE + '*', function(req, res) {
+ // Warning redirect should be under exact "/opa/#!page" or a redirect to home will be done
+ var redirect=config.URLBASE + '#!' + req.originalUrl.substring(config.URLBASE.length);
+ res.redirect(redirect);
+ console.log ("Redirect to: ", redirect);
+});
+
+
+// start app ===============================================
+app.listen(config.EXPRESS_PORT, config.EXPRESS_HOST);
+console.log('Server Listening http://%s:%d (rootdir=%s)', config.EXPRESS_HOST, config.EXPRESS_PORT, rootdir); \ No newline at end of file
diff --git a/afm-client/app/Frontend/app.js b/afm-client/app/Frontend/app.js
new file mode 100644
index 0000000..eac36e4
--- /dev/null
+++ b/afm-client/app/Frontend/app.js
@@ -0,0 +1,64 @@
+(function() {
+ 'use strict';
+
+ angular.module('@@APPNAME@@', [ // Warning: Appname should fit with gulpfile.js & index.html
+ 'ui.router',
+ 'ngAnimate',
+
+ //foundation
+ 'foundation',
+ 'foundation.dynamicRouting',
+ 'foundation.dynamicRouting.animations',
+
+ // external components
+ 'ui-notification',
+
+ // Application Components
+ 'AppConfig',
+ 'JQueryEmu',
+ 'DashboardModule',
+ 'SampleModule',
+ 'UploadFiles',
+ 'LinkButton',
+ 'TokenRefresh',
+ 'RangeSlider',
+ 'ModalNotification'
+ ])
+ .config(config)
+ .run(run)
+ ;
+
+ config.$inject = ['$urlRouterProvider', '$locationProvider'];
+
+ console.log ("***location=" + window.location + " search" + window.search);
+
+ function config($urlProvider, $locationProvider, AppConfig) {
+ $urlProvider.otherwise('/dashboard');
+
+ // https://docs.angularjs.org/error/$location/nobase
+ $locationProvider.html5Mode(true).hashPrefix('!');
+
+ }
+
+ function run() {
+ FastClick.attach(document.body);
+ }
+
+// Fondation-app.template is not included correctly by gulp
+// Include here missing templates from foundation-apps/dist/js/foundation-apps-templates.js
+angular.module('foundation').run(['$templateCache', function($templateCache) {
+ $templateCache.put('components/modal/modal.html',
+ '<div\n' +
+ ' class="modal-overlay"\n' +
+ ' ng-click="hideOverlay()">\n' +
+ ' <aside\n' +
+ ' class="modal"\n' +
+ ' ng-click="$event.stopPropagation();"\n' +
+ ' ng-transclude>\n' +
+ ' </aside>\n' +
+ '</div>\n' +
+ '');
+}]);
+
+console.log ("opa=@@APPNAME@@ Loaded");
+})();
diff --git a/afm-client/app/Frontend/etc/AppConfig.js b/afm-client/app/Frontend/etc/AppConfig.js
new file mode 100644
index 0000000..16a2c87
--- /dev/null
+++ b/afm-client/app/Frontend/etc/AppConfig.js
@@ -0,0 +1,55 @@
+(function () {
+ 'use strict';
+
+ // _all modules only reference dependencies
+ angular.module('AppConfig', [])
+
+ // Factory is a singleton and share its context within all instances.
+ .factory('AppConfig', function () {
+
+ // console.log ("URL="+ $location.url() + " Query=" + location.href+ " window=" + document.referrer);
+
+ var myConfig = {
+
+ paths: { // Warning paths should end with /
+ image : 'images/',
+ icons : 'images/icons/',
+ avatar: 'images/avatars/',
+ audio : 'images/audio/'
+ },
+
+ myapi: { // Warning paths should end with /
+ token : '/api/myplugin/xxxx'
+ },
+
+ session: { // Those data are updated by session service
+ create : '/api/token/create',
+ refresh : '/api/token/refresh',
+ check : '/api/token/check',
+ reset : '/api/token/reset',
+ ping : '/api/token/check',
+ initial : '123456789', // typical dev initial token
+ timeout : 3600, // timeout is updated client sessin context creation
+ pingrate: 60, // Ping rate to check if server is still alive
+ uuid : '', // uuid map with cookie or long term session access key
+ token : '' // will be returned from authentication
+ }
+ };
+
+ return myConfig;
+ })
+
+ // Factory is a singleton and share its context within all instances.
+ .factory('AppCall', function ($http, AppConfig) {
+ var myCalls = {
+ get : function(plugin, action, query, callback) {
+ query["token"] = AppConfig.session.token; // add token to provided query
+ $http.get('/api/' + plugin + '/' + action , {params: query}).then (callback, callback);
+ }
+
+ };
+ return myCalls;
+ });
+
+
+})(); \ No newline at end of file
diff --git a/afm-client/app/Frontend/etc/routes.js b/afm-client/app/Frontend/etc/routes.js
new file mode 100644
index 0000000..18d281a
--- /dev/null
+++ b/afm-client/app/Frontend/etc/routes.js
@@ -0,0 +1 @@
+var foundationRoutes = [{"name":"mysample","url":"/sample","controller":"SampleController as ctrl","animationIn":"slideInRight","path":"pages/Sample/Sample.html"},{"name":"Dashboard","url":"/dashboard","controller":"DashboardController as ctrl","animationIn":"slideInRight","path":"pages/Dashboard/Dashboard.html"},{"name":"dashboard","url":"/dashboard","controller":"DashboardController as ctrl","animationIn":"slideInRight","path":"pages/Home/Dashboard.html"}];
diff --git a/afm-client/app/Frontend/favicon.ico b/afm-client/app/Frontend/favicon.ico
new file mode 100644
index 0000000..eeb7ab7
--- /dev/null
+++ b/afm-client/app/Frontend/favicon.ico
Binary files differ
diff --git a/afm-client/app/Frontend/images/appli/isnotvalid.png b/afm-client/app/Frontend/images/appli/isnotvalid.png
new file mode 100644
index 0000000..057c215
--- /dev/null
+++ b/afm-client/app/Frontend/images/appli/isnotvalid.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/appli/istoobig.png b/afm-client/app/Frontend/images/appli/istoobig.png
new file mode 100644
index 0000000..5614073
--- /dev/null
+++ b/afm-client/app/Frontend/images/appli/istoobig.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/appli/upload-appli.png b/afm-client/app/Frontend/images/appli/upload-appli.png
new file mode 100644
index 0000000..a35fd3a
--- /dev/null
+++ b/afm-client/app/Frontend/images/appli/upload-appli.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/appli/w3c-widget.png b/afm-client/app/Frontend/images/appli/w3c-widget.png
new file mode 100644
index 0000000..74d3927
--- /dev/null
+++ b/afm-client/app/Frontend/images/appli/w3c-widget.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/audio/istoobig.png b/afm-client/app/Frontend/images/audio/istoobig.png
new file mode 100644
index 0000000..5614073
--- /dev/null
+++ b/afm-client/app/Frontend/images/audio/istoobig.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/audio/upload-music.png b/afm-client/app/Frontend/images/audio/upload-music.png
new file mode 100644
index 0000000..2006ef0
--- /dev/null
+++ b/afm-client/app/Frontend/images/audio/upload-music.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/avatars/istoobig.jpg b/afm-client/app/Frontend/images/avatars/istoobig.jpg
new file mode 100644
index 0000000..da0f255
--- /dev/null
+++ b/afm-client/app/Frontend/images/avatars/istoobig.jpg
Binary files differ
diff --git a/afm-client/app/Frontend/images/avatars/istoobig.png b/afm-client/app/Frontend/images/avatars/istoobig.png
new file mode 100644
index 0000000..5614073
--- /dev/null
+++ b/afm-client/app/Frontend/images/avatars/istoobig.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/avatars/tux-admin.png b/afm-client/app/Frontend/images/avatars/tux-admin.png
new file mode 100644
index 0000000..ee40d2a
--- /dev/null
+++ b/afm-client/app/Frontend/images/avatars/tux-admin.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/avatars/tux-bzh.png b/afm-client/app/Frontend/images/avatars/tux-bzh.png
new file mode 100644
index 0000000..16e001b
--- /dev/null
+++ b/afm-client/app/Frontend/images/avatars/tux-bzh.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/avatars/tux-visitor.png b/afm-client/app/Frontend/images/avatars/tux-visitor.png
new file mode 100644
index 0000000..bd491b0
--- /dev/null
+++ b/afm-client/app/Frontend/images/avatars/tux-visitor.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/icons/annex-ico.png b/afm-client/app/Frontend/images/icons/annex-ico.png
new file mode 100644
index 0000000..8e5aea9
--- /dev/null
+++ b/afm-client/app/Frontend/images/icons/annex-ico.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/icons/memorymatch-ico.png b/afm-client/app/Frontend/images/icons/memorymatch-ico.png
new file mode 100644
index 0000000..4041084
--- /dev/null
+++ b/afm-client/app/Frontend/images/icons/memorymatch-ico.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/icons/rabbit-ico.png b/afm-client/app/Frontend/images/icons/rabbit-ico.png
new file mode 100644
index 0000000..9dec018
--- /dev/null
+++ b/afm-client/app/Frontend/images/icons/rabbit-ico.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/icons/w3c-ico.png b/afm-client/app/Frontend/images/icons/w3c-ico.png
new file mode 100644
index 0000000..4e97980
--- /dev/null
+++ b/afm-client/app/Frontend/images/icons/w3c-ico.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/fb-logo.png b/afm-client/app/Frontend/images/login/fb-logo.png
new file mode 100644
index 0000000..fcf7847
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/fb-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/gg-logo.png b/afm-client/app/Frontend/images/login/gg-logo.png
new file mode 100644
index 0000000..0c372eb
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/gg-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/gh-logo.png b/afm-client/app/Frontend/images/login/gh-logo.png
new file mode 100644
index 0000000..ff856fc
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/gh-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/iot-logo.png b/afm-client/app/Frontend/images/login/iot-logo.png
new file mode 100644
index 0000000..ca594d7
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/iot-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/lk-logo.png b/afm-client/app/Frontend/images/login/lk-logo.png
new file mode 100644
index 0000000..d9bc51f
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/lk-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/ms-logo.png b/afm-client/app/Frontend/images/login/ms-logo.png
new file mode 100644
index 0000000..d4f23eb
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/ms-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/og-logo.png b/afm-client/app/Frontend/images/login/og-logo.png
new file mode 100644
index 0000000..a6f6e9a
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/og-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/pp-logo.png b/afm-client/app/Frontend/images/login/pp-logo.png
new file mode 100644
index 0000000..dbb8866
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/pp-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/login/yh-logo.png b/afm-client/app/Frontend/images/login/yh-logo.png
new file mode 100644
index 0000000..6ab90cf
--- /dev/null
+++ b/afm-client/app/Frontend/images/login/yh-logo.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/logo/logo_iot_bzh.svg b/afm-client/app/Frontend/images/logo/logo_iot_bzh.svg
new file mode 100644
index 0000000..6e60c95
--- /dev/null
+++ b/afm-client/app/Frontend/images/logo/logo_iot_bzh.svg
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="597.39423"
+ height="162.54224"
+ id="svg4035"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="New document 5">
+ <defs
+ id="defs4037">
+ <filter
+ color-interpolation-filters="sRGB"
+ id="filter4000"
+ inkscape:label="Drop Shadow">
+ <feFlood
+ id="feFlood4002"
+ flood-opacity="0.475"
+ flood-color="rgb(0,0,0)"
+ result="flood" />
+ <feComposite
+ id="feComposite4004"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur4006"
+ stdDeviation="5"
+ result="blur" />
+ <feOffset
+ id="feOffset4008"
+ dx="8"
+ dy="8"
+ result="offset" />
+ <feComposite
+ id="feComposite4010"
+ in2="offset"
+ in="SourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.98994949"
+ inkscape:cx="339.36637"
+ inkscape:cy="3.4393101"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1108"
+ inkscape:window-height="862"
+ inkscape:window-x="321"
+ inkscape:window-y="159"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata4040">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-49.874326,-433.94821)">
+ <g
+ id="g3091"
+ transform="translate(62.857151,24.927697)"
+ inkscape:export-filename="/home/sdx/Pictures/Logo/logo_iot_bzh_100dpi.png"
+ inkscape:export-xdpi="100.22011"
+ inkscape:export-ydpi="100.22011"
+ style="display:inline;filter:url(#filter4000)">
+ <text
+ sodipodi:linespacing="125%"
+ id="text3557-5-3-7-0-7-3"
+ y="519.50671"
+ x="27.886671"
+ style="font-size:97.09867096px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Sans"
+ xml:space="preserve"><tspan
+ style="font-size:97.09867096px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:FreeEuro;-inkscape-font-specification:FreeEuro Bold"
+ y="519.50671"
+ x="27.886671"
+ id="tspan3559-5-4-1-5-0-6"
+ sodipodi:role="line">IOT</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ d="m 296.73007,473.23356 c 28.21686,16.29102 28.75566,58.73779 0.99693,78.53831 -7.67688,5.47598 -8.77935,4.91028 -1.99529,-1.0238 17.47377,-15.28453 17.98492,-42.17775 1.08522,-57.09786 l -3.91266,-3.45435 0.72312,-3.71053 c 0.39771,-2.04076 0.5997,-5.73115 0.44885,-8.20083 -0.33876,-5.54623 0.15803,-6.49185 2.65383,-5.05094 z m -64.76568,11.40332 c 7.06047,-7.74198 18.64659,-14.16089 29.04027,-16.08874 l 6.87489,-1.27521 0.87404,2.89709 c 0.4807,1.59343 0.67439,5.2245 0.43037,8.06906 l -0.44364,5.17195 -6.13887,1.6918 c -10.91241,3.00731 -20.4022,10.85909 -25.4533,21.05979 l -2.41633,4.87984 -2.74281,-0.41238 c -5.14252,-0.77316 -12.72985,-3.97645 -12.79123,-5.40033 -0.092,-2.13451 8.34659,-15.74625 12.76661,-20.59287 z m 33.20546,36.39493 c -28.21687,16.29101 -65.24624,-4.46574 -68.51461,-38.40577 -0.9039,-9.38637 0.13723,-10.0583 1.88428,-1.21608 4.49989,22.77499 27.53453,36.66428 48.90556,29.48876 l 4.94788,-1.66128 2.85184,2.48149 c 1.56852,1.36481 4.66349,3.38493 6.87772,4.48914 4.97257,2.47973 5.54308,3.38282 3.04733,4.82374 z m 22.50729,-61.79039 c 3.17451,9.98553 2.94038,23.22889 -0.58688,33.19399 l -2.33309,6.59143 -2.94597,-0.69161 c -1.6203,-0.38041 -4.86173,-2.02821 -7.2032,-3.6618 l -4.25721,-2.97018 1.60429,-6.16234 c 2.85178,-10.95404 0.79685,-23.09833 -5.51167,-32.57307 l -3.01788,-4.53253 1.72854,-2.16916 c 3.24083,-4.06698 9.80863,-9.03614 11.07242,-8.37738 1.89457,0.98756 9.46336,15.1015 11.45065,21.35265 z m -48.80223,10.31437 c 0,-32.58201 36.49058,-54.27201 67.51771,-40.1325 8.58077,3.9104 8.6421,5.148 0.11108,2.23988 -21.97368,-7.49048 -45.51946,5.51348 -49.99082,27.6091 l -1.03521,5.11561 -3.57498,1.22902 c -1.96621,0.67596 -5.26316,2.34622 -7.32655,3.71171 -4.63379,3.06649 -5.70115,3.10904 -5.70115,0.22718 z m 42.25842,50.3871 c -10.23499,-2.24356 -21.58699,-9.06801 -28.45341,-17.10525 l -4.5418,-5.31622 2.07194,-2.20549 c 1.13957,-1.21302 4.18733,-3.19628 6.77282,-4.40726 l 4.70085,-2.20176 4.53458,4.47053 c 8.06061,7.94674 19.60535,12.23927 30.96496,11.51329 l 5.43422,-0.34731 1.01427,2.58154 c 1.90169,4.84014 2.92124,13.01261 1.71883,13.77769 -1.80254,1.14695 -17.80995,0.64475 -24.21726,-0.75976 z"
+ style="fill:#5a2ca0;display:inline"
+ id="path3415-4-2-2-5-0-3-7-4-4-1-5" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3557-5-3-7-46-7-3-7"
+ y="519.50671"
+ x="317.95816"
+ style="font-size:97.09867096px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Sans"
+ xml:space="preserve"><tspan
+ style="font-size:97.09867096px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:FreeEuro;-inkscape-font-specification:FreeEuro Bold"
+ y="519.50671"
+ x="317.95816"
+ id="tspan3559-5-4-1-90-0-2-9"
+ sodipodi:role="line">BZH</tspan></text>
+ </g>
+ <rect
+ style="fill:none;stroke:none;display:inline"
+ id="rect3098"
+ width="577.14288"
+ height="162.54224"
+ x="59.999989"
+ y="433.94821"
+ inkscape:export-filename="/home/sdx/Pictures/Logo/logo_iot_bzh_100dpi.png"
+ inkscape:export-xdpi="100.22011"
+ inkscape:export-ydpi="100.22011" />
+ </g>
+</svg>
diff --git a/afm-client/app/Frontend/images/logo/logo_iot_bzhx350.png b/afm-client/app/Frontend/images/logo/logo_iot_bzhx350.png
new file mode 100644
index 0000000..2c3b2ae
--- /dev/null
+++ b/afm-client/app/Frontend/images/logo/logo_iot_bzhx350.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/logo/tampon-iot-bzhx450.png b/afm-client/app/Frontend/images/logo/tampon-iot-bzhx450.png
new file mode 100644
index 0000000..f365e19
--- /dev/null
+++ b/afm-client/app/Frontend/images/logo/tampon-iot-bzhx450.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/logo/triskel_iot_bzh.png b/afm-client/app/Frontend/images/logo/triskel_iot_bzh.png
new file mode 100644
index 0000000..832dc1f
--- /dev/null
+++ b/afm-client/app/Frontend/images/logo/triskel_iot_bzh.png
Binary files differ
diff --git a/afm-client/app/Frontend/images/logo/triskel_iot_bzh.svg b/afm-client/app/Frontend/images/logo/triskel_iot_bzh.svg
new file mode 100644
index 0000000..096f424
--- /dev/null
+++ b/afm-client/app/Frontend/images/logo/triskel_iot_bzh.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="205.71426"
+ height="197.14285"
+ id="svg4199"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="triskel_iot_bzh.svg">
+ <defs
+ id="defs4201">
+ <filter
+ color-interpolation-filters="sRGB"
+ id="filter4111"
+ inkscape:label="Drop Shadow">
+ <feFlood
+ id="feFlood4113"
+ flood-opacity="0.475"
+ flood-color="rgb(0,0,0)"
+ result="flood" />
+ <feComposite
+ id="feComposite4115"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur4117"
+ stdDeviation="5"
+ result="blur" />
+ <feOffset
+ id="feOffset4119"
+ dx="8"
+ dy="8"
+ result="offset" />
+ <feComposite
+ id="feComposite4121"
+ in2="offset"
+ in="SourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35"
+ inkscape:cx="46.428557"
+ inkscape:cy="178.57144"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="500"
+ inkscape:window-height="435"
+ inkscape:window-x="1087"
+ inkscape:window-y="400"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata4204">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-328.57144,-513.79077)">
+ <path
+ id="path3415-4-2-2-5-0-3-7-4-4-1-9"
+ style="fill:#5a2ca0;display:inline;filter:url(#filter4111)"
+ d="m 470.88567,595.30412 c 28.21686,16.29102 28.75566,58.73778 0.99693,78.5383 -7.67688,5.47598 -8.77935,4.91028 -1.99529,-1.0238 17.47377,-15.28453 17.98492,-42.17774 1.08522,-57.09785 l -3.91266,-3.45435 0.72312,-3.71053 c 0.39771,-2.04076 0.5997,-5.73115 0.44885,-8.20083 -0.33876,-5.54623 0.15803,-6.49185 2.65383,-5.05094 z m -64.76568,11.40332 c 7.06047,-7.74198 18.64659,-14.16089 29.04027,-16.08874 l 6.87489,-1.27521 0.87404,2.89709 c 0.4807,1.59343 0.67439,5.2245 0.43037,8.06906 l -0.44364,5.17195 -6.13887,1.6918 c -10.91241,3.00731 -20.4022,10.85909 -25.4533,21.05979 l -2.41633,4.87984 -2.74281,-0.41238 c -5.14252,-0.77316 -12.72985,-3.97645 -12.79123,-5.40033 -0.092,-2.13451 8.34659,-15.74625 12.76661,-20.59287 z m 33.20546,36.39493 c -28.21687,16.291 -65.24624,-4.46574 -68.51461,-38.40577 -0.9039,-9.38637 0.13723,-10.0583 1.88428,-1.21608 4.49989,22.77499 27.53453,36.66428 48.90556,29.48876 l 4.94788,-1.66128 2.85184,2.48149 c 1.56852,1.36481 4.66349,3.38493 6.87772,4.48914 4.97257,2.47973 5.54308,3.38282 3.04733,4.82374 z m 22.50729,-61.7904 c 3.17451,9.98554 2.94038,23.2289 -0.58688,33.194 l -2.33309,6.59143 -2.94597,-0.69161 c -1.6203,-0.38041 -4.86173,-2.02821 -7.2032,-3.6618 l -4.25721,-2.97018 1.60429,-6.16234 c 2.85178,-10.95404 0.79685,-23.09834 -5.51167,-32.57308 l -3.01788,-4.53253 1.72854,-2.16916 c 3.24083,-4.06698 9.80863,-9.03614 11.07242,-8.37738 1.89457,0.98756 9.46336,15.1015 11.45065,21.35265 z m -48.80223,10.31438 c 0,-32.58202 36.49058,-54.27202 67.51771,-40.13251 8.58077,3.9104 8.6421,5.148 0.11108,2.23988 -21.97368,-7.49048 -45.51946,5.51348 -49.99082,27.6091 l -1.03521,5.11562 -3.57498,1.22902 c -1.96621,0.67596 -5.26316,2.34622 -7.32655,3.71171 -4.63379,3.06649 -5.70115,3.10904 -5.70115,0.22718 z m 42.25842,50.3871 c -10.23499,-2.24356 -21.58699,-9.06801 -28.45341,-17.10525 l -4.5418,-5.31622 2.07194,-2.20549 c 1.13957,-1.21302 4.18733,-3.19628 6.77282,-4.40726 l 4.70085,-2.20176 4.53458,4.47053 c 8.06061,7.94674 19.60535,12.23927 30.96496,11.51329 l 5.43422,-0.34731 1.01427,2.58154 c 1.90169,4.84014 2.92124,13.01261 1.71883,13.77769 -1.80254,1.14695 -17.80995,0.64475 -24.21726,-0.75976 z"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sdx/Pictures/Logo/triskel_iot_bzh_300dpi.png"
+ inkscape:export-xdpi="300"
+ inkscape:export-ydpi="300" />
+ <rect
+ style="fill:none;stroke:none;display:inline"
+ id="rect4179"
+ width="205.71426"
+ height="197.14285"
+ x="328.57144"
+ y="513.79077"
+ inkscape:export-filename="/home/sdx/Pictures/Logo/triskel_iot_bzh_300dpi.png"
+ inkscape:export-xdpi="300"
+ inkscape:export-ydpi="300" />
+ </g>
+</svg>
diff --git a/afm-client/app/Frontend/images/logo/triskel_iot_bzhx250.png b/afm-client/app/Frontend/images/logo/triskel_iot_bzhx250.png
new file mode 100644
index 0000000..f4e41ae
--- /dev/null
+++ b/afm-client/app/Frontend/images/logo/triskel_iot_bzhx250.png
Binary files differ
diff --git a/afm-client/app/Frontend/index.html b/afm-client/app/Frontend/index.html
new file mode 100644
index 0000000..0d55267
--- /dev/null
+++ b/afm-client/app/Frontend/index.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!--[if lt IE 7]> <html lang="en" ng-app="@@APPNAME@@" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]> <html lang="en" ng-app="@@APPNAME@@" class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]> <html lang="en" ng-app="@@APPNAME@@" class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html lang="en" ng-app="@@APPNAME@@" class="no-js"> <!--<![endif]-->
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Simple Sample Application</title>
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <!-- bower:css -->
+ <!-- endinject -->
+ <!--vendor:css -->
+ <!-- endinject -->
+ <!-- appli:css -->
+ <!-- endinject -->
+ <!-- inject:css -->
+ <!-- endinject -->
+ <base href="@@URLBASE@@"> <!-- https://docs.angularjs.org/error/$location/nobase -->
+
+</head>
+<body>
+<!--[if lt IE 7]>
+<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
+<![endif]-->
+<div ui-view></div>
+<!-- bower:js -->
+<!-- endinject -->
+<!-- inject:js -->
+<!-- endinject -->
+
+<!-- Generic Foundation Modal Template -->
+<script id="components/modal/modal.html" type="text/ng-template">
+ <div class="modal-overlay" ng-click="hideOverlay()">
+ <aside class="modal" ng-click="$event.stopPropagation();" ng-transclude></aside>
+ </div>
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/afm-client/app/Frontend/pages/Dashboard/Dashboard.html b/afm-client/app/Frontend/pages/Dashboard/Dashboard.html
new file mode 100644
index 0000000..c393271
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Dashboard/Dashboard.html
@@ -0,0 +1,33 @@
+<!-- Foundation Annotations generate tmp/route.js -->
+---
+name: Dashboard
+url: /dashboard
+controller: DashboardController as ctrl
+animationIn: slideInRight
+---
+
+<h3>
+<img class="logo" src="images/logo/triskel_iot_bzhx250.png" alt="IoT.bzh Logo" style="height:150px;">
+ Application Framework Manager Simple Client
+</h3>
+
+
+
+<div class="button-box box-content ">
+ <appli-button ng-repeat="appliID in ctrl.appliIDs" store="ctrl.appliStore" handle="{{appliID}}" callback="ctrl.AppliCB"> </appli-button>
+ <upload-appli class="ibz-right" label="Upload" icon="fi-upload" posturl="/api/post/upload-appli" callback="ctrl.FileUploaded" title="Upload AGL App"></upload-appli>
+</div>
+
+
+<div class="message-box box-content vertical grid-frame">
+ <div class="response">
+ <span class="grid-content noscroll req {{ctrl.status}} ">req= {{ctrl.request}}</span>
+ <span class="grid-content noscroll res {{ctrl.status}} ">res= {{ctrl.response}}</span>
+ <span class="grid-content noscroll status {{ctrl.status}}">status= {{ctrl.errcode}}</span>
+ </div>
+</div>
+
+
+<link-button href="sample" icon="fi-home" label="sample"></link-button>
+<token-refresh autolog="true" callback="ctrl.AutoStart"></token-refresh>
+
diff --git a/afm-client/app/Frontend/pages/Dashboard/DashboardModule.js b/afm-client/app/Frontend/pages/Dashboard/DashboardModule.js
new file mode 100644
index 0000000..c075372
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Dashboard/DashboardModule.js
@@ -0,0 +1,75 @@
+(function() {
+'use strict';
+
+// WARNING: make sure than app/frontend/services/AppConfig.js match your server
+
+// list all rependencies within the page + controler if needed
+angular.module('DashboardModule', ['SubmitButton', 'TokenRefresh', 'AppliButton'])
+
+ .controller('DashboardController', function (AppCall, Notification) {
+ var scope = this; // I hate JavaScript
+ scope.uuid ="none";
+ scope.token ="none";
+ scope.session="none";
+ scope.status ="err-no";
+ scope.appliIDs =[]; // array to hold applications ID
+ scope.appliStore={}; // array to hold applications json description
+
+ scope.AppliCB = function (appliID) {
+ console.log ("Application Clicked ID=[%s]", appliID);
+
+ };
+
+ scope.AppliCB = function(appliID, action, response) {
+ // Action is done within Widget Controller only update debug UI zone
+ scope.request = action;
+ scope.errcode = response.status;
+ if (response.data) scope.response = response.data;
+ };
+
+ scope.GetRunnables = function() {
+ console.log ("Dashboard GetRunnables");
+
+ AppCall.get ("afm-main", "runnables", {/*query*/}, function(response) {
+
+ // update debug UI zone
+ scope.request = "/api/afm-main/runnable";
+ scope.response = response.data;
+ scope.errcode = response.status;
+
+ if (response.status !== 200) {
+ console.log ("Hoop GetRunnable failed");
+ return;
+ }
+
+ // Check this is a valid response from Binder
+ if (response.data.request.jtype !== "AJB_reply" && response.data.request.api !== "runnables") {
+ Notification.error ({message: "Invalid Respond to /opa/afm-main/runnable response.data="+response.data, delay: 5000});
+ return;
+ }
+
+ // loop on runnable application to prepare for display
+ var appliIDs=[];
+ for (var idx=0; idx < response.data.response.length; idx ++) {
+ appliIDs[idx] = response.data.response [idx].id;
+ scope.appliStore [response.data.response [idx].id] = response.data.response [idx];
+ }
+ scope.appliIDs = appliIDs; // avoid partial update to limit UI refresh
+
+ });
+ };
+
+ scope.FileUploaded = function (status) {
+ console.log ("file Uploaded");
+ scope.GetRunnables();
+ };
+
+ scope.AutoStart = function () {
+ console.log ("AutoStart requesting Apps list");
+ scope.GetRunnables();
+ };
+
+ });
+
+console.log ("Dashboard Controller Loaded");
+})(); \ No newline at end of file
diff --git a/afm-client/app/Frontend/pages/Dashboard/DashboardModule.scss b/afm-client/app/Frontend/pages/Dashboard/DashboardModule.scss
new file mode 100644
index 0000000..8bf04a1
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Dashboard/DashboardModule.scss
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+@import "app/ibz-mixins";
+
+$COLOR_SUCCESS: green;
+$COLOR_FAIL: red;
+
+token-refresh {
+ display: block;
+ float: right;
+ margin: .5rem 1rem 0 0;
+}
+
+.button-box {
+ height : 4.5rem;
+
+ .session-button {
+ float: left;
+ width: 5rem;
+ };
+
+ .response > span{
+ display: block;
+ margin: .3rem .5rem .3rem .5rem;
+ }
+
+ .fail {
+ color:$COLOR_FAIL;
+ border: 1px solid darken($COLOR_FAIL,10%);
+ }
+ .success {
+ color:$COLOR_SUCCESS;
+ border: 1px solid darken($COLOR_SUCCESS,10%);
+ }
+
+};
+
+.message-box {
+ height : auto;
+ width: 100%;
+ font-size: .75rem;
+
+ .response {
+ .err-no { color:grey; }
+ .res.err-ok { color: blue; }
+ .req.err-ok { color:blueviolet; }
+ .status.err-ok { color:green; }
+ .status.err-fx { color:red; }
+ }
+
+
+};
+
+
+
diff --git a/afm-client/app/Frontend/pages/Home/Dashboard.html b/afm-client/app/Frontend/pages/Home/Dashboard.html
new file mode 100644
index 0000000..036148a
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Home/Dashboard.html
@@ -0,0 +1,40 @@
+<!-- Foundation Annotations generate tmp/route.js -->
+---
+name: dashboard
+url: /dashboard
+controller: DashboardController as ctrl
+animationIn: slideInRight
+---
+
+<h3>
+<img class="logo" src="images/logo/triskel_iot_bzhx250.png" alt="IoT.bzh Logo" style="height:150px;">
+ Application Framework Manager Simple Client
+</h3>
+
+
+
+<div class="button-box box-content ">
+ <appli-button ng-repeat="appliID in ctrl.appliIDs" store="ctrl.appliStore" handle={{appliID}} callback="ctrl.AppliCB"> </appli-button>
+ <upload-appli class="ibz-right" label="Upload" icon="fi-upload" posturl="/api/post/upload-appli" callback="ctrl.FileUploaded" title="Upload AGL App"></upload-appli>
+</div>
+
+
+<div class="button-box box-content ">
+
+ <submit-button class="session-button {{ctrl.APIcheck}}" icon="fi-play-circle" label="Start" clicked="ctrl.StartApp" ></submit-button>
+ <submit-button class="session-button {{ctrl.APIrefresh}}" icon="fi-x-circle" label="Stop" clicked="ctrl.StopApp" ></submit-button>
+ <submit-button class="session-button {{ctrl.APIreset}}" icon="fi-x" label="Remove" clicked="ctrl.RemoveApp" ></submit-button>
+
+</div>
+<div class="message-box box-content vertical grid-frame">
+ <div class="response">
+ <span class="grid-content noscroll req {{ctrl.status}} ">req= {{ctrl.request}}</span>
+ <span class="grid-content noscroll res {{ctrl.status}} ">res= {{ctrl.response}}</span>
+ <span class="grid-content noscroll status {{ctrl.status}}">status= {{ctrl.errcode}}</span>
+ </div>
+</div>
+
+
+<link-button href="sample" icon="fi-home" label="sample"></link-button>
+<token-refresh autolog="true" callback="ctrl.AutoStart"></token-refresh>
+
diff --git a/afm-client/app/Frontend/pages/Home/DashboardModule.js b/afm-client/app/Frontend/pages/Home/DashboardModule.js
new file mode 100644
index 0000000..f843df1
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Home/DashboardModule.js
@@ -0,0 +1,118 @@
+(function() {
+'use strict';
+
+// WARNING: make sure than app/frontend/services/AppConfig.js match your server
+
+// list all rependencies within the page + controler if needed
+angular.module('DashboardModule', ['SubmitButton', 'TokenRefresh', 'AppliButton'])
+
+ .controller('DashboardController', function ($http, AppConfig, Notification) {
+ var scope = this; // I hate JavaScript
+ scope.uuid ="none";
+ scope.token ="none";
+ scope.session="none";
+ scope.status ="err-no";
+ scope.appliIDs =[]; // array to hold applications ID
+ scope.appliStore={}; // array to hold applications json description
+
+ scope.ProcessResponse= function(data, errcode, headers, config) {
+ var apiname= 'API'+ data.request.api.replace('-','_');
+ scope.status = "err-ok";
+ scope.errcode= errcode;
+ scope.request = data.request;
+ scope.response = data.response;
+
+ // if token was refresh let's update AppConfig
+ if (data.request.token) AppConfig.session.token = data.request.token;
+ if (data.request.uuid) AppConfig.session.uuid = data.request.uuid;
+ if (data.request.timeout) AppConfig.session.timeout = data.request.timeout;
+
+ // Make sure we clean everything when Open/Close is called
+ if (apiname === "APIcreate" || apiname === "APIreset") {
+ scope.APIreset ='';
+ scope.APIcreate ='';
+ scope.APIrefresh='';
+ scope.APIcheck ='';
+ }
+ scope[apiname]="success";
+
+ // If we have a new token let's update it
+ if (data.request.token) scope.token=data.request.token;
+
+ console.log ("OK: "+ JSON.stringify(data));
+ };
+
+ scope.ProcessError= function(data, errcode, headers, config) {
+ var apiname= 'API'+data.request.api.replace('-','_');
+ scope.status = "err-fx";
+ scope.errcode = errcode;
+ scope.request = data.request;
+ scope.response = "";
+ scope[apiname]="fail";
+
+ console.log ("FX: "+ JSON.stringify(data));
+ };
+
+ scope.OpenSession = function() {
+ console.log ("OpenSession");
+ var handler = $http.get(AppConfig.session.create + '?token='+AppConfig.session.initial);
+
+ handler.success(scope.ProcessResponse);
+ handler.error(scope.ProcessError);
+ };
+
+ scope.CheckSession = function() {
+ console.log ("CloseSession");
+ var handler = $http.get(AppConfig.session.check + '?token='+AppConfig.session.token);
+
+ handler.success(scope.ProcessResponse);
+ handler.error(scope.ProcessError);
+ };
+
+ scope.RefreshSession = function() {
+ console.log ("RefreshSession");
+ var handler = $http.get(AppConfig.session.refresh + '?token='+AppConfig.session.token);
+
+ handler.success(scope.ProcessResponse);
+ handler.error(scope.ProcessError);
+ };
+
+ scope.ResetSession = function() {
+ console.log ("ResetSession");
+ var handler = $http.get(AppConfig.session.reset + '?token='+AppConfig.session.token);
+
+ handler.success(scope.ProcessResponse);
+ handler.error(scope.ProcessError);
+ };
+
+ scope.AppliCB = function (appliID) {
+ console.log ("Application Clicked ID=[%s]", appliID);
+
+ };
+
+ scope.AutoStart = function () {
+ console.log ("AutoStart requesting Apps list");
+ var handler = $http.get('/api/afm-main/runnables' + '?token='+AppConfig.session.token);
+ handler.success(function(result) {
+
+ // Check this is a valid response from Binder
+ if (result.request.jtype !== "AJB_reply" && result.request.api !== "runnables") {
+ Notification.error ({message: "Invalid Respond to /opa/afm-main/runnable result="+result, delay: 5000});
+ return;
+ }
+
+ // loop on runnable application to prepare for display
+ var appliIDs=[];
+ for (var idx=0; idx < result.response.length; idx ++) {
+ appliIDs[idx] = result.response [idx].id;
+ scope.appliStore [result.response [idx].id] = result.response [idx];
+ }
+ scope.appliIDs = appliIDs; // avoid partial update to limit UI refresh
+
+ });
+ };
+
+ });
+
+console.log ("Dashboard Controller Loaded");
+})(); \ No newline at end of file
diff --git a/afm-client/app/Frontend/pages/Home/DashboardModule.scss b/afm-client/app/Frontend/pages/Home/DashboardModule.scss
new file mode 100644
index 0000000..8bf04a1
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Home/DashboardModule.scss
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+@import "app/ibz-mixins";
+
+$COLOR_SUCCESS: green;
+$COLOR_FAIL: red;
+
+token-refresh {
+ display: block;
+ float: right;
+ margin: .5rem 1rem 0 0;
+}
+
+.button-box {
+ height : 4.5rem;
+
+ .session-button {
+ float: left;
+ width: 5rem;
+ };
+
+ .response > span{
+ display: block;
+ margin: .3rem .5rem .3rem .5rem;
+ }
+
+ .fail {
+ color:$COLOR_FAIL;
+ border: 1px solid darken($COLOR_FAIL,10%);
+ }
+ .success {
+ color:$COLOR_SUCCESS;
+ border: 1px solid darken($COLOR_SUCCESS,10%);
+ }
+
+};
+
+.message-box {
+ height : auto;
+ width: 100%;
+ font-size: .75rem;
+
+ .response {
+ .err-no { color:grey; }
+ .res.err-ok { color: blue; }
+ .req.err-ok { color:blueviolet; }
+ .status.err-ok { color:green; }
+ .status.err-fx { color:red; }
+ }
+
+
+};
+
+
+
diff --git a/afm-client/app/Frontend/pages/Sample/Sample.html b/afm-client/app/Frontend/pages/Sample/Sample.html
new file mode 100644
index 0000000..03a4558
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Sample/Sample.html
@@ -0,0 +1,35 @@
+<!-- Foundation Annotations generate tmp/route.js -->
+---
+name: mysample
+url: /sample
+controller: SampleController as ctrl
+animationIn: slideInRight
+---
+
+<h1><img class="logo" src="images/logo/triskel_iot_bzhx250.png" alt="IoT.bzh Logo" style="height:150px;">
+ Post File Upload
+</h1>
+
+<div class="sample-box box-content">
+
+ <!-- Usage: upload-xxxxx
+ name = [xxxxxx] is use a field label for xform input field. Should match with server side
+ category = [avatar] should match to a valid directory of thumbnail within AppConfig.path
+ thumbnail= [tux-bzh.png] a valid image within AppConfig.paths.[category]
+ istoobig = [istoobig.png] used image from AppConfig.paths.[category] when file is oversized
+ maxsize = [xxx] maximum size in KB [default max depend on upload-type]
+ accept = [image] acceptable accept for upload
+ -->
+ <upload-image name="avatar" category="avatar" thumbnail="tux-visitor.png" maxsize="100"
+ posturl="/api/post/upload-image" callback="ctrl.FileUploaded" accept="image" title="Change your Avatar">
+ </upload-image>
+
+ <!-- Warning: name=xxx should match with what server expect [used as xform input name -->
+ <upload-audio name="music" posturl="/api/post/upload-music" callback="ctrl.FileUploaded" title="Upload your Music"></upload-audio>
+
+ <!-- Warning: name=xxx should match with what server expect [used as xform input name -->
+ <upload-appli name="appli" posturl="/api/post/upload-appli" callback="ctrl.FileUploaded" title="Upload AGL App"></upload-appli>
+
+</div>
+
+<link-button href="home" icon="fi-home" label="home"></link-button>
diff --git a/afm-client/app/Frontend/pages/Sample/SampleModule.js b/afm-client/app/Frontend/pages/Sample/SampleModule.js
new file mode 100644
index 0000000..8ae82ea
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Sample/SampleModule.js
@@ -0,0 +1,18 @@
+(function() {
+'use strict';
+
+// list all rependencies within the page + controler if needed
+angular.module('SampleModule', ['SubmitButton','UploadFiles'])
+
+ .controller('SampleController', function ($http) {
+ var scope = this; // I hate JavaScript
+
+ console.log ("sample Init");
+
+ scope.FileUploaded = function (response) {
+ console.log ("FileUploaded response=%s", JSON.stringify(response));
+ };
+ });
+
+console.log ("SampleControler Loaded");
+})(); \ No newline at end of file
diff --git a/afm-client/app/Frontend/pages/Sample/SampleModule.scss b/afm-client/app/Frontend/pages/Sample/SampleModule.scss
new file mode 100644
index 0000000..7654424
--- /dev/null
+++ b/afm-client/app/Frontend/pages/Sample/SampleModule.scss
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+@import "app/ibz-mixins";
+
+
+.sample-box {
+ display: block;
+ height : 4.5rem;
+
+ .sample-button {
+ float: right;
+ width: 5rem;
+ };
+
+ .muted-on-on,.muted-off-off{
+ background: blueviolet;
+ };
+
+ .muted-error{
+ background: red;
+ };
+};
+
+
+
diff --git a/afm-client/app/Frontend/services/JQueryEmu.js b/afm-client/app/Frontend/services/JQueryEmu.js
new file mode 100644
index 0000000..6d6e338
--- /dev/null
+++ b/afm-client/app/Frontend/services/JQueryEmu.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Usage:
+ *
+ * // mouse event probably point on icon and not on button div
+ * ... ng-click="LockChannel($event)
+ var target= angular.element(event.currentTarget);
+ var button= JQemu.FindInParent (target, 'div');
+ *
+ */
+
+
+(function () {
+ 'use strict';
+
+
+
+ // _all modules only reference dependencies
+ angular.module('JQueryEmu', [])
+
+ // Factory is a singleton and share its context within all instances.
+ .factory('JQemu', function () {
+
+ var FindInParent = function (element, selector) {
+ var parent = element;
+ var search = selector.toUpperCase();
+ while (parent[0]) {
+ if (search === parent[0].tagName) {
+ return parent;
+ } // HTMLDivElement properties
+ parent = parent.parent();
+ }
+ };
+
+ var FindByTag= function (element, tag, selector) {
+ var search = selector.toLowerCase();
+ var type = tag.toLowerCase()+ "Name";
+ var children = element.children();
+ while (children[0]) {
+ if (search === children[0][type]) {
+ return children;
+ } // HTMLDivElement properties
+ children = children.next();
+ }
+ };
+
+ var FindByClass= function (element, selector) {
+ var search = selector.toLowerCase();
+ var children = element.children();
+ while (children[0]) {
+ if (children.hasClass(search)) {
+ return children;
+ } // HTMLDivElement properties
+ children = children.next();
+ }
+ };
+
+ var myMethods = {
+ FindInParent: FindInParent,
+ FindByTag: FindByTag,
+ FindByClass: FindByClass
+ };
+
+ return myMethods;
+ });
+
+})(); \ No newline at end of file
diff --git a/afm-client/app/Frontend/styles/README.md b/afm-client/app/Frontend/styles/README.md
new file mode 100644
index 0000000..dc50ced
--- /dev/null
+++ b/afm-client/app/Frontend/styles/README.md
@@ -0,0 +1,28 @@
+WARNING note about global style dir
+-------------------------------------
+
+ - styles placed in Frontend/styles is global and will be posted in dist.prod/styles
+ - styles defined within widget or page directory will be place in dist.prof/opa/styles
+
+This model allows to share global styles by multiple applications.
+
+To change this behaviour just rename styles directory on something else [eg: appstyles]
+
+
+ |---- /Frontend
+ | |
+ | |---- /styles
+ | | |
+ | | |---- _settings.scss
+ | | |---- app.scss
+ | |
+ | |---- /Widgets
+ | | |
+ | | |--- widget.js
+ | | |--- widget.sccs
+ | |
+ | |-----/Pages
+ | |--- page-partial.html
+ | |--- page-cntrl.js
+ | |--- page-style.scss
+ |
diff --git a/afm-client/app/Frontend/styles/app/_ibz-mixins.scss b/afm-client/app/Frontend/styles/app/_ibz-mixins.scss
new file mode 100644
index 0000000..ed9dba8
--- /dev/null
+++ b/afm-client/app/Frontend/styles/app/_ibz-mixins.scss
@@ -0,0 +1,52 @@
+/*
+ Every SCSS files will be injected into main HTML page
+*/
+%shadow-transition {
+ transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+ border-radius: 5px;
+}
+
+@mixin ibz-box-content {
+ border: 2px solid gainsboro !important;
+ border-radius: 5px;
+
+ margin-left: auto;
+ margin-right: auto;
+ min-width: 98% !important;
+ margin: .5rem .5rem .5rem .5rem;
+ background: rgba(200,205,200,.3);
+}
+
+@mixin ibz-button ($color, $size:2rem) {
+ display: inline-block;
+ border: 1px solid darken($color,10%);
+ box-shadow: 2px 2px 1px adjust-hue($color,20deg);
+ color: $color;
+ border-radius: 5px;
+ font-style: italic;
+ padding: 0rem .5rem 0rem .5rem;
+ margin:.5rem;
+ background: lighten(#9494b7, 20%);
+ i {
+ padding-right: 0.3rem;
+ font-size: $size;
+ }
+ span {font-size: 1rem}
+
+ &:hover {
+ background: lighten($color, 45%);
+ border: 2px solid darken($color,20%);
+
+ }
+}
+
+@mixin ibz-input-alert ($color, $background) {
+ position :fixed;
+ margin: -2.2rem 0 0 15rem;
+ border-radius: 5px;
+ font-style: italic;
+ border-color: darken($background,10%);
+ color: $color;
+ background-color: $background !important;
+ padding: 0.2rem !important;
+}
diff --git a/afm-client/app/Frontend/styles/app/ibz-global.scss b/afm-client/app/Frontend/styles/app/ibz-global.scss
new file mode 100644
index 0000000..e28e7be
--- /dev/null
+++ b/afm-client/app/Frontend/styles/app/ibz-global.scss
@@ -0,0 +1,51 @@
+/*
+Copyright (C) 2015 "IoT.bzh"
+Author "Fulup Ar Foll"
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+@import "app/ibz-mixins";
+
+submit-button {
+ float: right;
+ @include ibz-button(grey);
+}
+
+zf-modal {
+ background-color: rgba(180,180,180,.5);
+}
+
+.box-content {
+ @include ibz-box-content;
+
+ .box-title {
+ display: block;
+ font-size: 1.3rem;
+ }
+}
+
+// Change Notifications default size
+.ui-notification {
+ width: auto;
+ border-radius: 5px;
+}
+
+.ibz-right {
+ float: right !important;
+}
+
+.ibz-left {
+ float: left !important;
+} \ No newline at end of file
diff --git a/afm-client/app/Frontend/styles/foundation/_foundation-icons.scss b/afm-client/app/Frontend/styles/foundation/_foundation-icons.scss
new file mode 100644
index 0000000..d401f3c
--- /dev/null
+++ b/afm-client/app/Frontend/styles/foundation/_foundation-icons.scss
@@ -0,0 +1,591 @@
+/*
+ * Foundation Icons v 3.0
+ * Made by ZURB 2013 http://zurb.com/playground/foundation-icon-fonts-3
+ * MIT License
+ */
+
+@font-face {
+ font-family: "foundation-icons";
+ src: url("/bower_components/foundation-icons.woff") format("woff");
+ font-weight: normal;
+ font-style: normal;
+}
+
+
+.fi-address-book:before,
+.fi-alert:before,
+.fi-align-center:before,
+.fi-align-justify:before,
+.fi-align-left:before,
+.fi-align-right:before,
+.fi-anchor:before,
+.fi-annotate:before,
+.fi-archive:before,
+.fi-arrow-down:before,
+.fi-arrow-left:before,
+.fi-arrow-right:before,
+.fi-arrow-up:before,
+.fi-arrows-compress:before,
+.fi-arrows-expand:before,
+.fi-arrows-in:before,
+.fi-arrows-out:before,
+.fi-asl:before,
+.fi-asterisk:before,
+.fi-at-sign:before,
+.fi-background-color:before,
+.fi-battery-empty:before,
+.fi-battery-full:before,
+.fi-battery-half:before,
+.fi-bitcoin-circle:before,
+.fi-bitcoin:before,
+.fi-blind:before,
+.fi-bluetooth:before,
+.fi-bold:before,
+.fi-book-bookmark:before,
+.fi-book:before,
+.fi-bookmark:before,
+.fi-braille:before,
+.fi-burst-new:before,
+.fi-burst-sale:before,
+.fi-burst:before,
+.fi-calendar:before,
+.fi-camera:before,
+.fi-check:before,
+.fi-checkbox:before,
+.fi-clipboard-notes:before,
+.fi-clipboard-pencil:before,
+.fi-clipboard:before,
+.fi-clock:before,
+.fi-closed-caption:before,
+.fi-cloud:before,
+.fi-comment-minus:before,
+.fi-comment-quotes:before,
+.fi-comment-video:before,
+.fi-comment:before,
+.fi-comments:before,
+.fi-compass:before,
+.fi-contrast:before,
+.fi-credit-card:before,
+.fi-crop:before,
+.fi-crown:before,
+.fi-css3:before,
+.fi-database:before,
+.fi-die-five:before,
+.fi-die-four:before,
+.fi-die-one:before,
+.fi-die-six:before,
+.fi-die-three:before,
+.fi-die-two:before,
+.fi-dislike:before,
+.fi-dollar-bill:before,
+.fi-dollar:before,
+.fi-download:before,
+.fi-eject:before,
+.fi-elevator:before,
+.fi-euro:before,
+.fi-eye:before,
+.fi-fast-forward:before,
+.fi-female-symbol:before,
+.fi-female:before,
+.fi-filter:before,
+.fi-first-aid:before,
+.fi-flag:before,
+.fi-folder-add:before,
+.fi-folder-lock:before,
+.fi-folder:before,
+.fi-foot:before,
+.fi-foundation:before,
+.fi-graph-bar:before,
+.fi-graph-horizontal:before,
+.fi-graph-pie:before,
+.fi-graph-trend:before,
+.fi-guide-dog:before,
+.fi-hearing-aid:before,
+.fi-heart:before,
+.fi-home:before,
+.fi-html5:before,
+.fi-indent-less:before,
+.fi-indent-more:before,
+.fi-info:before,
+.fi-italic:before,
+.fi-key:before,
+.fi-laptop:before,
+.fi-layout:before,
+.fi-lightbulb:before,
+.fi-like:before,
+.fi-link:before,
+.fi-list-bullet:before,
+.fi-list-number:before,
+.fi-list-thumbnails:before,
+.fi-list:before,
+.fi-lock:before,
+.fi-loop:before,
+.fi-magnifying-glass:before,
+.fi-mail:before,
+.fi-male-female:before,
+.fi-male-symbol:before,
+.fi-male:before,
+.fi-map:before,
+.fi-marker:before,
+.fi-megaphone:before,
+.fi-microphone:before,
+.fi-minus-circle:before,
+.fi-minus:before,
+.fi-mobile-signal:before,
+.fi-mobile:before,
+.fi-monitor:before,
+.fi-mountains:before,
+.fi-music:before,
+.fi-next:before,
+.fi-no-dogs:before,
+.fi-no-smoking:before,
+.fi-page-add:before,
+.fi-page-copy:before,
+.fi-page-csv:before,
+.fi-page-delete:before,
+.fi-page-doc:before,
+.fi-page-edit:before,
+.fi-page-export-csv:before,
+.fi-page-export-doc:before,
+.fi-page-export-pdf:before,
+.fi-page-export:before,
+.fi-page-filled:before,
+.fi-page-multiple:before,
+.fi-page-pdf:before,
+.fi-page-remove:before,
+.fi-page-search:before,
+.fi-page:before,
+.fi-paint-bucket:before,
+.fi-paperclip:before,
+.fi-pause:before,
+.fi-paw:before,
+.fi-paypal:before,
+.fi-pencil:before,
+.fi-photo:before,
+.fi-play-circle:before,
+.fi-play-video:before,
+.fi-play:before,
+.fi-plus:before,
+.fi-pound:before,
+.fi-power:before,
+.fi-previous:before,
+.fi-price-tag:before,
+.fi-pricetag-multiple:before,
+.fi-print:before,
+.fi-prohibited:before,
+.fi-projection-screen:before,
+.fi-puzzle:before,
+.fi-quote:before,
+.fi-record:before,
+.fi-refresh:before,
+.fi-results-demographics:before,
+.fi-results:before,
+.fi-rewind-ten:before,
+.fi-rewind:before,
+.fi-rss:before,
+.fi-safety-cone:before,
+.fi-save:before,
+.fi-share:before,
+.fi-sheriff-badge:before,
+.fi-shield:before,
+.fi-shopping-bag:before,
+.fi-shopping-cart:before,
+.fi-shuffle:before,
+.fi-skull:before,
+.fi-social-500px:before,
+.fi-social-adobe:before,
+.fi-social-amazon:before,
+.fi-social-android:before,
+.fi-social-apple:before,
+.fi-social-behance:before,
+.fi-social-bing:before,
+.fi-social-blogger:before,
+.fi-social-delicious:before,
+.fi-social-designer-news:before,
+.fi-social-deviant-art:before,
+.fi-social-digg:before,
+.fi-social-dribbble:before,
+.fi-social-drive:before,
+.fi-social-dropbox:before,
+.fi-social-evernote:before,
+.fi-social-facebook:before,
+.fi-social-flickr:before,
+.fi-social-forrst:before,
+.fi-social-foursquare:before,
+.fi-social-game-center:before,
+.fi-social-github:before,
+.fi-social-google-plus:before,
+.fi-social-hacker-news:before,
+.fi-social-hi5:before,
+.fi-social-instagram:before,
+.fi-social-joomla:before,
+.fi-social-lastfm:before,
+.fi-social-linkedin:before,
+.fi-social-medium:before,
+.fi-social-myspace:before,
+.fi-social-orkut:before,
+.fi-social-path:before,
+.fi-social-picasa:before,
+.fi-social-pinterest:before,
+.fi-social-rdio:before,
+.fi-social-reddit:before,
+.fi-social-skillshare:before,
+.fi-social-skype:before,
+.fi-social-smashing-mag:before,
+.fi-social-snapchat:before,
+.fi-social-spotify:before,
+.fi-social-squidoo:before,
+.fi-social-stack-overflow:before,
+.fi-social-steam:before,
+.fi-social-stumbleupon:before,
+.fi-social-treehouse:before,
+.fi-social-tumblr:before,
+.fi-social-twitter:before,
+.fi-social-vimeo:before,
+.fi-social-windows:before,
+.fi-social-xbox:before,
+.fi-social-yahoo:before,
+.fi-social-yelp:before,
+.fi-social-youtube:before,
+.fi-social-zerply:before,
+.fi-social-zurb:before,
+.fi-sound:before,
+.fi-star:before,
+.fi-stop:before,
+.fi-strikethrough:before,
+.fi-subscript:before,
+.fi-superscript:before,
+.fi-tablet-landscape:before,
+.fi-tablet-portrait:before,
+.fi-target-two:before,
+.fi-target:before,
+.fi-telephone-accessible:before,
+.fi-telephone:before,
+.fi-text-color:before,
+.fi-thumbnails:before,
+.fi-ticket:before,
+.fi-torso-business:before,
+.fi-torso-female:before,
+.fi-torso:before,
+.fi-torsos-all-female:before,
+.fi-torsos-all:before,
+.fi-torsos-female-male:before,
+.fi-torsos-male-female:before,
+.fi-torsos:before,
+.fi-trash:before,
+.fi-trees:before,
+.fi-trophy:before,
+.fi-underline:before,
+.fi-universal-access:before,
+.fi-unlink:before,
+.fi-unlock:before,
+.fi-upload-cloud:before,
+.fi-upload:before,
+.fi-usb:before,
+.fi-video:before,
+.fi-volume-none:before,
+.fi-volume-strike:before,
+.fi-volume:before,
+.fi-web:before,
+.fi-wheelchair:before,
+.fi-widget:before,
+.fi-wrench:before,
+.fi-x-circle:before,
+.fi-x:before,
+.fi-yen:before,
+.fi-zoom-in:before,
+.fi-zoom-out:before {
+ font-family: "foundation-icons";
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ display: inline-block;
+ text-decoration: inherit;
+}
+
+.fi-address-book:before { content: "\f100"; }
+.fi-alert:before { content: "\f101"; }
+.fi-align-center:before { content: "\f102"; }
+.fi-align-justify:before { content: "\f103"; }
+.fi-align-left:before { content: "\f104"; }
+.fi-align-right:before { content: "\f105"; }
+.fi-anchor:before { content: "\f106"; }
+.fi-annotate:before { content: "\f107"; }
+.fi-archive:before { content: "\f108"; }
+.fi-arrow-down:before { content: "\f109"; }
+.fi-arrow-left:before { content: "\f10a"; }
+.fi-arrow-right:before { content: "\f10b"; }
+.fi-arrow-up:before { content: "\f10c"; }
+.fi-arrows-compress:before { content: "\f10d"; }
+.fi-arrows-expand:before { content: "\f10e"; }
+.fi-arrows-in:before { content: "\f10f"; }
+.fi-arrows-out:before { content: "\f110"; }
+.fi-asl:before { content: "\f111"; }
+.fi-asterisk:before { content: "\f112"; }
+.fi-at-sign:before { content: "\f113"; }
+.fi-background-color:before { content: "\f114"; }
+.fi-battery-empty:before { content: "\f115"; }
+.fi-battery-full:before { content: "\f116"; }
+.fi-battery-half:before { content: "\f117"; }
+.fi-bitcoin-circle:before { content: "\f118"; }
+.fi-bitcoin:before { content: "\f119"; }
+.fi-blind:before { content: "\f11a"; }
+.fi-bluetooth:before { content: "\f11b"; }
+.fi-bold:before { content: "\f11c"; }
+.fi-book-bookmark:before { content: "\f11d"; }
+.fi-book:before { content: "\f11e"; }
+.fi-bookmark:before { content: "\f11f"; }
+.fi-braille:before { content: "\f120"; }
+.fi-burst-new:before { content: "\f121"; }
+.fi-burst-sale:before { content: "\f122"; }
+.fi-burst:before { content: "\f123"; }
+.fi-calendar:before { content: "\f124"; }
+.fi-camera:before { content: "\f125"; }
+.fi-check:before { content: "\f126"; }
+.fi-checkbox:before { content: "\f127"; }
+.fi-clipboard-notes:before { content: "\f128"; }
+.fi-clipboard-pencil:before { content: "\f129"; }
+.fi-clipboard:before { content: "\f12a"; }
+.fi-clock:before { content: "\f12b"; }
+.fi-closed-caption:before { content: "\f12c"; }
+.fi-cloud:before { content: "\f12d"; }
+.fi-comment-minus:before { content: "\f12e"; }
+.fi-comment-quotes:before { content: "\f12f"; }
+.fi-comment-video:before { content: "\f130"; }
+.fi-comment:before { content: "\f131"; }
+.fi-comments:before { content: "\f132"; }
+.fi-compass:before { content: "\f133"; }
+.fi-contrast:before { content: "\f134"; }
+.fi-credit-card:before { content: "\f135"; }
+.fi-crop:before { content: "\f136"; }
+.fi-crown:before { content: "\f137"; }
+.fi-css3:before { content: "\f138"; }
+.fi-database:before { content: "\f139"; }
+.fi-die-five:before { content: "\f13a"; }
+.fi-die-four:before { content: "\f13b"; }
+.fi-die-one:before { content: "\f13c"; }
+.fi-die-six:before { content: "\f13d"; }
+.fi-die-three:before { content: "\f13e"; }
+.fi-die-two:before { content: "\f13f"; }
+.fi-dislike:before { content: "\f140"; }
+.fi-dollar-bill:before { content: "\f141"; }
+.fi-dollar:before { content: "\f142"; }
+.fi-download:before { content: "\f143"; }
+.fi-eject:before { content: "\f144"; }
+.fi-elevator:before { content: "\f145"; }
+.fi-euro:before { content: "\f146"; }
+.fi-eye:before { content: "\f147"; }
+.fi-fast-forward:before { content: "\f148"; }
+.fi-female-symbol:before { content: "\f149"; }
+.fi-female:before { content: "\f14a"; }
+.fi-filter:before { content: "\f14b"; }
+.fi-first-aid:before { content: "\f14c"; }
+.fi-flag:before { content: "\f14d"; }
+.fi-folder-add:before { content: "\f14e"; }
+.fi-folder-lock:before { content: "\f14f"; }
+.fi-folder:before { content: "\f150"; }
+.fi-foot:before { content: "\f151"; }
+.fi-foundation:before { content: "\f152"; }
+.fi-graph-bar:before { content: "\f153"; }
+.fi-graph-horizontal:before { content: "\f154"; }
+.fi-graph-pie:before { content: "\f155"; }
+.fi-graph-trend:before { content: "\f156"; }
+.fi-guide-dog:before { content: "\f157"; }
+.fi-hearing-aid:before { content: "\f158"; }
+.fi-heart:before { content: "\f159"; }
+.fi-home:before { content: "\f15a"; }
+.fi-html5:before { content: "\f15b"; }
+.fi-indent-less:before { content: "\f15c"; }
+.fi-indent-more:before { content: "\f15d"; }
+.fi-info:before { content: "\f15e"; }
+.fi-italic:before { content: "\f15f"; }
+.fi-key:before { content: "\f160"; }
+.fi-laptop:before { content: "\f161"; }
+.fi-layout:before { content: "\f162"; }
+.fi-lightbulb:before { content: "\f163"; }
+.fi-like:before { content: "\f164"; }
+.fi-link:before { content: "\f165"; }
+.fi-list-bullet:before { content: "\f166"; }
+.fi-list-number:before { content: "\f167"; }
+.fi-list-thumbnails:before { content: "\f168"; }
+.fi-list:before { content: "\f169"; }
+.fi-lock:before { content: "\f16a"; }
+.fi-loop:before { content: "\f16b"; }
+.fi-magnifying-glass:before { content: "\f16c"; }
+.fi-mail:before { content: "\f16d"; }
+.fi-male-female:before { content: "\f16e"; }
+.fi-male-symbol:before { content: "\f16f"; }
+.fi-male:before { content: "\f170"; }
+.fi-map:before { content: "\f171"; }
+.fi-marker:before { content: "\f172"; }
+.fi-megaphone:before { content: "\f173"; }
+.fi-microphone:before { content: "\f174"; }
+.fi-minus-circle:before { content: "\f175"; }
+.fi-minus:before { content: "\f176"; }
+.fi-mobile-signal:before { content: "\f177"; }
+.fi-mobile:before { content: "\f178"; }
+.fi-monitor:before { content: "\f179"; }
+.fi-mountains:before { content: "\f17a"; }
+.fi-music:before { content: "\f17b"; }
+.fi-next:before { content: "\f17c"; }
+.fi-no-dogs:before { content: "\f17d"; }
+.fi-no-smoking:before { content: "\f17e"; }
+.fi-page-add:before { content: "\f17f"; }
+.fi-page-copy:before { content: "\f180"; }
+.fi-page-csv:before { content: "\f181"; }
+.fi-page-delete:before { content: "\f182"; }
+.fi-page-doc:before { content: "\f183"; }
+.fi-page-edit:before { content: "\f184"; }
+.fi-page-export-csv:before { content: "\f185"; }
+.fi-page-export-doc:before { content: "\f186"; }
+.fi-page-export-pdf:before { content: "\f187"; }
+.fi-page-export:before { content: "\f188"; }
+.fi-page-filled:before { content: "\f189"; }
+.fi-page-multiple:before { content: "\f18a"; }
+.fi-page-pdf:before { content: "\f18b"; }
+.fi-page-remove:before { content: "\f18c"; }
+.fi-page-search:before { content: "\f18d"; }
+.fi-page:before { content: "\f18e"; }
+.fi-paint-bucket:before { content: "\f18f"; }
+.fi-paperclip:before { content: "\f190"; }
+.fi-pause:before { content: "\f191"; }
+.fi-paw:before { content: "\f192"; }
+.fi-paypal:before { content: "\f193"; }
+.fi-pencil:before { content: "\f194"; }
+.fi-photo:before { content: "\f195"; }
+.fi-play-circle:before { content: "\f196"; }
+.fi-play-video:before { content: "\f197"; }
+.fi-play:before { content: "\f198"; }
+.fi-plus:before { content: "\f199"; }
+.fi-pound:before { content: "\f19a"; }
+.fi-power:before { content: "\f19b"; }
+.fi-previous:before { content: "\f19c"; }
+.fi-price-tag:before { content: "\f19d"; }
+.fi-pricetag-multiple:before { content: "\f19e"; }
+.fi-print:before { content: "\f19f"; }
+.fi-prohibited:before { content: "\f1a0"; }
+.fi-projection-screen:before { content: "\f1a1"; }
+.fi-puzzle:before { content: "\f1a2"; }
+.fi-quote:before { content: "\f1a3"; }
+.fi-record:before { content: "\f1a4"; }
+.fi-refresh:before { content: "\f1a5"; }
+.fi-results-demographics:before { content: "\f1a6"; }
+.fi-results:before { content: "\f1a7"; }
+.fi-rewind-ten:before { content: "\f1a8"; }
+.fi-rewind:before { content: "\f1a9"; }
+.fi-rss:before { content: "\f1aa"; }
+.fi-safety-cone:before { content: "\f1ab"; }
+.fi-save:before { content: "\f1ac"; }
+.fi-share:before { content: "\f1ad"; }
+.fi-sheriff-badge:before { content: "\f1ae"; }
+.fi-shield:before { content: "\f1af"; }
+.fi-shopping-bag:before { content: "\f1b0"; }
+.fi-shopping-cart:before { content: "\f1b1"; }
+.fi-shuffle:before { content: "\f1b2"; }
+.fi-skull:before { content: "\f1b3"; }
+.fi-social-500px:before { content: "\f1b4"; }
+.fi-social-adobe:before { content: "\f1b5"; }
+.fi-social-amazon:before { content: "\f1b6"; }
+.fi-social-android:before { content: "\f1b7"; }
+.fi-social-apple:before { content: "\f1b8"; }
+.fi-social-behance:before { content: "\f1b9"; }
+.fi-social-bing:before { content: "\f1ba"; }
+.fi-social-blogger:before { content: "\f1bb"; }
+.fi-social-delicious:before { content: "\f1bc"; }
+.fi-social-designer-news:before { content: "\f1bd"; }
+.fi-social-deviant-art:before { content: "\f1be"; }
+.fi-social-digg:before { content: "\f1bf"; }
+.fi-social-dribbble:before { content: "\f1c0"; }
+.fi-social-drive:before { content: "\f1c1"; }
+.fi-social-dropbox:before { content: "\f1c2"; }
+.fi-social-evernote:before { content: "\f1c3"; }
+.fi-social-facebook:before { content: "\f1c4"; }
+.fi-social-flickr:before { content: "\f1c5"; }
+.fi-social-forrst:before { content: "\f1c6"; }
+.fi-social-foursquare:before { content: "\f1c7"; }
+.fi-social-game-center:before { content: "\f1c8"; }
+.fi-social-github:before { content: "\f1c9"; }
+.fi-social-google-plus:before { content: "\f1ca"; }
+.fi-social-hacker-news:before { content: "\f1cb"; }
+.fi-social-hi5:before { content: "\f1cc"; }
+.fi-social-instagram:before { content: "\f1cd"; }
+.fi-social-joomla:before { content: "\f1ce"; }
+.fi-social-lastfm:before { content: "\f1cf"; }
+.fi-social-linkedin:before { content: "\f1d0"; }
+.fi-social-medium:before { content: "\f1d1"; }
+.fi-social-myspace:before { content: "\f1d2"; }
+.fi-social-orkut:before { content: "\f1d3"; }
+.fi-social-path:before { content: "\f1d4"; }
+.fi-social-picasa:before { content: "\f1d5"; }
+.fi-social-pinterest:before { content: "\f1d6"; }
+.fi-social-rdio:before { content: "\f1d7"; }
+.fi-social-reddit:before { content: "\f1d8"; }
+.fi-social-skillshare:before { content: "\f1d9"; }
+.fi-social-skype:before { content: "\f1da"; }
+.fi-social-smashing-mag:before { content: "\f1db"; }
+.fi-social-snapchat:before { content: "\f1dc"; }
+.fi-social-spotify:before { content: "\f1dd"; }
+.fi-social-squidoo:before { content: "\f1de"; }
+.fi-social-stack-overflow:before { content: "\f1df"; }
+.fi-social-steam:before { content: "\f1e0"; }
+.fi-social-stumbleupon:before { content: "\f1e1"; }
+.fi-social-treehouse:before { content: "\f1e2"; }
+.fi-social-tumblr:before { content: "\f1e3"; }
+.fi-social-twitter:before { content: "\f1e4"; }
+.fi-social-vimeo:before { content: "\f1e5"; }
+.fi-social-windows:before { content: "\f1e6"; }
+.fi-social-xbox:before { content: "\f1e7"; }
+.fi-social-yahoo:before { content: "\f1e8"; }
+.fi-social-yelp:before { content: "\f1e9"; }
+.fi-social-youtube:before { content: "\f1ea"; }
+.fi-social-zerply:before { content: "\f1eb"; }
+.fi-social-zurb:before { content: "\f1ec"; }
+.fi-sound:before { content: "\f1ed"; }
+.fi-star:before { content: "\f1ee"; }
+.fi-stop:before { content: "\f1ef"; }
+.fi-strikethrough:before { content: "\f1f0"; }
+.fi-subscript:before { content: "\f1f1"; }
+.fi-superscript:before { content: "\f1f2"; }
+.fi-tablet-landscape:before { content: "\f1f3"; }
+.fi-tablet-portrait:before { content: "\f1f4"; }
+.fi-target-two:before { content: "\f1f5"; }
+.fi-target:before { content: "\f1f6"; }
+.fi-telephone-accessible:before { content: "\f1f7"; }
+.fi-telephone:before { content: "\f1f8"; }
+.fi-text-color:before { content: "\f1f9"; }
+.fi-thumbnails:before { content: "\f1fa"; }
+.fi-ticket:before { content: "\f1fb"; }
+.fi-torso-business:before { content: "\f1fc"; }
+.fi-torso-female:before { content: "\f1fd"; }
+.fi-torso:before { content: "\f1fe"; }
+.fi-torsos-all-female:before { content: "\f1ff"; }
+.fi-torsos-all:before { content: "\f200"; }
+.fi-torsos-female-male:before { content: "\f201"; }
+.fi-torsos-male-female:before { content: "\f202"; }
+.fi-torsos:before { content: "\f203"; }
+.fi-trash:before { content: "\f204"; }
+.fi-trees:before { content: "\f205"; }
+.fi-trophy:before { content: "\f206"; }
+.fi-underline:before { content: "\f207"; }
+.fi-universal-access:before { content: "\f208"; }
+.fi-unlink:before { content: "\f209"; }
+.fi-unlock:before { content: "\f20a"; }
+.fi-upload-cloud:before { content: "\f20b"; }
+.fi-upload:before { content: "\f20c"; }
+.fi-usb:before { content: "\f20d"; }
+.fi-video:before { content: "\f20e"; }
+.fi-volume-none:before { content: "\f20f"; }
+.fi-volume-strike:before { content: "\f210"; }
+.fi-volume:before { content: "\f211"; }
+.fi-web:before { content: "\f212"; }
+.fi-wheelchair:before { content: "\f213"; }
+.fi-widget:before { content: "\f214"; }
+.fi-wrench:before { content: "\f215"; }
+.fi-x-circle:before { content: "\f216"; }
+.fi-x:before { content: "\f217"; }
+.fi-yen:before { content: "\f218"; }
+.fi-zoom-in:before { content: "\f219"; }
+.fi-zoom-out:before { content: "\f21a"; }
diff --git a/afm-client/app/Frontend/styles/foundation/_foundation-settings.scss b/afm-client/app/Frontend/styles/foundation/_foundation-settings.scss
new file mode 100644
index 0000000..d4c6415
--- /dev/null
+++ b/afm-client/app/Frontend/styles/foundation/_foundation-settings.scss
@@ -0,0 +1,605 @@
+// FOUNDATION FOR APPS SETTINGS
+// ----------------------------
+//
+// Table of Contents:
+//
+// 1. CSS Exports
+// 2. Global Styles
+// 3. Breakpoints
+// 4. Typography
+// 5. Grid
+// 6. Button
+// 7. Accordion
+// 8. Action Sheet
+// 9. Block List
+// 10. Button Group
+// 11. Card
+// 12. Extras
+// 13. Forms
+// 14. Iconic
+// 15. Label
+// 16. Menu Bar
+// 17. Modal
+// 18. Motion UI
+// 19. Notification
+// 20. Off-canvas
+// 21. Panel
+// 22. Popup
+// 23. Switch
+// 24. Tabs
+// 25. Title Bar
+
+@import "helpers/functions";
+
+// 1. CSS Exports
+// - - - - - - - - - - - - - - -
+
+// Change any value in this map from "true" to "false" to disable that component's CSS class output. You'll still be able to use the component's mixins, but none of our pre-written classes will be in your CSS.
+
+ $include-css: (
+ accordion: true,
+ action-sheet: true,
+ block-list: true,
+ button: true,
+ button-group: true,
+ card: true,
+ coloring: true,
+ extras: true,
+ forms: true,
+ grid: true,
+ iconic: true,
+ label: true,
+ badge: true,
+ list: true,
+ menu-bar: true,
+ modal: true,
+ motion: true,
+ notification: true,
+ off-canvas: true,
+ panel: true,
+ popup: true,
+ switch: true,
+ tabs: true,
+ title-bar: true,
+ typography: true,
+ utilities: true,
+ );
+
+// 2. Global Styles
+// - - - - - - - - - - - - - - -
+
+// This sets 1rem to be 16px
+// $rem-base: 16px;
+
+// The default font-size is set to 100% of the browser style sheet (usually 16px)
+// for compatibility with browser-based text zoom or user-set defaults.
+
+// Since the typical default browser font-size is 16px, that makes the calculation for grid size.
+// If you want your base font-size to be different and not have it affect the grid breakpoints,
+// set $rem-base to $base-font-size and make sure $base-font-size is a px value.
+// $base-font-size: 100%;
+
+// $base-line-height is 24px while $base-font-size is 16px
+// $base-line-height: 1.5;
+
+// Text selector helpers
+// $headers: "h1,h2,h3,h4,h5,h6";
+
+// We use these to define default font weights
+// $font-weight-normal: normal;
+// $font-weight-bold: bold;
+
+// We use these to control various global styles
+// $body-background: #fff;
+// $body-font-color: #222;
+// $body-font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
+// $body-font-weight: $font-weight-normal;
+// $body-font-style: normal;
+// $body-antialiased: true;
+
+// Application Colors
+// $primary-color: #00558b;
+// $secondary-color: #f1f1f1;
+// $alert-color: #F04124;
+// $info-color: #A0D3E8;
+// $success-color: #43AC6A;
+// $warning-color: #F08A24;
+// $dark-color: #232323;
+// $gray: #dfdfdf;
+// $gray-dark: darken($gray, 8);
+// $gray-light: lighten($gray, 8);
+
+// We use these to make sure border radius matches unless we want it different.
+// $global-radius: 4px;
+// $global-rounded: 1000px;
+
+// We use this for default spacing
+// $global-padding: 1rem;
+// $global-spacing: rem-calc(15);
+
+// 3. Breakpoints
+// - - - - - - - - - - - - - - -
+
+// These are our named breakpoints. You can use them in our breakpoint function like this: @include breakpoint(medium) { // Medium and larger styles }
+// $breakpoints: (
+// small: rem-calc(0),
+// medium: rem-calc(640),
+// large: rem-calc(1200),
+// xlarge: rem-calc(1440),
+// xxlarge: rem-calc(1920),
+// );
+
+// All of the names in this list will be output as classes in your CSS, like small-12, medium-6, and so on.
+// $breakpoint-classes: (small medium large);
+
+// 4. Typography
+// - - - - - - - - - - - - - - -
+
+// We use these to control header font styles
+// $header-font-family: $body-font-family;
+// $header-font-weight: $font-weight-normal;
+// $header-font-style: $font-weight-normal;
+// $header-font-color: #222;
+// $header-line-height: 1.4;
+// $header-top-margin: .2rem;
+// $header-bottom-margin: .5rem;
+// $header-text-rendering: optimizeLegibility;
+
+// We use these to control header font sizes
+// $h1-font-size: rem-calc(44);
+// $h2-font-size: rem-calc(37);
+// $h3-font-size: rem-calc(27);
+// $h4-font-size: rem-calc(23);
+// $h5-font-size: rem-calc(18);
+// $h6-font-size: 1rem;
+
+// We use these to control header size reduction on small screens
+// $h1-font-reduction: rem-calc(10);
+// $h2-font-reduction: rem-calc(10);
+// $h3-font-reduction: rem-calc(5);
+// $h4-font-reduction: rem-calc(5);
+// $h5-font-reduction: 0;
+// $h6-font-reduction: 0;
+
+// These control how subheaders are styled.
+// $subheader-line-height: 1.4;
+// $subheader-font-color: scale-color($header-font-color, $lightness: 35%);
+// $subheader-font-weight: $font-weight-normal;
+// $subheader-top-margin: .2rem;
+// $subheader-bottom-margin: .5rem;
+
+// A general <small> styling
+// $small-font-size: 60%;
+// $small-font-color: scale-color($header-font-color, $lightness: 35%);
+
+// We use these to style paragraphs
+// $paragraph-font-family: inherit;
+// $paragraph-font-weight: $font-weight-normal;
+// $paragraph-font-size: 1rem;
+// $paragraph-line-height: 1.6;
+// $paragraph-margin-bottom: rem-calc(20);
+// $paragraph-aside-font-size: rem-calc(14);
+// $paragraph-aside-line-height: 1.35;
+// $paragraph-aside-font-style: italic;
+// $paragraph-text-rendering: optimizeLegibility;
+
+// We use these to style <code> tags
+// $code-color: grayscale($primary-color);
+// $code-font-family: Consolas, 'Liberation Mono', Courier, monospace;
+// $code-font-weight: $font-weight-normal;
+// $code-background-color: scale-color($secondary-color, $lightness: 70%);
+// $code-border-size: 1px;
+// $code-border-style: solid;
+// $code-border-color: scale-color($code-background-color, $lightness: -10%);
+// $code-padding: rem-calc(2) rem-calc(5) rem-calc(1);
+
+// We use these to style anchors
+// $anchor-text-decoration: none;
+// $anchor-text-decoration-hover: none;
+// $anchor-font-color: $primary-color;
+// $anchor-font-color-hover: scale-color($anchor-font-color, $lightness: -14%);
+
+// We use these to style the <hr> element
+// $hr-border-width: 1px;
+// $hr-border-style: solid;
+// $hr-border-color: #ddd;
+// $hr-margin: rem-calc(20);
+
+// We use these to style lists
+// $list-font-family: $paragraph-font-family;
+// $list-font-size: $paragraph-font-size;
+// $list-line-height: $paragraph-line-height;
+// $list-margin-bottom: $paragraph-margin-bottom;
+// $list-style-position: outside;
+// $list-side-margin: 1.1rem;
+// $list-ordered-side-margin: 1.4rem;
+// $list-side-margin-no-bullet: 0;
+// $list-nested-margin: rem-calc(20);
+// $definition-list-header-weight: $font-weight-bold;
+// $definition-list-header-margin-bottom: .3rem;
+// $definition-list-margin-bottom: rem-calc(12);
+
+// We use these to style blockquotes
+// $blockquote-font-color: scale-color($header-font-color, $lightness: 35%);
+// $blockquote-padding: rem-calc(9 20 0 19);
+// $blockquote-border: 1px solid #ddd;
+// $blockquote-cite-font-size: rem-calc(13);
+// $blockquote-cite-font-color: scale-color($header-font-color, $lightness: 23%);
+// $blockquote-cite-link-color: $blockquote-cite-font-color;
+
+// Acronym styles
+// $acronym-underline: 1px dotted #ddd;
+
+// 5. Grid
+// - - - - - - - - - - - - - - -
+
+// $container-width: rem-calc(900);
+// $block-padding: $global-padding;
+// $total-columns: 12;
+// $block-grid-max-size: 6;
+
+// 6. Button
+// - - - - - - - - - - - - - - -
+
+// $button-padding: 0.85em 1em;
+// $button-margin: 0 $global-padding $global-padding 0;
+// $button-style: solid;
+// $button-background: $primary-color;
+// $button-color: auto;
+// $button-radius: 0;
+// $button-sizes: (
+// tiny: 0.7,
+// small: 0.8,
+// medium: 1,
+// large: 1.3,
+// );
+// $button-font-size: 0.9rem;
+// $button-opacity-disabled: 0.5;
+// $button-tag-selector: false;
+
+// 7. Accordion
+// - - - - - - - - - - - - - - -
+
+// $accordion-border: 1px solid $gray-dark;
+
+// $accordion-title-background: $gray-light;
+// $accordion-title-background-hover: smartscale($accordion-title-background, 5%);
+// $accordion-title-background-active: smartscale($accordion-title-background, 3%);
+// $accordion-title-color: isitlight($accordion-title-background);
+// $accordion-title-color-active: isitlight($accordion-title-background);
+
+// $accordion-title-padding: $global-padding;
+// $accordion-content-padding: $global-padding;
+
+// 8. Action Sheet
+// - - - - - - - - - - - - - - -
+
+// $actionsheet-background: white;
+// $actionsheet-border-color: #ccc;
+// $actionsheet-animate: transform opacity;
+// $actionsheet-animation-speed: 0.25s;
+// $actionsheet-width: 300px;
+// $actionsheet-radius: 4px;
+// $actionsheet-shadow: 0 -3px 10px rgba(black, 0.25);
+// $actionsheet-padding: $global-padding;
+// $actionsheet-tail-size: 10px;
+
+// $actionsheet-popup-shadow: 0 0 10px rgba(black, 0.25);
+
+// $actionsheet-link-color: #000;
+// $actionsheet-link-background-hover: smartscale($actionsheet-background);
+
+// 9. Block List
+// - - - - - - - - - - - - - - -
+
+// $blocklist-background: #fff;
+// $blocklist-fullbleed: true;
+// $blocklist-fontsize: 1rem;
+
+// $blocklist-item-padding: 0.8rem 1rem;
+// $blocklist-item-color: isitlight($blocklist-background, #000, #fff);
+// $blocklist-item-background-hover: smartscale($blocklist-background, 4.5%);
+// $blocklist-item-color-disabled: #999;
+// $blocklist-item-border: 1px solid smartscale($blocklist-background, 18.5%);
+
+// $blocklist-item-label-color: scale-color($blocklist-item-color, $lightness: 60%);
+// $blocklist-item-icon-size: 0.8;
+
+// $blocklist-header-fontsize: 0.8em;
+// $blocklist-header-color: smartscale($blocklist-item-color, 40%);
+// $blocklist-header-uppercase: true;
+
+// $blocklist-check-icons: true;
+
+// 10. Button Group
+// - - - - - - - - - - - - - - -
+
+// $btngroup-background: $primary-color;
+// $btngroup-color: #fff;
+// $btngroup-radius: $button-radius;
+
+// 11. Card
+// - - - - - - - - - - - - - - -
+
+// $card-background: #fff;
+// $card-color: isitlight($card-background);
+// $card-border: 1px solid smartscale($card-background, 7%);
+// $card-radius: $global-radius;
+// $card-shadow: 0 1px 2px rgba(#000, 0.2);
+// $card-padding: $global-padding;
+// $card-margin: 0.5rem;
+
+// $card-divider-background: smartscale($card-background, 7%);
+
+// 12. Extras
+// - - - - - - - - - - - - - - -
+
+// $closebutton-position: (top right);
+// $closebutton-size: 2em;
+// $closebutton-lineheight: 0.5;
+// $closebutton-color: #999;
+// $closebutton-color-hover: #333;
+
+// $thumbnail-padding: 0.5rem;
+// $thumbnail-shadow: 0 3px 15px rgba(black, 0.25);
+
+// 13. Forms
+// - - - - - - - - - - - - - - -
+
+// Basic form variables
+// $form-fontsize: 1rem;
+// $form-padding: 0.5rem;
+
+// Text fields
+// $input-color: #000;
+// $input-color-hover: $input-color;
+// $input-color-focus: $input-color;
+// $input-background: #fff;
+// $input-background-hover: $input-background;
+// $input-background-focus: $input-background;
+// $input-border: 1px solid #ccc;
+// $input-border-hover: 1px solid #bbb;
+// $input-border-focus: 1px solid #999;
+
+// Select menus
+// $select-color: #000;
+// $select-background: #fafafa;
+// $select-background-hover: smartscale($select-background, 4%);
+// $select-arrow: true;
+// $select-arrow-color: $select-color;
+
+// Labels
+// $form-label-fontsize: 0.9rem;
+// $form-label-margin: 0.5rem;
+// $form-label-color: #333;
+
+// Inline labels
+// $inlinelabel-color: #333;
+// $inlinelabel-background: #eee;
+// $inlinelabel-border: $input-border;
+
+// Range slider
+// $slider-background: #ddd;
+// $slider-height: 1rem;
+// $slider-radius: 0px;
+// $slider-thumb-height: 1.5rem;
+// $slider-thumb-color: $primary-color;
+// $slider-thumb-radius: 0px;
+
+// Progress and meter
+// $meter-height: 1.5rem;
+// $meter-background: #ccc;
+// $meter-fill: $primary-color;
+// $meter-fill-high: $success-color;
+// $meter-fill-medium: #e7cf00;
+// $meter-fill-low: $alert-color;
+// $meter-radius: 0;
+
+// 14. Iconic
+// - - - - - - - - - - - - - - -
+
+// $iconic-primary-fill: $primary-color;
+// $iconic-primary-stroke: $primary-color;
+// $iconic-accent-fill: $iconic-primary-fill;
+// $iconic-accent-stroke: $iconic-accent-fill;
+
+// 15. Label
+// - - - - - - - - - - - - - - -
+
+// $label-fontsize: 0.8rem;
+// $label-padding: ($global-padding / 3) ($global-padding / 2);
+// $label-radius: 0;
+// $label-background: $primary-color;
+// $label-color: isitlight($primary-color);
+
+// $badge-fontsize: 0.8em;
+// $badge-diameter: 1.5rem;
+// $badge-background: $primary-color;
+// $badge-color: #fff;
+
+// DEPRECATED: these variables will be removed in v1.1.
+// $badge-padding: .1em .61em;
+// $badge-radius: $global-rounded;
+// $badge-font-color: #fff;
+
+// 16. Menu Bar
+// - - - - - - - - - - - - - - -
+
+// $menubar-fontsize: 1rem;
+// $menubar-background: #fff;
+// $menubar-background-hover: smartscale($menubar-background, 7%);
+// $menubar-background-active: $menubar-background-hover;
+// $menubar-color: isitlight($menubar-background);
+// $menubar-color-hover: $menubar-color;
+// $menubar-color-active: $menubar-color-hover;
+
+// $menubar-item-padding: $global-padding;
+// $menubar-icon-size: 25px;
+// $menubar-icon-spacing: $menubar-item-padding;
+
+// 17. Modal
+// - - - - - - - - - - - - - - -
+
+// $modal-background: #fff;
+// $modal-border: 0;
+// $modal-radius: 0px;
+// $modal-shadow: none;
+// $modal-zindex: 1000;
+// $modal-sizes: (
+// tiny: 300px,
+// small: 500px,
+// medium: 600px,
+// large: 800px,
+// );
+
+// $modal-overlay-class: 'modal-overlay';
+// $modal-overlay-background: rgba(#333, 0.7);
+
+// 18. Motion UI
+// - - - - - - - - - - - - - - -
+
+// Classes to use when triggering in/out animations
+// $motion-class: (
+// in: "ng-enter",
+// out: "ng-leave",
+// );
+// $motion-class-active: (
+// in: "ng-enter-active",
+// out: "ng-leave-active",
+// );
+// $motion-class-stagger: (
+// in: "ng-enter-stagger",
+// out: "ng-leave-stagger",
+// );
+
+// Set if movement-based transitions should also fade the element in and out
+// $motion-slide-and-fade: false;
+// $motion-hinge-and-fade: true;
+// $motion-scale-and-fade: true;
+// $motion-spin-and-fade: true;
+
+// Default speed for transitions and animations
+// $motion-duration-default: 500ms;
+// Slow and fast modifiders
+// $motion-duration-slow: 750ms;
+// $motion-duration-fast: 250ms;
+// $motion-stagger-duration-default: 150ms;
+// $motion-stagger-duration-short: 50ms;
+// $motion-stagger-duration-long: 300ms;
+
+// Default timing function for transitions and animations
+// $motion-timing-default: ease;
+// Built-in and custom easing functions
+// Every item in this map becomes a CSS class
+// $motion-timings: (
+// linear: linear,
+// ease: ease,
+// easeIn: ease-in,
+// easeOut: ease-out,
+// easeInOut: ease-in-out,
+// bounceIn: cubic-bezier(0.485, 0.155, 0.240, 1.245),
+// bounceOut: cubic-bezier(0.485, 0.155, 0.515, 0.845),
+// bounceInOut: cubic-bezier(0.760, -0.245, 0.240, 1.245),
+// );
+
+// Default delay for all transitions and animations
+// $motion-delay-default: 0;
+// Short and long delay modifiers
+// $motion-delay-short: 300ms;
+// $motion-delay-long: 700ms;
+
+// 19. Notification
+// - - - - - - - - - - - - - - -
+
+// $notification-default-position: right top;
+// $notification-width: rem-calc(400);
+// $notification-offset: $global-padding;
+
+// $notification-background: $primary-color;
+// $notification-color: white;
+// $notification-padding: $global-padding;
+// $notification-radius: 4px;
+
+// $notification-icon-size: 60px;
+// $notification-icon-margin: $global-padding;
+// $notification-icon-align: top;
+
+
+// 20. Off-canvas
+// - - - - - - - - - - - - - - -
+
+// $offcanvas-size-horizontal: 250px;
+// $offcanvas-size-vertical: 250px;
+
+// $offcanvas-background: #fff;
+// $offcanvas-color: isitlight($offcanvas-background);
+// $offcanvas-padding: 0;
+// $offcanvas-shadow: 3px 0 10px rgba(black, 0.25);
+// $offcanvas-animation-speed: 0.25s;
+
+// $offcanvas-frame-selector: '.grid-frame';
+
+// 21. Panel
+// - - - - - - - - - - - - - - -
+
+// $panel-size-horizontal: 300px;
+// $panel-size-vertical: 300px;
+// $panel-padding: 0;
+
+// $panel-background: #fff;
+// $panel-shadow: 3px 0 10px rgba(black, 0.25);
+// $panel-animation-speed: 0.25s;
+
+// 22. Popup
+// - - - - - - - - - - - - - - -
+
+// $popup-width: rem-calc(300);
+// $popup-background: #fff;
+// $popup-border: 0;
+// $popup-radius: 0;
+// $popup-shadow: 0 0 10px rgba(#000, 0.25);
+
+// 23. Switch
+// - - - - - - - - - - - - - - -
+
+// $switch-width: rem-calc(50);
+// $switch-height: rem-calc(32);
+// $switch-background: #ccc;
+// $switch-background-active: $primary-color;
+// $switch-border: 0;
+// $switch-radius: 9999px;
+// $switch-animation-speed: 0.15s;
+
+// $switch-paddle-color: white;
+// $switch-paddle-offset: 4px;
+
+// 24. Tabs
+// - - - - - - - - - - - - - - -
+
+// $tabstrip-background: transparent;
+
+// $tab-title-background: $gray-light;
+// $tab-title-background-hover: smartscale($tab-title-background, 5%);
+// $tab-title-background-active: smartscale($tab-title-background, 3%);
+// $tab-title-color: isitlight($tab-title-background);
+// $tab-title-color-active: $tab-title-color;
+
+// $tab-title-padding: $global-padding;
+// $tab-content-padding: $global-padding;
+
+// 25. Title Bar
+// - - - - - - - - - - - - - - -
+
+// $titlebar-center-width: 50%;
+// $titlebar-side-width: (100% - $titlebar-center-width) / 2;
+// $titlebar-background: #eee;
+// $titlebar-color: #000;
+// $titlebar-border: 1px solid #ccc;
+// $titlebar-padding: $global-padding;
+// $titlebar-item-classes: (
+// center: 'center',
+// left: 'left',
+// right: 'right',
+// title: 'title',
+// );
+
diff --git a/afm-client/app/Frontend/styles/foundation/foundation-conf.scss b/afm-client/app/Frontend/styles/foundation/foundation-conf.scss
new file mode 100644
index 0000000..bdcfe1f
--- /dev/null
+++ b/afm-client/app/Frontend/styles/foundation/foundation-conf.scss
@@ -0,0 +1,19 @@
+@import "foundation-settings";
+@import "foundation-icons";
+@import "foundation";
+
+
+/*.sidebar {
+ // Panel on small screens
+ @extend %panel-base;
+ @include panel-position(left);
+ // Override styles to become a block on medium screens
+ @include breakpoint(medium) {
+ @include grid-panel-reset;
+ @include grid-block(4);
+ }
+ // Change size to 3 columns on large screens
+ @include breakpoint(large) {
+ @include grid-size(3);
+ }
+}*/ \ No newline at end of file
diff --git a/afm-client/app/Frontend/widgets/ActionButtons/ActionButtons.scss b/afm-client/app/Frontend/widgets/ActionButtons/ActionButtons.scss
new file mode 100644
index 0000000..16f7bb5
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/ActionButtons/ActionButtons.scss
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+@import "app/ibz-mixins";
+
+// place here your submit buttons customization
+appli-button {
+ @include ibz-button(grey,1rem)
+ img {
+ height: 3rem;
+ }
+}
diff --git a/afm-client/app/Frontend/widgets/ActionButtons/AppliButton.js b/afm-client/app/Frontend/widgets/ActionButtons/AppliButton.js
new file mode 100644
index 0000000..387212e
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/ActionButtons/AppliButton.js
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Bugs: Input with Callback SHOULD BE get 'required' class
+ */
+
+(function () {
+ 'use strict';
+
+ var tmplAppli = '<div ng-click="clicked()">' +
+ '<img ng-src={{icon}}-ico.png>' +
+ '<span>{{label}}</span>' +
+ '</div>';
+
+ var tmplModal =
+ '<b class="close-button" ng-click="close()">×</b>' +
+ '<img ng-src="{{appicon}}-ico.png">' +
+ '<span class="modal-text">Application <b>{{appname}}</b></span>' +
+ '<ul class="vertical icon-left primary menu-bar">' +
+ '<li><a ng-click=action("start")><i class="fi-check"> Start</i></a></li>' +
+ '<li><a href="#"><i class="fi-x"> Stop</i></a></li>' +
+ '<li><a href="#"><i class="fi-info"> Info</i></a></li>' +
+ '</ul>' +
+ '';
+
+ angular.module('AppliButton', [])
+ .directive('appliButton', function (AppConfig, AppCall, ModalFactory, Notification, $timeout) {
+
+ function mymethods(scope, elem, attrs) {
+ scope.clicked = function () {
+
+ var closeModal = function() {
+ console.log ("Modal Closing");
+ scope.modal.deactivate();
+ $timeout (function() {scope.modal.destroy();}, 1000);
+ };
+
+ var actionModal = function(action) {
+ console.log ("Modal Action=%s", action);
+ switch (action) {
+
+ case "start":
+ AppCall.get ("afm-main", "start", {id: scope.appliID}, function(response) {
+ if (response.status !== 200) {
+ Notification.error ({message: "Fail to start application=" + scope.label +" ID="+ scope.appliID, delay: 5000});
+ elem.addClass ("fail");
+ elem.removeClass ("success");
+ scope.callback (scope.appliID, "/api/afm-main/start", response);
+ return;
+ }
+
+ // Check this is a valid response from Binder
+ if (response.data.request.jtype !== "AJB_reply" && response.data.request.api !== "start") {
+ Notification.error ({message: "Invalid Respond to /opa/afm-main/start response.data="+response.data, delay: 5000});
+ elem.addClass ("fail");
+ elem.removeClass ("success");
+ scope.callback (scope.appliID, "/api/afm-main/start", response);
+ return;
+ }
+
+ // Application was stated
+ scope.callback (scope.appliID, "/api/afm-main/start", response);
+ });
+ break;
+
+ case "stop":
+ break;
+
+ default:
+ console.log ("ActionModal unknown action=[%s]", action);
+ break;
+ }
+
+ closeModal();
+ };
+
+ // reference http://foundation.zurb.com/apps/docs/#!/angular-modules
+ var config = {
+ animationIn: 'slideInFromTop',
+ contentScope: {
+ action : actionModal,
+ close : closeModal,
+ appicon : scope.icon,
+ appname : scope.label,
+ }, template : tmplModal
+ };
+ // Popup Modal to render application data
+ scope.modal = new ModalFactory(config);
+ scope.modal.activate ();
+ };
+
+ // extract application information from AppID+Store
+ if (attrs.handle && scope.store [attrs.handle].name) {
+ scope.icon = AppConfig.paths.icons + scope.store [attrs.handle].name.toLowerCase();
+ scope.label = scope.store [attrs.handle].name;
+ scope.appliID= attrs.handle;
+ } else {
+ scope.icon = AppConfig.paths.icons + 'w3c-ico.png';
+ scope.label = attrs.handle;
+ }
+
+ // add label as class
+ elem.addClass (scope.label.toLowerCase());
+
+ // note: clicked in imported and when template is clicked
+ // it will call clicked method passed in param.
+ }
+
+ return {
+ restrict: 'E',
+ template: tmplAppli,
+ link: mymethods,
+ scope: {callback: '=', store: '='}
+ };
+ });
+})();
diff --git a/afm-client/app/Frontend/widgets/ActionButtons/SubmitButton.js b/afm-client/app/Frontend/widgets/ActionButtons/SubmitButton.js
new file mode 100644
index 0000000..323cd46
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/ActionButtons/SubmitButton.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Bugs: Input with Callback SHOULD BE get 'required' class
+ */
+
+(function () {
+ 'use strict';
+
+ var tmpl = '<div ng-click="clicked()">' +
+ '<i class="{{icon}}"></i>' +
+ '<span>{{label}}</span>' +
+ '</div>';
+
+ angular.module('SubmitButton', [])
+ .directive('submitButton', function () {
+
+ function mymethods(scope, elem, attrs) {
+
+ // ajust icon or use default
+ scope.icon = attrs.icon || 'fi-foot';
+ scope.label = attrs.label || 'Next';
+
+ // add label as class
+ elem.addClass (scope.label.toLowerCase());
+
+ // note: clicked in imported and when template is clicked
+ // it will call clicked method passed in param.
+ }
+
+ return {
+ restrict: 'E',
+ template: tmpl,
+ link: mymethods,
+ scope: {clicked : '='}
+ };
+ });
+})();
diff --git a/afm-client/app/Frontend/widgets/FormInput/FormInput.scss b/afm-client/app/Frontend/widgets/FormInput/FormInput.scss
new file mode 100644
index 0000000..528ddfd
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/FormInput/FormInput.scss
@@ -0,0 +1,90 @@
+/*
+ Sample of style overload for a specific widget
+
+ Note: this SCSS is injected with main HTML page, it scope should be reduce
+ to a specific widget or it value will be propagated at a global level.
+*/
+
+@import "app/ibz-mixins";
+
+.upload-file {
+ @include ibz-button(grey,1rem)
+ float: right;
+ height : 3rem;
+ margin: 0.5rem;
+
+ i { font-size: 2rem}
+
+ .ibz-range-slider {
+ height: 10% !important;
+ border-radius: 5px;
+ background-color: lightgrey !important;
+
+ .range-slider-handle {
+ width: 10% !important;
+ height: 100% !important;
+ margin-top: .2rem;
+ background-color: purple !important;
+ }
+
+ .range-slider-active-segment {
+ height: 80% !important;
+ background-color: lightgreen;
+ }
+ }
+
+}
+
+input-text {
+
+ alert {@include ibz-input-alert(darkblue, rgba(200, 200, 200, 0.6))};
+
+ input {
+ margin-bottom: .5rem !important;
+ }
+
+ label {
+ margin-top: 1rem !important;
+ }
+
+ .required {
+ color: blue;
+ float: right;
+ color: lightskyblue;
+ }
+ .required.valid {
+ color: green;
+ }
+
+ .required.invalid {
+ color: plum;
+ }
+
+ .status-untouch {
+ border-color: rgba(200, 200, 200, 0.6) !important;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(200, 200, 200, 0.6) !important;
+ color: #696969 !important;
+ }
+
+ input:focus {
+ border-color: rgba(82,168,236,0.8) ;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(82,168,236,0.8) !important;
+ transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+ color: darkslateblue !important;
+ @extend shadow-transition;
+ }
+
+ .status-valid {
+ border-color: rgba(154, 205, 50, 0.6)!important;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 205, 50, 0.6)!important;
+ @extend shadow-transition;
+ }
+
+ .status-invalid {
+ border-color: rgba(154, 17, 69, 0.6);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 17, 69, 0.6)!important;
+ color: rgb(154, 17, 69);
+ @extend shadow-transition;
+ }
+
+}
diff --git a/afm-client/app/Frontend/widgets/FormInput/InputPassword.js b/afm-client/app/Frontend/widgets/FormInput/InputPassword.js
new file mode 100644
index 0000000..157009c
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/FormInput/InputPassword.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+(function() {
+'use strict';
+
+var tmpl = '<input-text class="password" tip="{{tip1}}" placeholder="{{place1}}"' +
+ 'label="{{label1}}" callback="valid1" name="{{name}}-1" value="pass1" required minlen="{{minlen}}" type="password" >' +
+ '</input-text>' +
+ '<input-text class="password" tip="tip2" placeholder="{{place2}}"' +
+ 'label="{{label2}}" callback="valid2" name="{{name}}-2" value="pass2" required minlen="{{minlen}}" type="password" > '+
+ '</input-text>';
+
+angular.module('InputPassword',[])
+
+.directive('inputPassword', function() {
+ function mymethods(scope, elem, attrs) {
+
+ scope.valid1 = function (name, value) {
+ console.log ("Clicked InputPassword1 name=%s value=%s", name, value);
+ scope.firstpwd = value;
+ };
+
+ scope.valid2 = function (name, value, done) {
+ console.log ("Clicked InputPassword2 name=%s value=%s", name, value);
+
+ // if both passwd equal then call form CB
+ if (scope.firstpwd !== value) {
+ done({valid: false, status: 'invalid', errmsg: "both password should match"});
+ } else {
+ scope.callback (attrs.name, value);
+ }
+
+ };
+
+ // this method can be called from controller to update widget status
+ scope.done=function (data) {
+ console.log ("Text-Input Callback ID="+ attrs.name + " data=", data);
+ for (var i in data) scope[i] = data[i];
+ };
+
+ // Export some attributes within directive scope for template
+ scope.name = attrs.name;
+ scope.label1 = attrs.label || 'Password';
+ scope.label2 = attrs.label || 'Password Verification';
+ scope.place1 = attrs.placeholder1 || 'User Password';
+ scope.tip1 = attrs.tip || 'Choose a Password';
+ scope.place2 = attrs.placeholder1 || 'Password Verification';
+ scope.tip2 = attrs.tip || 'Confirme your Password';
+ scope.minlen = attrs.minlen || 10;
+
+ if ("required" in attrs) scope.required = 'required';
+
+ }
+
+ return {
+ restrict: 'E',
+ template: tmpl,
+ link: mymethods,
+ scope: {
+ callback : '=',
+ }
+ };
+});
+
+console.log ("InputPassword Loaded");
+})();
diff --git a/afm-client/app/Frontend/widgets/FormInput/InputText.js b/afm-client/app/Frontend/widgets/FormInput/InputText.js
new file mode 100644
index 0000000..2653175
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/FormInput/InputText.js
@@ -0,0 +1,179 @@
+
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details..
+ */
+
+
+
+(function() {
+'use strict';
+
+var tmpl = '<tip-modal tip="tip"></tip-modal>' +
+ '<label for="{{name}}-intext">{{label}} <i ng-show="required" ng-click="ToBeDefined" ' +
+ 'class="required {{status}} fi-checkbox" title="Free Value But Mandatory Argument" alt="?"> &nbsp; </i></label>'+
+ '<input '+
+ ' type="{{type}}" id="{{name}}-intext" placeholder="{{placeholder}}" class="status-{{status}}"'+
+ ' ng-model="value" ng-blur="validate()" ng-focus="selected()" '+
+ ' ng-model-options="{ updateOn: \'default blur\', debounce: {default: 500, blur: 0} }"' +
+ '><alert data-ng-show="!valid&&errmsg">{{errmsg}}</alert>';
+
+var emailpatern = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
+
+angular.module('InputText',['JQueryEmu'])
+
+.directive('inputText', function(JQemu) {
+ function mymethods(scope, elem, attrs) {
+
+ // default value at 1st rendering
+ scope.error = false;
+ scope.valid = false;
+ scope.status = 'untouch';
+
+ scope.input = elem.find ("input");
+ scope.required = 0;
+
+ // requirer is use to increment requested counter
+ if ("required" in attrs) {
+ scope.required = 1;
+ elem.addClass ("required");
+ }
+
+ // user enter input reset error status
+ scope.selected = function () {
+ scope.error=false;
+ scope.errmsg=false;
+ scope.status = 'touch';
+ };
+
+ scope.validate = function () {
+
+ // get value from input field bypassing Angular ng-model
+ console.log ("Clicked InputText name=%s value=%s valid=%s", scope.name, scope.value, scope.valid);
+
+ // form is not untouched anymore
+ scope.parent.removeClass ("ng-pristine");
+
+ // if value not null clean up string
+ if (scope.value) {
+ scope.error=false;
+ // remove leading and trailling space
+ scope.value = scope.value.trim();
+
+ // remove any space is not allowed
+ if ('nospace' in attrs) {
+ scope.value=scope.value.replace(/\s/g, '');
+ }
+
+ if ('lowercase' in attrs) {
+ scope.value = scope.value.toLowerCase();
+ }
+
+ // check minimum lenght
+ if ("minlen" in attrs) {
+ if (scope.value.length < attrs.minlen) {
+ scope.status='invalid';
+ scope.errmsg=scope.name + ': Mininum Lengh= ' + attrs.minlen + ' Characters';
+ scope.error=true;
+ }
+ }
+
+ if ('email' in attrs) {
+ if (!emailpatern.test (scope.value)) {
+ scope.status='invalid';
+ scope.errmsg='invalid email address';
+ scope.error=true;
+ }
+ }
+
+ } else {
+ if (scope.required) {
+ scope.status='invalid';
+ scope.errmsg=scope.name + ': Required Attribute';
+ scope.error=true;
+ }
+ }
+
+ // If local control fail let's refuse input
+ if (scope.error) {
+ if (scope.required && scope.valid) {
+ scope.valid = false;
+ if (scope.l4acounter.validated > 0) scope.l4acounter.validated --;
+ }
+ // use call to update form scope on form completeness
+ scope.callback (attrs.name, null, scope.done);
+ } else {
+ // localcheck is OK backup may nevertheless change status to false
+ if (scope.required && !scope.valid) scope.l4acounter.validated ++;
+ scope.status='valid';
+ scope.valid=true;
+ scope.callback (attrs.name, scope.value, scope.done);
+ }
+
+ };
+
+ // this method can be called from controller to update widget status
+ scope.done=function (data) {
+ console.log ("Text-Input Callback ID="+ attrs.name + " data=", data);
+ for (var i in data) scope[i] = data[i];
+ };
+
+ // Export some attributes within directive scope for template
+ scope.label = attrs.label;
+ scope.name = attrs.name;
+ scope.placeholder = attrs.placeholder;
+ scope.type = attrs.type || "text";
+ scope.tip = attrs.tip;
+
+ // search for form within parent elemnts
+ scope.parent = JQemu.parent (elem, "FORM");
+
+ // email enforce lowercase and nospace
+ if ("email" in attrs) {
+ attrs.lowercase=true;
+ attrs.nospace=true;
+ attrs.minlen=6;
+ }
+
+ if (scope.required) {
+ scope.l4acounter = scope.parent.data ("l4acounter");
+ if (!scope.l4acounter) {
+ scope.l4acounter = {required:1, validated:0};
+ console.log("Field "+scope.name+" is required (1st)");
+ scope.parent.data ("l4acounter", scope.l4acounter);
+ } else {
+ console.log("Field "+scope.name+" is required");
+ scope.l4acounter.required ++;
+ }
+ }
+
+ // refresh validation each time controler update value
+ scope.$watch ('value', function(){
+ if(scope.value) scope.validate(); }
+ );
+
+ }
+
+ return {
+ restrict: 'E',
+ template: tmpl,
+ link: mymethods,
+ scope: {
+ callback : '=',
+ value: '='
+ }
+ };
+});
+
+console.log ("InputText Loaded");
+})();
diff --git a/afm-client/app/Frontend/widgets/FormInput/UploadAppli.js b/afm-client/app/Frontend/widgets/FormInput/UploadAppli.js
new file mode 100644
index 0000000..d18f620
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/FormInput/UploadAppli.js
@@ -0,0 +1,230 @@
+
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details..
+ *
+ * Reference:
+ * https://developer.mozilla.org/en/docs/Web/API/FileReader
+ * https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method
+ * https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
+ * https://www.terlici.com/2015/05/16/uploading-files-locally.html
+ * https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js
+ * https://stuk.github.io/jszip/documentation/howto/read_zip.html
+ * http://onehungrymind.com/zip-parsing-jszip-angular/
+ * http://stackoverflow.com/questions/15341912/how-to-go-from-blob-to-arraybuffer
+ *
+ * Bugs: zip file sent even when flag as invalid
+ */
+
+
+
+(function() {
+'use strict';
+
+var tmplAppli = '<input type="file" name="{{name}}-input" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mimetype}}" style="display:none">'+
+ '<div class="upload-file" ng-click="imgClicked()">' +
+ '<i class="{{icon}}"></i> <span>{{label}}</span>' +
+ '<range-slider ng-show="!noslider" id="{{name}}-slider" automatic=true inithook="SliderInitCB"></range-slider>' +
+ '</div>';
+
+var tmplModal = '<span class="modal-text">Upload Application <b>{{appname}}</b> ?</span>' +
+ '<div>'+
+ '<img ng-src="{{appicon}}">' +
+ '<submit-button icon="fi-x" label="Cancel" clicked="refused"></submit-button>'+
+ '<submit-button icon="fi-like" label="Install" clicked="accepted"></submit-button> ' +
+ '</div>';
+
+
+// Service Create xform insert files in and Post it to url
+function LoadFileSvc (scope, files, fileCB) {
+ var xmlReq = new XMLHttpRequest();
+ var xform = new FormData();
+
+ // Update slider during Upload
+ xmlReq.upload.onprogress = function (event) {
+ var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
+ if (scope.slider) scope.slider.setValue (progress);
+ };
+
+ // Upload is finish let's notify controler callback
+ xmlReq.onload = function () {
+ scope.divElem.addClass ("success");
+ scope.divElem.removeClass ("error");
+ var response ={
+ status : xmlReq.status,
+ headers: xmlReq.getAllResponseHeaders()
+ };
+ scope.callback (response);
+ };
+
+ xmlReq.onerror = function () {
+ scope.divElem.addClass ("error");
+ scope.divElem.removeClass ("success");
+ };
+
+ xmlReq.onabort = function () {
+ scope.divElem.addClass ("error");
+ scope.divElem.removeClass ("success");
+ var response ={
+ status : xmlReq.status,
+ headers: xmlReq.getAllResponseHeaders()
+ };
+ scope.callback (response);
+ };
+
+ this.postfile = function(posturl) {
+ // everything looks OK let's Post it
+ xmlReq.open("POST", posturl , true);
+ xmlReq.send(xform);
+ };
+
+ for (var i = 0; i < files.length; i++) {
+ this.file = files[i];
+ console.log ("filetype=%s",this.file.type );
+ // Unknow Type !!! if (!this.file.type.match(scope.mimetype)) continue;
+
+ console.log ("Selected file=" + this.file.name + " size="+ this.file.size/1024 + " Type="+ this.file.type);
+
+ // File to upload is too big
+ if (this.file.size > scope.maxsize*1024) {
+ scope.thumbnail = scope.istoobig; // warning if image path is wrong nothing happen
+ scope.$apply('thumbnail'); // we short-circuit Angular resync Image
+ return;
+ }
+
+ // This is not an uploadable file
+ if(isNaN(this.file.size)) {
+ scope.thumbnail = scope.isnotvalid;
+ scope.$apply('thumbnail');
+ return;
+ }
+
+ this.basename= this.file.name.split('/').reverse()[0];
+ //scope.imgElem[0].file = this.file;
+
+ // If File is an image let display it now
+ if (fileCB) {
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(this.file);
+ reader.onload = fileCB;
+ }
+ // if everything is OK let's add file to xform
+ xform.append(scope.name, this.file, this.file.name);
+ }
+
+}
+
+angular.module('UploadFiles',['AppConfig', 'ModalNotification', 'RangeSlider'])
+
+.directive('uploadAppli', function(AppConfig, JQemu, Notification, ModalFactory, $timeout) {
+ function mymethods(scope, elem, attrs) {
+
+ // get widget image handle from template
+ scope.inputElem = elem.find('input');
+ scope.divElem = elem.find('div');
+
+ // Image was ckick let's simulate an input (file) click
+ scope.imgClicked = function () {
+ scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
+ };
+
+ // Slider control handle registration after creation
+ scope.SliderInitCB=function (slider) {
+ scope.slider= slider;
+ };
+
+ // Upload is delegated to a shared function
+ scope.UpLoadFile=function (files) {
+ var handle;
+ var appicon;
+
+ var accepted = function() {
+ console.log ("Modal Accepted");
+ // This Looks OK let's Post Xform/File
+ handle.postfile(attrs.posturl + "?token=" + AppConfig.session.token);
+
+ scope.modal.deactivate();
+ $timeout (function() {scope.modal.destroy();}, 1000);
+ };
+
+ var refused = function() {
+ console.log ("Modal Refused");
+ scope.modal.deactivate();
+ $timeout (function() {scope.modal.destroy();}, 1000);
+ };
+
+ var readerCB = function (upload) {
+
+ var zipapp = new JSZip (upload.target.result);
+ var thumbnail = zipapp.file("icon_128.png");
+
+ // Check is we have a thumbnail within loaded Zipfile
+ if (!thumbnail) {
+ console.log ("This is not a valid Application Framework APP");
+ scope.thumbnail=AppConfig.paths[scope.category] + 'isnotvalid.png';
+ scope.$apply('thumbnail'); // we short-circuit Angular resync Image
+ } else {
+ //scope.imgElem[0].src = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));
+ appicon = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));
+
+ // reference http://foundation.zurb.com/apps/docs/#!/angular-modules
+ var config = {
+ animationIn: 'slideInFromTop',
+ contentScope: {
+ accepted: accepted,
+ refused: refused,
+ appicon: appicon,
+ appname: handle.basename
+ }, template: tmplModal
+ };
+ // Popup Modal to render application data
+ scope.modal = new ModalFactory(config);
+ scope.modal.activate ();
+ }
+ };
+
+ // Load file within browser and if OK call readerCB
+ handle = new LoadFileSvc (scope, files, readerCB);
+ };
+
+ // Initiallize default values from attributes values
+ scope.name= attrs.name || 'appli';
+ scope.category= attrs.category || 'appli';
+ scope.mimetype= (attrs.accept || '.wgt');
+ scope.maxsize = attrs.maxsize || 100000; // default max size 100MB
+ scope.regexp = new RegExp (attrs.accept+ '.*','i');
+ scope.icon = attrs.icon || 'fi-upload';
+ scope.label = attrs.label || 'Upload';
+
+ if (attrs.thumbnail) scope.isnotvalid= AppConfig.paths[scope.category] + attrs.isnotvalid;
+ else scope.isnotvalid=AppConfig.paths[scope.category] + 'isnotvalid.png';
+
+ if (attrs.istoobig) scope.istoobig= AppConfig.paths[scope.category] + attrs.istoobig;
+ else scope.istoobig=AppConfig.paths[scope.category] + 'istoobig.png';
+ scope.noslider = attrs.noslider || false;
+
+ if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);
+ }
+ return {
+ restrict: 'E',
+ template: tmplAppli,
+ link: mymethods,
+ scope: {
+ callback : '='
+ }
+ };
+
+});
+
+console.log ("UploadFile Loaded");
+})();
diff --git a/afm-client/app/Frontend/widgets/Navigation/LinkButton.js b/afm-client/app/Frontend/widgets/Navigation/LinkButton.js
new file mode 100644
index 0000000..3e83425
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/Navigation/LinkButton.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Bugs: Input with Callback SHOULD BE get 'required' class
+ */
+
+(function () {
+ 'use strict';
+
+ var tmpl = '<span title="Goto: {{href}}" ng-click="clicked()">' +
+ '<i class="{{icon}}"></i>' +
+ '<span>{{label}}</span>' +
+ '</span>';
+
+
+ angular.module('LinkButton', [])
+ .directive('linkButton', function ($location) {
+
+ function mymethods(scope, elem, attrs) {
+
+ scope.clicked = function () {
+
+ if (!attrs.query) $location.path(attrs.href);
+ else $location.path(attrs.href).search(attrs.query);
+ };
+
+ // ajust icon or use default
+ scope.icon = attrs.icon || 'fi-link';
+ scope.label = attrs.label || 'Jump';
+ scope.href = attrs.href || '/home';
+
+ // add label as class
+ elem.addClass (scope.label.toLowerCase());
+ }
+
+ return {
+ restrict: 'E',
+ template: tmpl,
+ link: mymethods,
+ scope: {}
+ };
+ });
+})();
diff --git a/afm-client/app/Frontend/widgets/Navigation/Navigation.scss b/afm-client/app/Frontend/widgets/Navigation/Navigation.scss
new file mode 100644
index 0000000..2babf24
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/Navigation/Navigation.scss
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+@import "app/ibz-mixins";
+
+
+link-button {@include ibz-button(#3366ff,1rem)};
+
+//pale blue for secondaty link
+link-button.secondary {@include ibz-button(#99b3ff,1rem)};
+
diff --git a/afm-client/app/Frontend/widgets/Notifications/ModalNotification.js b/afm-client/app/Frontend/widgets/Notifications/ModalNotification.js
new file mode 100644
index 0000000..37ba047
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/Notifications/ModalNotification.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Bugs: Input with Callback SHOULD BE get 'required' class
+ *
+ * ref: https://developer.mozilla.org/en-US/docs/Web/Events/mouseover
+ *
+ * usage:
+ *
+ * tipModal: listen event from elem.parent() to display tip-modal
+ * <div class="xxxx">
+ * <tip-modal tip=xxxx></tip-modal>
+ * <input-text ....></input-text>
+ * </div>
+ *
+ * Note: use CSS.visibility to avoid display flickering at initial display.
+ */
+
+(function () {
+ 'use strict';
+
+ var tmpl = '<div class="tip-modal-popup">' +
+ '<i class="{{icon}}"></i>' +
+ '<span>{{tip}}</span>' +
+ '</span></div>' ;
+
+ angular.module('ModalNotification', [])
+ .directive('tipModal', function ($timeout) {
+
+ function mymethods(scope, elem, attrs) {
+ scope.parent = elem.parent();
+ scope.modal = elem.find("div");
+
+
+ // delay tip display to avoid blinking when moving mouse fast
+ function display () {
+ function action() {
+ if (scope.show) scope.modal.css({opacity: 1, visibility:'visible'});
+ }
+ scope.show = true;
+ scope.timeout = $timeout(action, scope.delay);
+ }
+
+ function close () {
+ scope.show = false;
+ scope.modal.css({opacity: 0, visibility:'hidden'});
+ }
+
+
+ // ajust icon or use default
+ scope.icon = attrs.icon || 'fi-lightbulb';
+
+ // Update Parent element to get mouse event
+ scope.parent.addClass ('as-modal-tip');
+ scope.parent.bind('click', close);
+ scope.parent.bind('focus', display);
+ scope.parent.bind('mouseover', display);
+ scope.parent.bind('mouseleave', close);
+ scope.parent.bind('blur', close);
+
+ scope.delay = attrs.delay || 1000; // wait 1s before displaying tip
+ }
+
+ return {
+ restrict: 'E',
+ template: tmpl,
+ link: mymethods,
+ scope: {tip: "="} // tip may not be defined when widget is display
+ };
+ });
+})();
diff --git a/afm-client/app/Frontend/widgets/Notifications/Notifications.scss b/afm-client/app/Frontend/widgets/Notifications/Notifications.scss
new file mode 100644
index 0000000..fb740b7
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/Notifications/Notifications.scss
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Reference: http://www.greywyvern.com/?post=337
+ */
+
+@import "app/ibz-mixins";
+
+
+link-button {@include ibz-button(#3366ff,1rem)};
+
+// Modal should be relative and tip-modal-popup absolute
+tip-modal {
+ position:relative;
+}
+
+.tip-modal-popup {
+ //visibility: hidden;
+ width: 20rem;
+ position:absolute;
+ top:1em;
+ padding: 0.2em 0.6em;
+ border:1px solid #996633;
+ background-color:#e5ffff;
+ color:#000;
+ opacity:0;
+ transition:visibility .5s linear 1s,opacity 1s linear;
+ border-radius: 5px;
+ i {
+ margin: 0 .3rem 0 0;
+ display: inline;
+ }
+}
+
+token-refresh {
+ @include ibz-button(grey,1rem)
+ i {margin-left: .5rem;}
+ margin-right: 1rem;
+}
+
+token-refresh.online {
+ color: #0066cc;
+ i {color: lime;}
+}
+
+token-refresh.offline {
+ color: #ff00ff;
+ i {color: red;}
+}
diff --git a/afm-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js b/afm-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js
new file mode 100644
index 0000000..5c5b5ae
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js
@@ -0,0 +1,131 @@
+/*
+ alsa-gateway -- provide a REST/HTTP interface to ALSA-Mixer
+
+ Copyright (C) 2015, Fulup Ar Foll
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with scope program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ References:
+
+ */
+
+(function () {
+ 'use strict';
+
+ var template =
+ '<div class="afb-monitor" ng-click="getping()">' +
+ '<span class="afb-refresh-token" >afb://{{hostname}}:{{httpdport}}</span>' +
+ '<i class="{{icon}}"></i>' +
+ '</div>';
+
+
+// scope module is load statically before any route is cativated
+angular.module('TokenRefresh', ['AppConfig', 'ModalNotification'])
+
+ .directive ('tokenRefresh', function($timeout, $http, $location, Notification, AppConfig) {
+
+ function mymethods(scope, elem, attrs) {
+ scope.status=undefined; // neither thu neither false
+
+
+ scope.online = function () {
+ elem.addClass ("online");
+ elem.removeClass ("offline");
+ };
+
+ scope.offline = function(){
+ elem.addClass ("offline");
+ elem.removeClass ("online");
+ };
+
+ scope.onerror = function(data, errcode, headers) {
+ if (scope.status !== false) {
+ Notification.warning ({message: "AppFramework Binder Lost", delay: 5000});
+ scope.offline();
+ }
+ scope.status = 0;
+ };
+
+ scope.onsuccess = function(data, errcode, headers, config) {
+ if (scope.status !== true) {
+ if (data.request.token) AppConfig.session.token = data.request.token;
+ if (data.request.uuid) AppConfig.session.uuid = data.request.uuid;
+ if (data.request.timeout) AppConfig.session.timeout = data.request.timeout;
+
+ Notification.success ({message: "AppFramework Binder Back to Live", delay: 3000});
+ scope.online();
+ if (scope.callback) scope.callback();
+ }
+ scope.status = 1;
+ };
+
+ // Check Binder status
+ scope.getping = function() {
+
+ var handler = $http.get(AppConfig.session.ping+'?token='+ AppConfig.session.token);
+
+ // process success and error
+ handler.success(scope.onsuccess);
+ handler.error(scope.onerror);
+
+ // restart a new timer for next ping
+ $timeout (scope.getping, AppConfig.session.pingrate*1000);
+ };
+
+ // Check Binder status
+ scope.refresh = function() {
+ var handler = $http.get(AppConfig.session.refresh+'?token='+ AppConfig.session.token);
+
+ // process success and error
+ handler.success(scope.onsuccess);
+ handler.error(scope.onerror);
+ // restart a new timer for next refresh to 1/4 of timeout session
+ $timeout (scope.refresh, AppConfig.session.timeout *250);
+ };
+
+ // Initial connection
+ scope.tkcreate = function() {
+ var handler = $http.get(AppConfig.session.create+'?token='+ AppConfig.session.initial);
+
+ // process success and error
+ handler.success(scope.onsuccess);
+ handler.error(scope.onerror);
+ };
+
+ scope.icon = attrs.icon || "fi-lightbulb";
+ scope.hostname = $location.host();
+ scope.httpdport = $location.port();
+ scope.autolog = JSON.parse(attrs.autolog || false);
+
+ if (scope.autolog) scope.tkcreate();
+
+ // Init ping and refresh process
+ $timeout (scope.getping, AppConfig.session.pingrate*1000);
+ $timeout (scope.refresh, AppConfig.session.timeout *250);
+ }
+
+ return {
+ template: template,
+ scope: {
+ callback : "="
+ },
+ restrict: 'E',
+ link: mymethods
+ };
+});
+
+})();
+console.log ("Token Refresh Loaded");
+
diff --git a/afm-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js b/afm-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js
new file mode 100644
index 0000000..77f0fce
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Bugs: Input with Callback SHOULD BE get 'required' class
+ *
+ * ref: https://developer.mozilla.org/en-US/docs/Web/Events/mouseover
+ *
+ * usage:
+Usage <range-slider>
+---------------------
+ <range-slider
+ id="my-slider-name" // only use as an argument to callback
+ class="my-custom-class" // default class is ibz-range-slider
+ placeholder="Track Date Selection" // place holder for date readonly input zone
+
+ <!-- Foundation classes -->
+ class="radius" // check Zurb foundation doc for further info.
+ class="ibz-handle-display" // increase handle width to hold slider current value
+
+ <!-- Angular Scope Variables -->
+ callback="myCallBack" // $scope.myCallBack(sliderhandle) is called when ever slider handle blur
+ formatter="SliderFormatCB" // $scope.myFormatter(value, sliderid) when exist is call when ever slider handle moves. Should return external form of slider value.
+ ng-model="xxxxxx" // xxx Must be defined, script will store a new RangerObject within provided ng-model variable.
+ start-at="ScopeVar" // Dynamic limitation when slider is constrains by an external componant [ex: check in/out]
+ stop-at="ScopeVar" // Idem but for end.
+
+ <!-- Angular Directive Attributes -->
+ not-less="integer" // Fixed starting value for slider [default 0]
+ not-more="integer" // Fixed end value for sliders [default 100]
+ by-step="+-integer" // If by-step is >0 then slider use it as step-value, when negative use it for decimal precision
+ display-target="handle" // display slider external formated value in the handle [requirer calss="ibz-handle-display"]
+ dual-handles='true' // add a second handle to slider for min/max range
+ initial='value|[start/stop]' // slider initial value [dual-handles] may have initial values
+ /></range-slider>
+ */
+
+(function () {
+ 'use strict';
+
+var RangeSlider = angular.module('RangeSlider',[]);
+
+function RangeSliderHandle (scope) {
+ var internals = [];
+ var externals = [];
+
+ this.getId = function() {
+ return scope.sliderid;
+ };
+
+ this.getCbHandle = function() {
+ return scope.cbhandle;
+ };
+
+ this.getView= function (handle) {
+ if (!handle) handle = 0;
+
+ // if value did not change return current external representation
+ if (scope.value[handle] === internals[handle]) return externals[handle];
+
+ // build external representation and save it for further requests
+ internals[handle] = scope.value[handle];
+ if (scope.formatter) externals[handle] = scope.formatter(scope.value[handle], scope.ctrlhandle);
+ else externals[handle] = scope.value[handle];
+
+ return externals[handle];
+ };
+
+ this.updateClass = function (classe, status) {
+ scope.updateClass (classe, status);
+ };
+
+ this.forceRefresh = function (timer) {
+ scope.forceRefresh(timer);
+ };
+
+ this.getValue= function (handle) {
+ if (!handle) handle = 0;
+ return scope.value[handle];
+ };
+
+ this.getRelative= function (handle) {
+ if (!handle) handle = 0;
+ return scope.relative[handle];
+ };
+
+ this.setValue= function (value, handle) {
+ if (!handle) handle = 0;
+ scope.setValue (value, handle);
+ };
+
+ this.setDisable= function (flag) {
+ scope.setDisable(flag);
+ };
+}
+
+RangeSlider.directive('rangeSlider', function ($log, $document, $timeout) {
+
+ var template= '<div class="ibz-range-slider range-slider" title="{{title}}"data-slider>'+
+ '<span class="range-slider-handle handle-min" ng-mousedown="handleCB($event,0)" ng-focus="focusCB(true)" ng-blur="focusCB(false)" role="slider" tabindex="0"></span>'+
+ '<span class="handle-max" ng-mousedown="handleCB($event,1)" ng-focus="focusCB(true)" ng-blur="focusCB(false)" role="slider" tabindex="0"></span>'+
+ '<span class="range-slider-active-segment"></span>'+
+ '<span class="ibz-range-slider-start" ></span> '+
+ '<span class="ibz-range-slider-stop"></span> '+
+ '<input id={{sliderid}} type="hidden">'+
+ '</div>';
+
+
+ function link (scope, element, attrs, model) {
+ // full initialisation of slider from a single object
+ scope.initWidget = function (initvalues) {
+
+ if (initvalues.byStep) scope.byStep = parseInt(initvalues.byStep);
+ if (initvalues.notMore) scope.notMore = parseInt(initvalues.notMore);
+ if (initvalues.notLess) scope.notLess = parseInt(initvalues.notLess);
+ if (initvalues.id) scope.sliderid= initvalues.id;
+
+ // hugely but in some case DOM is not finish when we try to set values !!!
+ if (initvalues.value !== undefined) {
+ scope.value = initvalues.value;
+ scope.forceRefresh (50); // wait 50ms for DOM to be ready
+ }
+ };
+
+ // this function recompute slide positioning
+ scope.forceRefresh = function (timer) {
+ var value = scope.value;
+ scope.value = [undefined,undefined];
+ $timeout (function() {
+ scope.setValue(value[0],0);
+ if (scope.dual) scope.setValue(value[1],1);
+ }, timer);
+ };
+
+ // handler to change class from slider handle
+ scope.updateClass = function (classe, status) {
+
+ if (status) element.addClass (classe);
+ else element.removeClass (classe);
+ };
+
+ scope.setDisable = function (disabled) {
+
+ if (disabled) {
+ element.addClass ("disable");
+ scope.handles[0].css ('visibility','hidden');
+ if (scope.dual) {
+ scope.handles[1].css ('visibility','hidden');
+ }
+ } else {
+ element.removeClass ("disable");
+ scope.handles[0].css ('visibility','visible');
+ if (scope.dual) scope.handles[1].css ('visibility','visible');
+ }
+
+ };
+
+ scope.normalize = function (value) {
+ var result;
+ var range = scope.notMore - scope.notLess;
+ var point = value * range;
+
+ // if step is positive let's round step by step
+ if (scope.byStep > 0) {
+ var mod = (point - (point % scope.byStep)) / scope.byStep;
+ var rem = point % scope.byStep;
+
+ var round = (rem >= scope.byStep * 0.5 ? scope.byStep : 0);
+ result= (mod * scope.byStep + round) + scope.notLess;
+ //console.log ("range=%d value=%d point=%d mod=%d rem=%d round=%d result=%d", range, value, point, mod, rem, round, result)
+ return result;
+ }
+
+ // if step is negative return round to asked decimal
+ if (scope.byStep < 0) {
+ var power = Math.pow (10,(scope.byStep * -1));
+ result = scope.notLess + parseInt (point * power) / power;
+ return (result);
+ }
+
+ // if step is null return full value
+ return point;
+ };
+
+ // return current value
+ scope.getValue = function (offset, handle) {
+ if (scope.vertical) {
+ scope.relative[handle] = (offset - scope.bounds.handles[handle].getBoundingClientRect().height) / (scope.bounds.bar.getBoundingClientRect().height - scope.bounds.handles[handle].getBoundingClientRect().height);
+ } else {
+ scope.relative[handle] = offset / (scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width);
+ }
+
+ var newvalue = scope.normalize (scope.relative[handle]);
+
+
+ // if internal value change update or model
+ if (newvalue !== scope.value[handle]) {
+ if (newvalue < scope.startValue) newvalue=scope.startValue;
+ if (newvalue > scope.stopValue) newvalue=scope.stopValue;
+
+
+ if (scope.formatter) {
+ scope.viewValue = scope.formatter (newvalue, scope.ctrlhandle);
+ } else {
+ scope.viewValue = newvalue;
+ }
+ if (scope.displays[handle]) {
+ scope.displays[handle].html (scope.viewValue);
+ }
+
+ // update external representation of the model
+ scope.value[handle] = newvalue;
+ if (model) model.$setViewValue (scope.viewValue);
+ scope.$apply();
+ if (newvalue > scope.startValue && newvalue < scope.stopValue) scope.translate(offset, handle);
+ }
+ };
+
+
+ scope.setStart = function (value) {
+ var offset;
+
+ if (value > scope.value[0]) {
+ if (!scope.dual) scope.setValue (value,0);
+ else scope.setValue (value,1);
+ }
+
+ if (scope.vertical) {
+ offset = scope.bounds.bar.getBoundingClientRect().height * (value - scope.notLess) / (scope.notMore - scope.notLess);
+ scope.start.css('height',offset + 'px');
+ } else {
+ offset = scope.bounds.bar.getBoundingClientRect().width * (value - scope.notLess) / (scope.notMore - scope.notLess);
+ scope.start.css('width',offset + 'px');
+ }
+
+ scope.startValue= value;
+ };
+
+ scope.setStop = function (value) {
+ var offset;
+
+ if (value < scope.value[0]) {
+ if (!scope.dual) scope.setValue (value,0);
+ else scope.setValue (value,1);
+ }
+
+ if (scope.vertical) {
+ offset = scope.bounds.bar.getBoundingClientRect().height * (value - scope.notLess) / (scope.notMore - scope.notLess);
+ scope.start.css('height',offset + 'px');
+ } else {
+ offset = scope.bounds.bar.getBoundingClientRect().width * (value - scope.notLess) / (scope.notMore - scope.notLess);
+ scope.stop.css({'right': 0, 'width': (scope.bounds.bar.getBoundingClientRect().width - offset) + 'px'});
+ }
+
+ scope.stopValue= value;
+ };
+
+ scope.translate = function (offset, handle) {
+ var start;
+
+ if (scope.vertical) {
+ // take handle size in account to compute middle
+ var voffset = scope.bounds.bar.getBoundingClientRect().height - offset;
+
+ scope.handles[handle].css({
+ '-webkit-transform': 'translateY(' + voffset + 'px)',
+ '-moz-transform': 'translateY(' + voffset + 'px)',
+ '-ms-transform': 'translateY(' + voffset + 'px)',
+ '-o-transform': 'translateY(' + voffset + 'px)',
+ 'transform': 'translateY(' + voffset + 'px)'
+ });
+ if (!scope.dual) scope.slider.css('height', offset + 'px');
+ else if (scope.relative[1] && scope.relative[0]) {
+ var height = (scope.relative[1] - scope.relative[0]) * scope.bounds.bar.getBoundingClientRect().height;
+ start = (scope.relative[0] * scope.bounds.bar.getBoundingClientRect().height);
+ scope.slider.css ({'bottom': start+'px','height': height + 'px'});
+ }
+ } else {
+
+ scope.handles[handle].css({
+ '-webkit-transform': 'translateX(' + offset + 'px)',
+ '-moz-transform': 'translateX(' + offset + 'px)',
+ '-ms-transform': 'translateX(' + offset + 'px)',
+ '-o-transform': 'translateX(' + offset + 'px)',
+ 'transform': 'translateX(' + offset + 'px)'
+ });
+ if (!scope.dual) scope.slider.css('width',offset + 'px');
+ else if (scope.relative[1] && scope.relative[0]) {
+ var width = (scope.relative[1] - scope.relative[0]) * scope.bounds.bar.getBoundingClientRect().width;
+ start = (scope.relative[0] * scope.bounds.bar.getBoundingClientRect().width);
+ scope.slider.css ({'left': start+'px','width': width + 'px'});
+ }
+ }
+ };
+
+ // position handle on the bar depending a given value
+ scope.setValue = function (value , handle) {
+ var offset;
+
+ // if value did not change ignore
+ if (value === scope.value[handle]) return;
+ if (value === undefined) value=0;
+ if (value > scope.notMore) value=scope.notMore;
+ if (value < scope.notLess) value=scope.notLess;
+
+ if (scope.vertical) {
+ scope.relative[handle] = (value - scope.notLess) / (scope.notMore - scope.notLess);
+ if (handle === 0) offset = (scope.relative[handle] * scope.bounds.bar.getBoundingClientRect().height) + scope.bounds.handles[handle].getBoundingClientRect().height/2;
+ if (handle === 1) offset = scope.relative[handle] * scope.bounds.bar.getBoundingClientRect().height;
+
+ } else {
+ scope.relative[handle] = (value - scope.notLess) / (scope.notMore - scope.notLess);
+ offset = scope.relative[handle] * (scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width);
+ }
+
+ scope.translate (offset,handle);
+ scope.value[handle] = value;
+
+ if (scope.formatter) {
+ // when call through setValue we do not pass cbHandle
+ scope.viewValue = scope.formatter (value, undefined);
+ } else {
+ scope.viewValue = value;
+ }
+
+ if (model) model.$setViewValue( scope.viewValue);
+
+ if (scope.displays[handle]) {
+ scope.displays[handle].html (scope.viewValue);
+ }
+ };
+
+
+ // Minimal keystroke handling to close picker with ESC [scope.actif is current handle index]
+ scope.keydown= function(e){
+
+ switch(e.keyCode){
+ case 39: // Right
+ case 38: // up
+ if (scope.byStep > 0) scope.$apply(scope.setValue ((scope.value[scope.actif]+scope.byStep), scope.actif));
+ if (scope.byStep < 0) scope.$apply(scope.setValue ((scope.value[scope.actif]+(1 / Math.pow(10, scope.byStep*-1))),scope.actif));
+ if (scope.callback) scope.callback (scope.value[scope.actif], scope.ctrlhandle);
+ break;
+ case 37: // left
+ case 40: // down
+ if (scope.byStep > 0) scope.$apply(scope.setValue ((scope.value[scope.actif] - scope.byStep), scope.actif));
+ if (scope.byStep < 0) scope.$apply(scope.setValue ((scope.value[scope.actif] - (1 / Math.pow(10, scope.byStep*-1))),scope.actif));
+ if (scope.callback) scope.callback (scope.value[scope.actif], scope.ctrlhandle);
+ break;
+ case 27: // esc
+ scope.handles[scope.actif][0].blur();
+ }
+ };
+
+ scope.moveHandle = function (handle, clientX, clientY) {
+ var offset;
+ if (scope.vertical) {
+ offset = scope.bounds.bar.getBoundingClientRect().bottom - clientY;
+ if (offset > scope.bounds.bar.getBoundingClientRect().height) offset = scope.bounds.bar.getBoundingClientRect().height;
+ if (offset < scope.bounds.handles[handle].getBoundingClientRect().height) offset = scope.bounds.handles[handle].getBoundingClientRect().height;
+ } else {
+ offset = clientX - scope.bounds.bar.getBoundingClientRect().left;
+
+ if (offset < 0) offset = 0;
+ if ((clientX + scope.bounds.handles[handle].getBoundingClientRect().width) > scope.bounds.bar.getBoundingClientRect().right) {
+ offset = scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width;
+ }
+ }
+
+ scope.getValue (offset, handle);
+
+ // prevent dual handle to cross
+ if (scope.dual && scope.value [0] > scope.value[1]) {
+ if (handle === 0) scope.setValue (scope.value[0] , 1);
+ else scope.setValue(scope.value[1],0);
+ }
+ };
+
+
+ scope.focusCB = function (inside) {
+ if (inside) {
+ $document.on('keydown',scope.keydown);
+ } else {
+ $document.unbind('keydown',scope.keydown);
+ }
+ };
+
+ // bar was touch let move handle to this point
+ scope.touchBarCB = function (event) {
+ var handle=0;
+ var relative;
+ var touches = event.changedTouches;
+ var oldvalue = scope.value[handle];
+
+ event.preventDefault();
+
+ // if we have two handles select closest one from touch point
+ if (scope.dual) {
+ if (scope.vertical) relative = (touches[0].pageY - scope.bounds.bar.getBoundingClientRect().bottom) / scope.bounds.bar.getBoundingClientRect().height;
+ else relative= (touches[0].pageX - scope.bounds.bar.getBoundingClientRect().left) / scope.bounds.bar.getBoundingClientRect().width;
+
+ var distance0 = Math.abs(relative - scope.relative[0]);
+ var distance1 = Math.abs(relative - scope.relative[1]);
+ if (distance1 < distance0) handle=1;
+ }
+
+ // move handle to new place
+ scope.moveHandle (handle,touches[0].pageX, touches[0].pageY);
+ if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
+ };
+
+ // handle was touch and drag
+ scope.touchHandleCB = function (touchevt, handle) {
+ var oldvalue = scope.value[handle];
+
+ touchevt.preventDefault();
+ $document.on('touchmove',touchmove);
+ $document.on('touchend' ,touchend);
+ element.unbind('touchstart', scope.touchBarCB);
+
+ function touchmove(event) {
+ event.preventDefault();
+ var touches = event.changedTouches;
+ for (var idx = 0; idx < touches.length; idx++) {
+ scope.moveHandle (handle,touches[idx].pageX, touches[idx].pageY);
+ }
+ }
+
+ function touchend(event) {
+ $document.unbind('touchmove',touchmove);
+ $document.unbind('touchend' ,touchend);
+ element.on('touchstart', scope.touchBarCB);
+
+ // if value change notify application callback
+ if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
+ }
+ };
+
+ scope.handleCB = function (clickevent, handle) {
+
+ if (attrs.automatic) return;
+
+ var oldvalue = scope.value[handle];
+ // register mouse event to track handle
+ clickevent.preventDefault();
+
+ $document.on('mousemove',mousemove);
+ $document.on('mouseup', mouseup);
+ scope.handles[handle][0].focus();
+ scope.actif=handle;
+
+ // slider handle is moving
+ function mousemove(event) {
+ scope.moveHandle (handle, event.clientX, event.clientY);
+ }
+
+ // mouse is up dans leave slider send resize events
+ function mouseup() {
+ $document.unbind('mousemove', mousemove);
+ $document.unbind('mouseup', mouseup);
+
+ // if value change notify application callback
+ if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
+ }
+ };
+
+ // simulate jquery find by classes capabilities [warning only return 1st elements]
+ scope.find = function (select, elem) {
+ var domelem;
+
+ if (elem) domelem = elem[0].querySelector(select);
+ else domelem = element[0].querySelector(select);
+
+ var angelem = angular.element(domelem);
+ return (angelem);
+ };
+
+
+
+ scope.initialSettings = function (initial) {
+ var decimal_places_match_result;
+ scope.value=[]; // store low/height value when two handles
+ scope.relative=[];
+
+ if (scope.precision === null) {
+ decimal_places_match_result = ('' + scope.byStep).match(/\.([\d]*)/);
+ scope.precision = decimal_places_match_result && decimal_places_match_result[1] ? decimal_places_match_result[1].length : 0;
+ }
+
+ // position handle to initial value(s)
+ element.on('touchstart', scope.touchBarCB);
+ scope.handles[0].on('touchstart', function(evt){scope.touchHandleCB(evt,0);});
+
+ // this slider has two handles low/hight
+ if (scope.dual) {
+ scope.handles[1].addClass('range-slider-handle');
+ scope.handles[1].on('touchstart', function(evt){scope.touchHandleCB(evt,1);});
+ if (!scope.initvalues) scope.setValue (initial[1],1);
+ }
+
+ // if we have an initstate object apply it
+ if (scope.initvalues) scope.initWidget (scope.initvalues);
+ else scope.setValue (initial[0],0);
+ };
+
+ scope.init = function () {
+ scope.sliderid = attrs.id || "slider-" + parseInt (Math.random() * 1000);
+ scope.startValue = -Infinity;
+ scope.stopValue = Infinity;
+ scope.byStep = parseInt(attrs.byStep) || 1;
+ scope.vertical = attrs.vertical || false;
+ scope.dual = attrs.dualHandles|| false;
+ scope.trigger_input_change= false;
+ scope.notMore = parseInt(attrs.notMore) || 100;
+ scope.notLess = parseInt(attrs.notLess) || 0;
+
+ if (scope.vertical) element.addClass("vertical-range");
+
+ scope.handles= [scope.find('.handle-min'), scope.find('.handle-max')];
+ scope.bar = element;
+ scope.slider = scope.find('.range-slider-active-segment');
+ scope.start = scope.find('.ibz-range-slider-start');
+ scope.stop = scope.find('.ibz-range-slider-stop');
+ scope.disable= attrs.disable || false;
+
+ scope.ctrlhandle = new RangeSliderHandle (scope);
+
+ // prepare DOM object pointer to compute size dynamically
+ scope.bounds = {
+ bar : element[0],
+ handles: [scope.handles[0][0], scope.handles[1][0]]
+ };
+
+ if (attrs.disable === 'true') scope.setDisable(true);
+
+ if (attrs.displayTarget) {
+ switch (attrs.displayTarget) {
+ case true :
+ case 'handle' :
+ scope.displays = scope.handles;
+ scope.handles[0].addClass('ibz-range-slider-display');
+ if (scope.dual) scope.handles[1].addClass('ibz-range-slider-display');
+ break;
+ default:
+ scope.displays = [$document.getElementById (attrs.displayTarget)];
+ }
+ } else scope.displays=[];
+
+ // extract initial values from attrs and parse into int
+ if (!attrs.initial) {
+ scope.initial = [scope.ngModel, scope.ngModel]; // initialize to model values
+ } else {
+ var initial = attrs.initial.split(',');
+ scope.initial = [
+ initial[0] !== undefined ? parseInt (initial[0]) : scope.notLess,
+ initial[1] !== undefined ? parseInt (initial[1]) : scope.notMore
+ ];
+ }
+
+ // Monitor any changes on start/stop dates.
+ scope.$watch('startAt', function() {
+ if (scope.value < scope.startAt ) {
+ //scope.setValue (scope.startAt);
+ }
+ if (scope.startAt) scope.setStart (scope.startAt);
+ });
+
+ scope.$watch('stopAt' , function() {
+ if (scope.value > scope.stopAt) {
+ //scope.setValue (scope.stopAt);
+ }
+ if (scope.stopAt) scope.setStop (scope.stopAt);
+ });
+
+ // finish widget initialisation
+ scope.initialSettings (scope.initial);
+
+ };
+
+ scope.init();
+
+ // slider is ready provide control handle to application controller
+ scope.$watch ('inithook', function () { // init Values may arrive late
+ if (scope.inithook) scope.inithook (scope.ctrlhandle);
+ });
+
+ scope.$watch ('initvalues', function () { // init Values may arrive late
+ if (scope.initvalues) scope.initWidget(scope.initvalues);
+ });
+
+ // two-way binding if model value changes
+ scope.$watch ('ngModel', function (newValue) {
+ scope.setValue(newValue, 0);
+ });
+ }
+
+return {
+ restrict: "E", // restrict to <range-slider> HTML element name
+ scope: {
+ startAt :'=', // First acceptable date
+ stopAt :'=', // Last acceptable date
+ callback :'=', // Callback to actif when a date is selected
+ formatter:'=', // Callback for drag event call each time internal value changes
+ inithook :'=', // Hook point to control slider from API
+ cbhandle :'=', // Argument added to every callback
+ initvalues:'=', // Initial values as a single object
+ ngModel: '=' // the model value
+ },
+ require: '?ngModel',
+ template: template, // html template is build from JS
+ replace: true, // replace current directive with template while inheriting of class
+ link: link // pickadate object's methods
+};
+});
+
+console.log ("RangeSlider Loaded");
+
+})(); \ No newline at end of file
diff --git a/afm-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss b/afm-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss
new file mode 100644
index 0000000..6717d0e
--- /dev/null
+++ b/afm-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss
@@ -0,0 +1,67 @@
+/*
+ * ibz-Datepicker for Foundation
+ *
+ * Author: Fulup Ar Foll
+ * Date : March-2015
+ * Object: SASS stylesheet, customized to Foundation
+ * References: https://css-tricks.com/stripes-css/
+ *
+ */
+@import "app/ibz-mixins";
+
+.range-slider-handle {
+ display: inline-block;
+ position: absolute;
+ z-index: 1;
+ top: -0.2rem;
+ width: 2rem;
+ height: 1.375rem;
+ border: 1px solid none;
+ cursor: pointer;
+ background: #008cba;
+}
+
+.range-slider.radius, .range-slider-handle {
+ background: #008cba;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.range-slider-active-segment {
+ display: inline-block;
+ top: 0.07rem;
+ position: absolute;
+ height: 0.80rem;
+ background: #e5e5e5;
+}
+
+.ibz-range-slider {
+ background-color: rgba(154, 205, 50, 0.6) !important;
+ height: 1rem;
+ position: relative;
+
+ .range-slider-active-segment {
+ background-color: rgba(82, 168, 200, 0.6);
+ }
+
+ &-display {
+ background-color: rgba(82, 168, 200, 0.6) !important;
+ width : 4rem !important;
+ padding: .25rem;
+ text-align:center
+ }
+
+ &-start,&-stop {
+ display: inline-block;
+ position: absolute;
+ padding-top: 2px;
+ height: 95%;
+ background: repeating-linear-gradient(
+ 45deg,
+ #606dbc,
+ #606dbc 10px,
+ #465298 10px,
+ #465298 20px
+ );}
+
+}
diff --git a/afm-client/app/etc/AppDefaults.js b/afm-client/app/etc/AppDefaults.js
new file mode 100644
index 0000000..b0eb1a8
--- /dev/null
+++ b/afm-client/app/etc/AppDefaults.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var SESSION_TIMEOUT= 3600000; // default is 1h loggin session
+
+// Default config will be superseaded by ProjectRoot/.config-l4a.js $HOME/.config-l4a.js /etc/default/config-l4a.js
+config = {
+
+ APPNAME : 'AFBclient', // AppName is use as main Angular Module name
+ FRONTEND: "Frontend", // HTML5 frontend [no leading ./]
+ BACKEND : "Backend", // NodeJS Rest API [no leading ./]
+ URLBASE : '/opa/', // HTML basedir when running in production [should end with a /]
+ APIBASE : '/api/', // Api url base dir [should end with a /]
+ DEBUG : 4001, // Node Debug Port
+ DBG_LVL : 5, // Debug Trace Level 0=no trace.
+
+ UPLOAD_DIR: '/tmp/uploads', // directory destination for uploaded files [/api/post/upload]
+
+ // EXPRESS WEB server config [note: URLBASE generate rewriting rules]
+ EXPRESS_HOST : 'localhost', // HTTP will only listen on related Internet interface
+ EXPRESS_PORT : 4000, // HTTP port
+ EXPRESS_LOGDIR : __dirname + '/../../log', // httpd log file
+ EXPRESS_SECRET : Math.random().toString(36).slice(2), // [default cookie session]
+ EXPRESS_SESSION : SESSION_TIMEOUT
+};
+
+module.exports = config;
+
diff --git a/afm-client/app/etc/_Config.js b/afm-client/app/etc/_Config.js
new file mode 100644
index 0000000..ce93d43
--- /dev/null
+++ b/afm-client/app/etc/_Config.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 Fulup Ar Foll
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var fs = require('fs');
+
+function Config () {
+ 'use strict';
+ var values=[];
+ var extention='-l4a.js';
+ var conf;
+
+ // Configs file path last one supersead first one.
+ var files= [__dirname + "/AppDefaults.js", "/etc/default/noderc"+ extention, process.env.NODERC, process.env.HOME + "/.noderc"+ extention , __dirname +"/../../.noderc.js" ];
+
+ // Parse any existing files within config list & merge them
+ for (var idx in files) {
+ if (files[idx]) {
+ //console.log ("files=", files[idx]);
+ if (fs.existsSync (files[idx])) conf=require (files[idx]);
+ for (var i in conf) values[i] = conf[i];
+ }
+ }
+
+ // set path to search for node_module within parent directory
+ process.env.NODE_PATH= process.env.NODE_PATH + '../node_modules';
+
+ // console.log ("values=", values);
+ return values;
+}
+
+module.exports = Config();
diff --git a/afm-client/app/etc/_Trace.js b/afm-client/app/etc/_Trace.js
new file mode 100644
index 0000000..79ef4f5
--- /dev/null
+++ b/afm-client/app/etc/_Trace.js
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 Fulup Ar Foll
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var util = require("util");
+var path = require("path");
+var config= require('./_Config');
+
+function TracePoint () {
+ var saved = Error.prepareStackTrace; // save default prepareStack function
+ Error.prepareStackTrace = function(_, stack){ return stack; }; // overload err stack handling
+ Error.captureStackTrace(this, arguments.callee); // request a stack
+ this.trace = this.stack; // effectively build trace
+ Error.prepareStackTrace = saved; // restore original nodejs function
+}
+
+// ------- Public Methods --------------
+var dbgLevel = function(target, level, format) { //+ arguments
+ // try to get debugLevel from calling object or global config
+ if (target && target.dbgLevel) dbgLevel = target.dbgLevel;
+ else dbgLevel = config.DBG_LVL || 1;
+
+ if (dbgLevel >= level ) {
+
+ var args = [].slice.call(arguments, 2); // copy argument in a real array leaving out level
+ var message = util.format.apply(null, args);
+
+ var trace = new TracePoint().trace;
+ var info = {
+ fullpath : trace[1].getFileName(),
+ linenum : trace[1].getLineNumber(),
+ basename : path.basename (trace[1].getFileName())
+ };
+
+ if (dbgLevel >= 5) {
+ console.log("%s:%d", info.fullpath, info.linenum);
+ console.log("\t[%d] %j", dbgLevel, message);
+ }
+ else console.log("--%d-- [%s:%d] -- %j", dbgLevel, info.basename, info.linenum, message);
+ }
+};
+
+module.exports = dbgLevel;