How to use mangos own login to secure an AngularJS custom dashboard
-
Thank you a lot. I've used your templates for creating the dashboard so 'mango-3.0/bootstrap' was a remnant from before copying app.js over. The login page isn't required to be standard for mango so it should be for the dashboard only.
export.js is there for my custom directives. I'll copy it's content here
/** * @copyright 2016 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved. * @author Jared Wiltshire */ define([ 'angular', 'mango-3.0/maMaterialDashboards' ], function(angular, maMaterialDashboards) { 'use strict'; var myApp = angular.module('myApp', ['maMaterialDashboards']); myApp.run(['$rootScope', function($rootScope) { $rootScope.pi = function() { return Math.PI; } }]); myApp.directive('myCustomComponent', function() { return { restrict: 'E', scope: { name: '@' }, template: '<span>Hello {{name}}!</span>' }; }); myApp.controller('myCtrl',['$scope', $scope.exportData = function(values){ $scope.list = []; for (var i = 0, len = values.length; i < len; i++) { $scope.d = new Date(values*.timestamp); $scope.list.splice(0,0,{"timestamp":$scope.d.toLocaleString(),"value":values*.value}); }; }; }]); angular.element(document).ready(function() { angular.bootstrap(document.documentElement, ['myApp']); }); }); // define
I haven't done any changes to app.js, although it looks like I should change the "state: 'dashboard.home'" to point to the directory I have my index.html in?
Here is the content of app.js
/** * @copyright 2016 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved. * @author Jared Wiltshire */ define([ 'angular', './directives/menu/menuLink', './directives/menu/menuToggle', './directives/login/login', 'mango-3.0/maMaterialDashboards', 'mango-3.0/maAppComponents', 'angular-ui-router', 'angular-loading-bar' ], function(angular, menuLink, menuToggle, login, maMaterialDashboards, maAppComponents) { 'use strict'; var myAdminApp = angular.module('myAdminApp', [ 'ui.router', 'angular-loading-bar', 'maMaterialDashboards', 'maAppComponents', 'ngMessages' ]); myAdminApp .directive('menuLink', menuLink) .directive('menuToggle', menuToggle) .directive('login', login); myAdminApp.constant('PAGES', [ { state: 'dashboard', url: '/dashboard', templateUrl: 'views/dashboard/main.html', resolve: { auth: ['$rootScope', 'User', function($rootScope, User) { $rootScope.user = User.current(); return $rootScope.user.$promise; }] } }, { state: 'login', url: '/login', templateUrl: 'views/login.html' }, { state: 'dashboard.home', url: '/home', templateUrl: 'views/dashboard/home.html', menuTr: 'dashboards.v3.dox.home', menuIcon: 'fa fa-home', menuType: 'link' }, { state: 'dashboard.apiErrors', url: '/api-errors', templateUrl: 'views/dashboard/errors.html', menuTr: 'dashboards.v3.dox.apiErrors' }, { state: 'dashboard.section1', url: '/section-1', menuText: 'Section 1', menuIcon: 'fa fa-building', menuType: 'toggle', children: [ { state: 'dashboard.section1.page1', templateUrl: 'views/section1/page1.html', url: '/page-1', menuText: 'Page 1', menuType: 'link' }, { state: 'dashboard.section1.page2', templateUrl: 'views/section1/page2.html', url: '/page-2', menuText: 'Page 2', menuType: 'link' } ] }, { state: 'dashboard.section2', url: '/section-2', menuText: 'Section 2', menuIcon: 'fa fa-bolt', menuType: 'toggle', children: [ { state: 'dashboard.section2.page1', templateUrl: 'views/section2/page1.html', url: '/page-1', menuText: 'Page 1', menuType: 'link' }, { state: 'dashboard.section2.page2', templateUrl: 'views/section2/page2.html', url: '/page-2', menuText: 'Page 2', menuType: 'link' } ] } ]); myAdminApp.config([ 'PAGES', '$stateProvider', '$urlRouterProvider', '$httpProvider', '$mdThemingProvider', '$injector', function(PAGES, $stateProvider, $urlRouterProvider, $httpProvider, $mdThemingProvider, $injector) { $mdThemingProvider .theme('default') .primaryPalette('yellow') .accentPalette('red'); $httpProvider.interceptors.push('errorInterceptor'); $urlRouterProvider.otherwise('/dashboard/home'); addStates(PAGES); function addStates(pages, parent) { angular.forEach(pages, function(page, area) { if (page.state) { var state = { url: page.url } if (page.menuTr) { state.menuTr = page.menuTr; } if (page.menuText) { state.menuText = page.menuText; } if (parent) { state.parentPage = parent; } if (page.templateUrl) { state.templateUrl = page.templateUrl; } else { state.template = '<div ui-view></div>'; state['abstract'] = true; } if (page.resolve) { state.resolve = page.resolve; } $stateProvider.state(page.state, state); } addStates(page.children, page); }); } }]); myAdminApp.run([ 'PAGES', '$rootScope', '$state', '$timeout', '$mdSidenav', '$mdColors', '$MD_THEME_CSS', function(PAGES, $rootScope, $state, $timeout, $mdSidenav, $mdColors, $MD_THEME_CSS) { $rootScope.pages = PAGES; $rootScope.Math = Math; // inserts a style tag to style <a> tags with accent color if ($MD_THEME_CSS) { var acc = $mdColors.getThemeColor('accent-500-1.0'); var accT = $mdColors.getThemeColor('accent-500-0.2'); var accD = $mdColors.getThemeColor('accent-700-1.0'); var styleContent = 'a:not(.md-button) {color: ' + acc +'; border-bottom-color: ' + accT + ';}\n' + 'a:not(.md-button):hover, a:not(.md-button):focus {color: ' + accD + '; border-bottom-color: ' + accD + ';}\n'; var style = document.createElement('style'); style.appendChild(document.createTextNode(styleContent)); document.head.appendChild(style); } $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) { if (error && (error.status === 401 || error.status === 403)) { event.preventDefault(); $state.loginRedirect = toState; $state.go('login'); } }); $rootScope.$on("$stateChangeSuccess", function(event, toState, toParams, fromState, fromParams) { var crumbs = []; var state = toState; do { if (state.menuTr) { crumbs.unshift({maTr: state.menuTr}); } else if (state.menuText) { crumbs.unshift({text: state.menuText}); } } while (state = state.parentPage); $rootScope.crumbs = crumbs; }); $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) { if ($state.includes('dashboard')) { $rootScope.closeMenu(); } }); $rootScope.closeMenu = function() { $mdSidenav('left').close(); } $rootScope.openMenu = function() { angular.element('#menu-button').blur(); $mdSidenav('left').open(); } }]); angular.element(document).ready(function() { angular.bootstrap(document.documentElement, ['myAdminApp']); }); }); // define
-
So what you need to do is to combine the contents of export.js and app.js, at the moment you are trying to bootstrap two separate AngularJS apps on the same element (document.documentElement) which wont work.
Either migrate your controller to app.js and remove export.js or migrate the login stuff as per my previous post into export.js and remove app.js. Decided which way to go based on whether you wish to keep the menu and toolbar or not.
-
Here is my app.js if it is of any assistance:
/** * @copyright 2016 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved. * @author Jared Wiltshire */ define([ 'angular', './directives/login/login', 'mango-3.0/maMaterialDashboards', 'mango-3.0/maAppComponents', 'angular-ui-router', 'angular-loading-bar' ], function(angular, login, maMaterialDashboards, maAppComponents) { 'use strict'; var myApp = angular.module('myApp', [ 'ui.router', 'angular-loading-bar', 'maMaterialDashboards', 'maAppComponents', 'ngMessages' ]); myApp .directive('login', login); myApp.constant('PAGES', [ { state: 'dashboard', url: '/dashboard', templateUrl: 'views/dashboard/main.html', resolve: { auth: ['$rootScope', 'User', function($rootScope, User) { $rootScope.user = User.current(); return $rootScope.user.$promise; }] } }, { state: 'login', url: '/login', templateUrl: 'views/login.html' }, { state: 'dashboard.home', url: '/home', templateUrl: 'views/dashboard/home.html' } ]); myApp.config([ 'PAGES', '$stateProvider', '$urlRouterProvider', '$httpProvider', '$mdThemingProvider', '$injector', function(PAGES, $stateProvider, $urlRouterProvider, $httpProvider, $mdThemingProvider, $injector) { $mdThemingProvider.theme('default') .dark() .primaryPalette('orange') .accentPalette('light-blue') .warnPalette('blue'); $httpProvider.interceptors.push('errorInterceptor'); $urlRouterProvider.otherwise('/dashboard/home'); addStates(PAGES); function addStates(pages, parent) { angular.forEach(pages, function(page, area) { if (page.state) { var state = { url: page.url } if (parent) { state.parentPage = parent; } if (page.templateUrl) { state.templateUrl = page.templateUrl; } else { state.template = '<div ui-view></div>'; state['abstract'] = true; } if (page.resolve) { state.resolve = page.resolve; } $stateProvider.state(page.state, state); } addStates(page.children, page); }); } }]); myApp.run([ 'PAGES', '$rootScope', '$state', '$timeout', '$mdSidenav', '$mdColors', '$MD_THEME_CSS', function(PAGES, $rootScope, $state, $timeout, $mdSidenav, $mdColors, $MD_THEME_CSS) { $rootScope.pages = PAGES; $rootScope.Math = Math; $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) { if (error && (error.status === 401 || error.status === 403)) { event.preventDefault(); $state.loginRedirect = toState; $state.go('login'); } }); }]); angular.element(document).ready(function() { angular.bootstrap(document.documentElement, ['myApp']); }); }); // define
I took out all of the menu portions for the side nav and the extra pages. My dashboard is only one page at the moment.
-
Thank you a lot for your reference. I tried copying your app.js and see if it would work. It does the redirect to /#/login when the user is not logged in but the page shows my index.html for some reason. I'll upload a picture about the situation.
My index.html is the same it was before with the exception that the only require line I have includes the app.js.
-
Here is how my pages are structured:
index.html
<!DOCTYPE html> <html lang="en" class="no-js"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>Demo</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes"> <link rel="icon" type="image/png" sizes="192x192" href="../img/icon192.png"> <link rel="icon" type="image/png" sizes="128x128" href="../img/icon128.png"> <link rel="apple-touch-icon" type="image/png" sizes="128x128" href="../img/icon128.png"> <link rel="apple-touch-icon" type="image/png" sizes="128x128" href="../img/icon128.png"> <link rel="manifest" href="manifest.json"> <link rel="stylesheet" href="/resources/angular-csp.css"></link> <link rel="stylesheet" href="../vendor/angular-material/angular-material.css"> <link rel="stylesheet" href="../vendor/angular-loading-bar/loading-bar.css"> <link rel="stylesheet" href="../vendor/material-design-icons/iconfont/material-icons.css"> <link rel="stylesheet" href="../vendor/font-awesome/css/font-awesome.css"> <link rel="stylesheet" href="../vendor/mdPickers/mdPickers.css"> <link rel="stylesheet" href="../vendor/angular-material-data-table/md-data-table.css"> <link rel="stylesheet" href="styles/main.css"> </head> <body layout="column"> <div ng-if="appLoading" class="app-loading"> <i class="fa fa-cog fa-spin"></i> </div> <div ui-view ng-cloak layout="column" flex></div> <script src="/resources/require.js"></script> <script src="/resources/loaderConfig.js"></script> <script src="../js/loaderConfig.js"></script> <script>require(['dashboards/Demo/app']);</script> </body> </html>
main.html
<div ui-view flex="noshrink"></div>
You could do away with the main.html if you rework the code a bit, but I expect to use it further along in my dashboard development.
The home.html page holds all of my dashboard content. So in order to make it work with my app.js, copy the <body> portion of your index.html into /views/dashboard/home.html and reduce the main.html to something similar to above.
-
Your home.html would look something like this:
<div id="maincontainer" ng-cloak> <header> <img src="images/jamkfi_tunnus_sininen_suomi.png" alt="jamk.fi" style="width:50%;height:50%;margin-left:25%;"> </header> <div id="textContainer"> <img src="images/header.jpg" alt="Fosfaattifosfori PO4-P [µg/l]" style="width:50%;height:50%;margin-left:25%;"> </div> <div> <ma-get-point-value point-xid="DP_355369" point="point1"></ma-get-point-value> <ma-point-values id="testi" point="point1" values="point1Values" from="from" to="to" rollup="AVERAGE" rollup-interval="1 minutes"> </ma-point-values> <ma-serial-chart id="chartti" style="height: 500px; width: 100%" series-1-values="point1Values" series-1-point="point1" options="{chartCursor:{categoryBalloonDateFormat:'YYYY-MM-DD HH:NN'}, chartScrollbar:{oppositeAxis: false, scrollbarHeight: 40,offset: 25, graph:'DP_355369'}}"> </ma-serial-chart> <div id = "control"> <div id="selectorDiv" layout="row"> <div class="selectorContainer"> <md-input-container flex="33" class="no-errors-spacer timeSelector"> <label>Date preset</label> <ma-date-range-picker from="from" to="to" preset="LAST_1_DAYS" update-interval="1 minutes"></ma-date-range-picker> </md-input-container> </div> <div class="selectorContainer"> <md-input-container flex="33" class="no-errors-spacer timeSelector"> <label>From date</label> <ma-date-picker ng-model="from"></ma-date-picker> </md-input-container> </div> <div class="selectorContainer"> <md-input-container flex="33" class="no-errors-spacer timeSelector"> <label>To date</label> <ma-date-picker ng-model="to"></ma-date-picker> </md-input-container> </div> </div> <div> <div> <md-button class="md-raised" ng-click="showStats=true;showData=false" ng-hide="showStats">Statistics</md-button> <md-button class="md-raised" ng-click="showStats=false;showData=false" ng-show="showStats">Statistics</md-button> <md-button class="md-raised" ng-click="showData=true;showStats=false" ng-hide="showData">Data</md-button> <md-button class="md-raised" ng-click="showData=false;showStats=false" ng-show="showData">Data</md-button> </div> <div ng-controller="myCtrl"> <md-button class="md-raised" ng-click="testi()">Push me</md-button> </div> <div layout="row" ng-show="showStats" class="ng-hide"> <ma-point-statistics point="point1" from="from" to="to" statistics="statsObj"></ma-point-statistics> <ma-statistics-table id="statsTable" statistics="statsObj"></ma-statistics-table> </div> <div layout="row" ng-show="showData" class="ng-hide"> <table width="100%" height="500px"> <thead> <tr> <th>Value</th> <th>Time</th> </tr> </thead> <tbody> <tr ng-repeat="item in point1Values"> <td>{{item.value}}</td> <td>{{item.timestamp | moment:'format':'lll' }}</td> </tr> </tbody> </table> </div> </div> </div> </div> </div>
-
Thank you a lot brad! Thanks to your reference I got it working. I really can't thank you enough for sharing your experience here!
I also got a lot clearer idea of the new dashboard module thanks to this discussion.
So to summarize all the steps that a newbie like me had to take to get authentication work in my custom dashboard:
- Copy the files in adminTemplate (found at {mango root}\web\modules\dashboards\web) to my custom dashboard directory
- Change the content of my app.js directory to reflect brads.
- Copy the html data between the <body> tags in my old index.html to home.html found in views/dasboard/ directory
- Replace my old index.html file with adminTemplate index.html with the require line modified to point to my dashboard (eg. <script>require(['dashboards/fosfaattifosfori/app']);</script>)
- Changed my main.html to reflect what brad posted earlier
I believe steps 2 and 5 aren't necessary to get the thing working, but without those I'd have the side nav and the extra pages.
Thank you a lot for all the help you provided!
-
Thanks for posting up your pages and helping out Brad!
I've added an example for a simple "Single Page App" with a login page using UI Router to the next version of the dashboards module. You can have a look here for reference in the meantime - https://github.com/infiniteautomation/ma-dashboards/tree/master/Custom Dashboards/web/loginPageTemplate
-
@Jared-Wiltshire the link https://github.com/infiniteautomation/ma-dashboards/tree/master/Custom Dashboards/web/loginPageTemplate isn't working atm :)
-