diff options
Diffstat (limited to 'app')
72 files changed, 4557 insertions, 0 deletions
diff --git a/app/assets/images/icon-power.svg b/app/assets/images/icon-power.svg new file mode 100644 index 0000000..6c534e1 --- /dev/null +++ b/app/assets/images/icon-power.svg @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + x="0px" y="0px" viewBox="0 0 75 75" xml:space="preserve"> +<path fill="#3C6DF0" d="M17,14.6L17,14.6c1.2-1,1.3-2.7,0.3-3.9c-1-1.1-2.7-1.3-3.9-0.2C1.8,20.6-2.3,36.8,3.1,51.2 + S22.3,75.1,37.6,75C53,74.9,66.7,65.3,72,50.9c5.3-14.4,1-30.6-10.6-40.6c-2.8-2.4-6.3,1.8-3.5,4.1c7,5.9,11,14.7,11,23.8 + c0,17.3-14,31.3-31.3,31.3S6.2,55.5,6.2,38.2C6.2,29.2,10.1,20.5,17,14.6z"/> +<path fill="#3C6DF0" d="M40.2,40.8V2.7c0-3.6-5.4-3.6-5.4,0v38.1c0,1.5,1.2,2.8,2.7,2.8C39,43.6,40.3,42.3,40.2,40.8z"/> +</svg> diff --git a/app/assets/images/icon-refresh-white.svg b/app/assets/images/icon-refresh-white.svg new file mode 100644 index 0000000..1543cad --- /dev/null +++ b/app/assets/images/icon-refresh-white.svg @@ -0,0 +1 @@ +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52.18 51.91"><defs><style>.cls-1{fill:#fff;}</style></defs><title>icon-refresh-white</title><path class="cls-1" d="M38,20.77H52v-14H48.3v6.07A26,26,0,1,0,48.7,38H44.32a22.14,22.14,0,1,1,1.8-20.92H38v3.67Z" transform="translate(0.16 0.14)"/></svg>
\ No newline at end of file diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg new file mode 100644 index 0000000..d0fa158 --- /dev/null +++ b/app/assets/images/logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 241.23 240.05"><defs><style>.cls-1{fill:#a6a8ab;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}.cls-4{fill:url(#linear-gradient-3);}.cls-5{fill:url(#linear-gradient-4);}.cls-6{fill:#626366;}</style><linearGradient id="linear-gradient" x1="82.9" y1="11.55" x2="82.9" y2="154.54" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00b0da"/><stop offset="1" stop-color="#008abf"/></linearGradient><linearGradient id="linear-gradient-2" x1="81.55" y1="27.55" x2="81.55" y2="158.66" xlink:href="#linear-gradient"/><linearGradient id="linear-gradient-3" x1="156.66" y1="51.54" x2="156.66" y2="154.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a5d440"/><stop offset="1" stop-color="#8cce3f"/></linearGradient><linearGradient id="linear-gradient-4" x1="158.41" y1="51.54" x2="158.41" y2="154.8" xlink:href="#linear-gradient-3"/></defs><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M241.23,205.77a18.66,18.66,0,1,0-.24,16L237.26,220a14.51,14.51,0,1,1,.21-12.46Z"/><path class="cls-2" d="M65.85,81.86a53.68,53.68,0,0,0,11.61,33.41c-.1.29-.15.6-.22.9a10.81,10.81,0,0,0-.34,2.57,11,11,0,1,0,11-11,10.75,10.75,0,0,0-1.2.07c-.31,0-.61.08-.91.13A42.82,42.82,0,0,1,99.95,43.86h0V2.07l-.77.21q-3.63.94-7.12,2.2c-1.29.47-2.58,1-3.84,1.48h0V38.19l-.13.1A53.79,53.79,0,0,0,65.85,81.86Z"/><path class="cls-3" d="M120.28,96.58a14.54,14.54,0,0,1-14.55-14.37H93.59v0a26.29,26.29,0,0,0,21,25.65v45.35A71.13,71.13,0,0,1,63.9,38.1c.31.06.63.1,1,.13s.64,0,1,0a10.83,10.83,0,1,0-10.25-7.41,82.23,82.23,0,0,0,64.18,133.6c1.41,0,2.81-.06,4.2-.14l1.63-.09h0V95.57A14.47,14.47,0,0,1,120.28,96.58Z"/><path class="cls-4" d="M171.95,68.54a53.78,53.78,0,0,0-9.85-19.71,11.31,11.31,0,0,0,.32-1.3,10.78,10.78,0,0,0,.24-2.17,11,11,0,1,0-8.89,10.8,42.83,42.83,0,0,1-14.17,64.08V162c1.08-.27,2.14-.56,3.2-.87a82.35,82.35,0,0,0,8.53-3V125.91a53.91,53.91,0,0,0,20.6-57.37Z"/><path class="cls-5" d="M184.63,132.75A82.21,82.21,0,0,0,119.79,0c-1.64,0-3.26.06-4.87.16h-.11V68.55h0A14.53,14.53,0,0,1,120,67.48h.27A14.56,14.56,0,0,1,134.87,82s0,.07,0,.11,0,.08,0,.13h11.08A26.21,26.21,0,0,0,125.81,56.8V11.3A71.14,71.14,0,0,1,176,125.83h-.07a11,11,0,0,0-12.58,10.88,11,11,0,0,0,11,11h0a11,11,0,0,0,10.54-14.13C184.82,133.3,184.73,133,184.63,132.75Z"/><polygon class="cls-1" points="201.22 231.42 201.22 195.65 196.53 195.65 182 225.14 167.47 195.65 162.72 195.65 162.72 231.42 166.89 231.42 166.89 204.21 180.33 231.42 183.67 231.42 197.1 204.21 197.1 231.42 201.22 231.42"/><path class="cls-6" d="M119.82,208.4a10.6,10.6,0,0,0-7.9-3.34,10.15,10.15,0,0,0-4.16.83,15.94,15.94,0,0,0-3.62,2.24v-2.7H99.91v26h4.23V216.64a7.74,7.74,0,0,1,2.08-5.5,7.48,7.48,0,0,1,10.66,0,7.76,7.76,0,0,1,2.08,5.48v14.78h4.11v-15a11.12,11.12,0,0,0-3.24-8"/><path class="cls-6" d="M63.68,224.4a12.41,12.41,0,0,1-4.86,5.17,13.54,13.54,0,0,1-7,1.85H45.54V240h-4V205.43H51.64a13.41,13.41,0,0,1,9.57,3.76,12.73,12.73,0,0,1,2.47,15.21m-18.14,3.24h5.77A9.48,9.48,0,0,0,58.05,225a8.59,8.59,0,0,0,2.76-6.54,8.38,8.38,0,0,0-2.7-6.41,9.43,9.43,0,0,0-6.68-2.51H45.54Z"/><path class="cls-6" d="M96.44,219.75a4.56,4.56,0,0,0,.14-1.36c0-7.38-6.27-13.36-14-13.36s-14,6-14,13.36,6.27,13.36,14,13.36a14,14,0,0,0,11.93-6.52l-3.25-2.45a9.89,9.89,0,0,1-8.68,5,9.43,9.43,0,1,1,0-18.83,9.8,9.8,0,0,1,9.35,6.54c0,.07.15.63.17.7H76.59v3.52Z"/><path class="cls-6" d="M33.19,213.53A14.53,14.53,0,1,1,18.66,199a14.53,14.53,0,0,1,14.53,14.53m4.12,0a18.66,18.66,0,1,0-18.66,18.66,18.66,18.66,0,0,0,18.66-18.66"/><path class="cls-1" d="M154.37,220.69a6.77,6.77,0,0,1-6.75,6.79H132.14V213.9h15.49a6.78,6.78,0,0,1,6.75,6.79m-2.29-15.93a5.08,5.08,0,0,1-5.05,5.08l-14.89,0V199.67H147a5.07,5.07,0,0,1,5.05,5.08m.94,6.7a9,9,0,0,0-5.69-15.75H128v35.75h20.14l.28,0v0A10.73,10.73,0,0,0,153,211.46"/></g></g></svg>
\ No newline at end of file diff --git a/app/common/directives/app-header.html b/app/common/directives/app-header.html new file mode 100644 index 0000000..5b2de3d --- /dev/null +++ b/app/common/directives/app-header.html @@ -0,0 +1,19 @@ +<div id="header__wrapper"> +<!-- HEADER --> +<!-- Class of status-light__good has 3 states: status-light__good, status-light__error, status-light__disabled --> +<!-- Server Power buttton links to 'server power operations' page; Server Status may link to event log page. TBD.--> +<header id="header"> + <span class="app__version float-left">{{dataService.app_version}}</span> + <a href="" class="header__logout" ng-click="logout()">Log out</a> +</header> +<div class="header__functions-wrapper" role="heading"> + <div class="logo__wrapper"><img src="assets/images/logo.svg" id="header__logo" alt="company logo"/></div> + <button id="header__server-name">{{dataService.server_id}}</button> + <div class="header__functions"> + <a href="" id="header__server-health">Server health<span class="status-light__error">{{dataService.server_health}}</span></a> + <a href="#/overview/power-operations" class="header__server-power" role="button">Server power<span ng-class="{'status-light__error': dataService.server_state == 'Off', 'status-light__disabled': dataService.server_state == 'Unreachable', 'status-light__good': dataService.server_state == 'Running', 'status-light__warn': dataService.server_state == 'Quiesced'}">{{dataService.server_state}}</span></a> + <p class="header__refresh">Page last refreshed <span>{{dataService.last_updated |date:'h:mm:ss MMM dd yyyy'}}</span></p> + <button class="header__page-refresh" ng-click="refresh()"><img src="assets/images/icon-refresh-white.svg" alt="refresh page" role="button"/></button> + </div> +</div> +</div> diff --git a/app/common/directives/app-header.js b/app/common/directives/app-header.js new file mode 100644 index 0000000..23038a1 --- /dev/null +++ b/app/common/directives/app-header.js @@ -0,0 +1,57 @@ +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.directives') + .directive('appHeader', ['APIUtils', function (APIUtils) { + return { + 'restrict': 'E', + 'templateUrl': 'common/directives/app-header.html', + 'scope': { + 'path': '=' + }, + 'controller': ['$rootScope', '$scope','dataService', 'userModel', '$location', function($rootScope, $scope, dataService, userModel, $location){ + $scope.dataService = dataService; + + $scope.loadServerStatus = function(){ + if(!userModel.isLoggedIn()){ + //@TODO:some error message? + return; + } + APIUtils.getHostState(function(status){ + if(status == 'xyz.openbmc_project.State.Host.HostState.Off'){ + dataService.setPowerOffState(); + }else if(status == 'xyz.openbmc_project.State.Host.HostState.Running'){ + dataService.setPowerOnState(); + }else{ + dataService.setBootingState(); + } + }); + } + $scope.loadServerStatus(); + + $scope.logout = function(){ + userModel.logout(function(status, error){ + if(status){ + $location.path('/logout'); + }else{ + console.log(error); + } + }); + } + + $scope.refresh = function(){ + $scope.loadServerStatus(); + } + + var loginListener = $rootScope.$on('user-logged-in', function(event, arg){ + $scope.loadServerStatus(); + }); + + $scope.$on('$destroy', function(){ + loginListener(); + }); + }] + }; + }]); +})(window.angular); diff --git a/app/common/directives/app-navigation.html b/app/common/directives/app-navigation.html new file mode 100644 index 0000000..f2c6ed0 --- /dev/null +++ b/app/common/directives/app-navigation.html @@ -0,0 +1,70 @@ +<nav> + <ul id="nav__top-level" ng-style="navStyle"> + <li> + <button class="btn-overview" ng-class="{opened: firstLevel == 'overview'}" ng-click="firstLevel = 'overview';" tabindex="1"> + <svg version="1.1" class="nav__icon" x="0px" y="0px" + viewBox="0 0 20 20"> + <g> + <path d="M7.5,12.5v5.8H1.7v-5.8H7.5 M8.5,11.5H0.7v7.8h7.8V11.5L8.5,11.5z"/> + </g> + <g> + <path d="M18,12.5v5.8h-5.8v-5.8H18 M19,11.5h-7.8v7.8H19L19,11.5C19,11.5,19,11.5,19,11.5L19,11.5z"/> + </g> + <g> + <path d="M7.5,2v5.8H1.7V2H7.5 M8.5,1H0.7v7.8h7.8V1L8.5,1z"/> + </g> + <g> + <path d="M18,2v5.8h-5.8V2H18 M19,1h-7.8v7.8H19V1L19,1z"/> + </g> + </svg> + + <span class="">Overview</span></button> + </li> + <li> + <button class="btn-settings" ng-class="{opened: firstLevel == 'settings'}" ng-click="firstLevel = 'settings';" tabindex="9"> + <svg class="nav__icon" viewBox="0 0 20 20"> + <path d="M17.498,11.697c-0.453-0.453-0.704-1.055-0.704-1.697c0-0.642,0.251-1.244,0.704-1.697c0.069-0.071,0.15-0.141,0.257-0.22c0.127-0.097,0.181-0.262,0.137-0.417c-0.164-0.558-0.388-1.093-0.662-1.597c-0.075-0.141-0.231-0.22-0.391-0.199c-0.13,0.02-0.238,0.027-0.336,0.027c-1.325,0-2.401-1.076-2.401-2.4c0-0.099,0.008-0.207,0.027-0.336c0.021-0.158-0.059-0.316-0.199-0.391c-0.503-0.274-1.039-0.498-1.597-0.662c-0.154-0.044-0.32,0.01-0.416,0.137c-0.079,0.106-0.148,0.188-0.22,0.257C11.244,2.956,10.643,3.207,10,3.207c-0.642,0-1.244-0.25-1.697-0.704c-0.071-0.069-0.141-0.15-0.22-0.257C7.987,2.119,7.821,2.065,7.667,2.109C7.109,2.275,6.571,2.497,6.07,2.771C5.929,2.846,5.85,3.004,5.871,3.162c0.02,0.129,0.027,0.237,0.027,0.336c0,1.325-1.076,2.4-2.401,2.4c-0.098,0-0.206-0.007-0.335-0.027C3.001,5.851,2.845,5.929,2.77,6.07C2.496,6.572,2.274,7.109,2.108,7.667c-0.044,0.154,0.01,0.32,0.137,0.417c0.106,0.079,0.187,0.148,0.256,0.22c0.938,0.936,0.938,2.458,0,3.394c-0.069,0.072-0.15,0.141-0.256,0.221c-0.127,0.096-0.181,0.262-0.137,0.416c0.166,0.557,0.388,1.096,0.662,1.596c0.075,0.143,0.231,0.221,0.392,0.199c0.129-0.02,0.237-0.027,0.335-0.027c1.325,0,2.401,1.076,2.401,2.402c0,0.098-0.007,0.205-0.027,0.334C5.85,16.996,5.929,17.154,6.07,17.23c0.501,0.273,1.04,0.496,1.597,0.66c0.154,0.047,0.32-0.008,0.417-0.137c0.079-0.105,0.148-0.186,0.22-0.256c0.454-0.453,1.055-0.703,1.697-0.703c0.643,0,1.244,0.25,1.697,0.703c0.071,0.07,0.141,0.15,0.22,0.256c0.073,0.098,0.188,0.152,0.307,0.152c0.036,0,0.073-0.004,0.109-0.016c0.558-0.164,1.096-0.387,1.597-0.66c0.141-0.076,0.22-0.234,0.199-0.393c-0.02-0.129-0.027-0.236-0.027-0.334c0-1.326,1.076-2.402,2.401-2.402c0.098,0,0.206,0.008,0.336,0.027c0.159,0.021,0.315-0.057,0.391-0.199c0.274-0.5,0.496-1.039,0.662-1.596c0.044-0.154-0.01-0.32-0.137-0.416C17.648,11.838,17.567,11.77,17.498,11.697 M16.671,13.334c-0.059-0.002-0.114-0.002-0.168-0.002c-1.749,0-3.173,1.422-3.173,3.172c0,0.053,0.002,0.109,0.004,0.166c-0.312,0.158-0.64,0.295-0.976,0.406c-0.039-0.045-0.077-0.086-0.115-0.123c-0.601-0.6-1.396-0.93-2.243-0.93s-1.643,0.33-2.243,0.93c-0.039,0.037-0.077,0.078-0.116,0.123c-0.336-0.111-0.664-0.248-0.976-0.406c0.002-0.057,0.004-0.113,0.004-0.166c0-1.75-1.423-3.172-3.172-3.172c-0.054,0-0.11,0-0.168,0.002c-0.158-0.312-0.293-0.639-0.405-0.975c0.044-0.039,0.085-0.078,0.124-0.115c1.236-1.236,1.236-3.25,0-4.486C3.009,7.719,2.969,7.68,2.924,7.642c0.112-0.336,0.247-0.664,0.405-0.976C3.387,6.668,3.443,6.67,3.497,6.67c1.75,0,3.172-1.423,3.172-3.172c0-0.054-0.002-0.11-0.004-0.168c0.312-0.158,0.64-0.293,0.976-0.405C7.68,2.969,7.719,3.01,7.757,3.048c0.6,0.6,1.396,0.93,2.243,0.93s1.643-0.33,2.243-0.93c0.038-0.039,0.076-0.079,0.115-0.123c0.336,0.112,0.663,0.247,0.976,0.405c-0.002,0.058-0.004,0.114-0.004,0.168c0,1.749,1.424,3.172,3.173,3.172c0.054,0,0.109-0.002,0.168-0.004c0.158,0.312,0.293,0.64,0.405,0.976c-0.045,0.038-0.086,0.077-0.124,0.116c-0.6,0.6-0.93,1.396-0.93,2.242c0,0.847,0.33,1.645,0.93,2.244c0.038,0.037,0.079,0.076,0.124,0.115C16.964,12.695,16.829,13.021,16.671,13.334 M10,5.417c-2.528,0-4.584,2.056-4.584,4.583c0,2.529,2.056,4.584,4.584,4.584s4.584-2.055,4.584-4.584C14.584,7.472,12.528,5.417,10,5.417 M10,13.812c-2.102,0-3.812-1.709-3.812-3.812c0-2.102,1.71-3.812,3.812-3.812c2.102,0,3.812,1.71,3.812,3.812C13.812,12.104,12.102,13.812,10,13.812"></path> + </svg> + <span>Settings</span></button> + </li> + <li> + <button class="btn-multi" ng-class="{opened: firstLevel == 'multi_system'}" ng-click="firstLevel = 'multi_system';"> + <svg class="nav__icon" viewBox="0 0 20 20"> + <path d="M10,1.529c-4.679,0-8.471,3.792-8.471,8.471c0,4.68,3.792,8.471,8.471,8.471c4.68,0,8.471-3.791,8.471-8.471C18.471,5.321,14.68,1.529,10,1.529 M10,17.579c-4.18,0-7.579-3.399-7.579-7.579S5.82,2.421,10,2.421S17.579,5.82,17.579,10S14.18,17.579,10,17.579 M14.348,10c0,0.245-0.201,0.446-0.446,0.446h-5c-0.246,0-0.446-0.201-0.446-0.446s0.2-0.446,0.446-0.446h5C14.146,9.554,14.348,9.755,14.348,10 M14.348,12.675c0,0.245-0.201,0.446-0.446,0.446h-5c-0.246,0-0.446-0.201-0.446-0.446s0.2-0.445,0.446-0.445h5C14.146,12.229,14.348,12.43,14.348,12.675 M7.394,10c0,0.245-0.2,0.446-0.446,0.446H6.099c-0.245,0-0.446-0.201-0.446-0.446s0.201-0.446,0.446-0.446h0.849C7.194,9.554,7.394,9.755,7.394,10 M7.394,12.675c0,0.245-0.2,0.446-0.446,0.446H6.099c-0.245,0-0.446-0.201-0.446-0.446s0.201-0.445,0.446-0.445h0.849C7.194,12.229,7.394,12.43,7.394,12.675 M14.348,7.325c0,0.246-0.201,0.446-0.446,0.446h-5c-0.246,0-0.446-0.2-0.446-0.446c0-0.245,0.2-0.446,0.446-0.446h5C14.146,6.879,14.348,7.08,14.348,7.325 M7.394,7.325c0,0.246-0.2,0.446-0.446,0.446H6.099c-0.245,0-0.446-0.2-0.446-0.446c0-0.245,0.201-0.446,0.446-0.446h0.849C7.194,6.879,7.394,7.08,7.394,7.325"></path> + </svg> + <span>Multi System</span></button> + </li> + <li> + <button class="btn-help" ng-class="{opened: firstLevel == 'help'}" ng-click="firstLevel = 'help';"> + <svg class="nav__icon" viewBox="0 0 20 20"> + <path class="nav__icon-help__outer" d="M10,0.9c-5,0-9,4-9,9s4,9,9,9s9-4,9-9S14.9,0.9,10,0.9"/> + <path class="nav__icon-help__inner" d="M9.2,5.4V3.7h1.5v1.8H9.2z M9.2,16.1v-9h1.5v9H9.2z"/> + </svg> + <span>Help</span></button> + </li> + </ul> + <ul class="nav__second-level btn-overview" ng-style="navStyle" ng-class="{opened: firstLevel == 'overview'}"> + <li ng-class="{'active': (path == '/overview/system')}" tabindex="2"><a href="#/system-overview">System Overview</a></li> + <li ng-class="{'active': (path == '/overview/power-operations')}" tabindex="3"><a href="#/overview/power-operations">Server power operations</a></li> + <li ng-class="{'active': (path == '/overview/power-consumption')}"><a href="" tabindex="4">Power consumption</a></li> + <li ng-class="{'active': (path == '/overview/remote-console')}"><a href="" tabindex="5">Remote console</a></li> + <li ng-class="{'active': (path == '/overview/unit-id')}"><a href="#/overview/unit-id" tabindex="6">Unit ID</a></li> + <li ng-class="{'active': (path == '/overview/log')}"><a href="#/overview/log" tabindex="7">Log</a></li> + <li ng-class="{'active': (path == '/overview/bmc-reboot')}"><a href="#/overview/bmc-reboot" tabindex="8">Reboot BMC</a></li> + </ul> + <ul class="nav__second-level btn-settings" ng-style="navStyle" ng-class="{opened: firstLevel == 'settings'}"> + <li><a href="">2nd level 4</a></li> + <li><a href="">2nd level 5</a></li> + <li><a href="">2nd level 6</a></li> + </ul> + <ul class="nav__second-level btn-multi" ng-style="navStyle" ng-class="{opened: firstLevel == 'multi_system'}"> + <li><a href="">2nd level 7</a></li> + <li><a href="">2nd level 8</a></li> + <li><a href="">2nd level 9</a></li> + </ul> + <ul class="nav__second-level btn-help" ng-style="navStyle" ng-class="{opened: firstLevel == 'help'}"> + <li><a href="">2nd level 10</a></li> + <li><a href="">2nd level 212</a></li> + <li><a href="">2nd level 312</a></li> + </ul> +</nav>
\ No newline at end of file diff --git a/app/common/directives/app-navigation.js b/app/common/directives/app-navigation.js new file mode 100644 index 0000000..d09588a --- /dev/null +++ b/app/common/directives/app-navigation.js @@ -0,0 +1,28 @@ +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.directives') + .directive('appNavigation', function () { + return { + 'restrict': 'E', + 'templateUrl': 'common/directives/app-navigation.html', + 'scope': { + 'path': '=', + 'showNavigation': '=' + }, + 'controller': ['$scope', 'dataService', function($scope, dataService){ + $scope.$watch('showNavigation', function(){ + var paddingTop = 0; + $scope.firstLevel = 'overview'; + $scope.secondLevel = 'system_overview'; + if($scope.showNavigation){ + paddingTop = document.getElementById('header__wrapper').offsetHeight; + } + dataService.bodyStyle = {'padding-top': paddingTop + 'px'}; + $scope.navStyle = {'top': paddingTop + 'px'}; + }); + }] + }; + }); +})(window.angular); diff --git a/app/common/directives/confirm.html b/app/common/directives/confirm.html new file mode 100644 index 0000000..a38cf60 --- /dev/null +++ b/app/common/directives/confirm.html @@ -0,0 +1,10 @@ +<div class="inline__confirm" ng-class="{active: confirm}"> + <div class="inline__confirm-message"> + <p class="h3"><i></i>Are you sure you want to <strong>{{title}}?</strong></p> + <p>{{message}}</p> + </div> + <div class="inline__confirm-buttons"> + <button class="btn-primary" ng-click="accept()">Yes</button> + <button class="btn-primary" ng-click="cancel()">No</button> + </div> +</div>
\ No newline at end of file diff --git a/app/common/directives/confirm.js b/app/common/directives/confirm.js new file mode 100644 index 0000000..ed8c9dc --- /dev/null +++ b/app/common/directives/confirm.js @@ -0,0 +1,39 @@ +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.directives') + .directive('confirm', ['$timeout', function($timeout){ + return { + 'restrict': 'E', + 'templateUrl': 'common/directives/confirm.html', + 'scope': { + 'title': '@', + 'message': '@', + 'confirm': '=', + 'callback': '=' + }, + 'controller': ['$scope',function($scope){ + $scope.cancel = function(){ + $scope.confirm = false; + $scope.$parent.confirm = false; + }; + $scope.accept = function(){ + $scope.callback(); + $scope.cancel(); + } + }], + link: function(scope, e) { + scope.$watch('confirm', function(){ + if(scope.confirm){ + $timeout(function(){ + angular.element(e[0].parentNode).css({'min-height': e[0].querySelector('.inline__confirm').offsetHeight + 'px'}); + }, 0); + }else{ + angular.element(e[0].parentNode).css({'min-height': 0+ 'px'}); + } + }); + } + }; + }]); +})(window.angular); diff --git a/app/common/directives/index.js b/app/common/directives/index.js new file mode 100644 index 0000000..1fed678 --- /dev/null +++ b/app/common/directives/index.js @@ -0,0 +1,9 @@ +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.directives', [ + 'app.common.services' + ]); + +})(window.angular); diff --git a/app/common/filters/index.js b/app/common/filters/index.js new file mode 100644 index 0000000..d1b4b44 --- /dev/null +++ b/app/common/filters/index.js @@ -0,0 +1,7 @@ +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.filters', []); + +})(window.angular); diff --git a/app/common/services/api-utils.js b/app/common/services/api-utils.js new file mode 100644 index 0000000..af66f81 --- /dev/null +++ b/app/common/services/api-utils.js @@ -0,0 +1,252 @@ +/** + * API utilities service + * + * @module app/common/services/api-utils + * @exports APIUtils + * @name APIUtils + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + angular + .module('app.common.services') + .factory('APIUtils', ['$http', 'Constants', function($http, Constants){ + var SERVICE = { + LOGIN_CREDENTIALS: Constants.LOGIN_CREDENTIALS, + API_CREDENTIALS: Constants.API_CREDENTIALS, + API_RESPONSE: Constants.API_RESPONSE, + CHASSIS_POWER_STATE: Constants.CHASSIS_POWER_STATE, + HOST_STATE_TEXT: Constants.HOST_STATE, + HOST_STATE: Constants.HOST_STATE, + getChassisState: function(callback){ + $http({ + method: 'GET', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/chassis0", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + callback(content.data.CurrentPowerState); + }).error(function(error){ + console.log(error); + }); + }, + getHostState: function(callback){ + $http({ + method: 'GET', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + callback(content.data.CurrentHostState); + }).error(function(error){ + console.log(error); + }); + }, + login: function(username, password, callback){ + $http({ + method: 'POST', + url: SERVICE.API_CREDENTIALS.host + "/login", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": [username, password]}) + }).success(function(response){ + if(callback){ + callback(response); + } + }).error(function(error){ + if(callback){ + callback(null, true); + } + console.log(error); + }); + }, + logout: function(callback){ + $http({ + method: 'POST', + url: SERVICE.API_CREDENTIALS.host + "/logout", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": []}) + }).success(function(response){ + if(callback){ + callback(response); + } + }).error(function(error){ + if(callback){ + callback(null, error); + } + console.log(error); + }); + }, + chassisPowerOn: function(callback){ + $http({ + method: 'POST', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": []}) + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + if(callback){ + return callback(content.data.CurrentPowerState); + } + }).error(function(error){ + if(callback){ + callback(error); + }else{ + console.log(error); + } + }); + }, + chassisPowerOff: function(callback){ + $http({ + method: 'POST', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": []}) + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + if(callback){ + return callback(content.data.CurrentPowerState); + } + }).error(function(error){ + if(callback){ + callback(error); + }else{ + console.log(error); + } + }); + }, + hostPowerOn: function(callback){ + /** + curl -c cjar -b cjar -k -H "Content-Type: application/json" -d + "{\"data\": \"xyz.openbmc_project.State.Host.Transition.Off\"}" + -X PUT + https://9.3.164.147/xyz/openbmc_project/state/host0/attr/RequestedHostTransition + **/ + $http({ + method: 'PUT', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0/attr/RequestedHostTransition", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": "xyz.openbmc_project.State.Host.Transition.On"}) + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + if(callback){ + return callback(content.status); + } + }).error(function(error){ + if(callback){ + callback(error); + }else{ + console.log(error); + } + }); + }, + hostPowerOff: function(callback){ + $http({ + method: 'PUT', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0/attr/RequestedHostTransition", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": "xyz.openbmc_project.State.Host.Transition.Off"}) + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + if(callback){ + return callback(content.status); + } + }).error(function(error){ + if(callback){ + callback(error); + }else{ + console.log(error); + } + }); + }, + hostReboot: function(callback){ + $http({ + method: 'POST', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": []}), + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + if(callback){ + return callback(content); + } + }).error(function(error){ + if(callback){ + callback(error); + }else{ + console.log(error); + } + }); + }, + hostShutdown: function(callback){ + $http({ + method: 'POST', + url: SERVICE.API_CREDENTIALS.host + "/xyz/openbmc_project/state/host0", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + withCredentials: true, + data: JSON.stringify({"data": []}) + }).success(function(response){ + var json = JSON.stringify(response); + var content = JSON.parse(json); + if(callback){ + return callback(content); + } + }).error(function(error){ + if(callback){ + callback(error); + }else{ + console.log(error); + } + }); + } + }; + return SERVICE; + }]); + + })(window.angular); diff --git a/app/common/services/apiInterceptor.js b/app/common/services/apiInterceptor.js new file mode 100644 index 0000000..5a715ec --- /dev/null +++ b/app/common/services/apiInterceptor.js @@ -0,0 +1,46 @@ +/** + * api Interceptor + * + * @module app/common/services/apiInterceptor + * @exports apiInterceptor + * @name apiInterceptor + + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.services') + .service('apiInterceptor', ['$q', '$rootScope', 'dataService', function($q, $rootScope, dataService){ + return { + 'request': function(config){ + dataService.server_unreachable = false; + dataService.loading = true; + return config; + }, + 'response': function(response){ + dataService.loading = false; + dataService.last_updated = new Date(); + + if(response == null){ + dataService.server_unreachable = true; + } + + if(response && response.status == 'error' && + dataService.path != '/login'){ + $rootScope.$emit('timedout-user', {}); + } + + return response; + }, + 'responseError': function(rejection){ + dataService.server_unreachable = true; + dataService.loading = false; + return $q.reject(rejection); + } + }; + }]); + +})(window.angular);
\ No newline at end of file diff --git a/app/common/services/constants.js b/app/common/services/constants.js new file mode 100644 index 0000000..b98d5d6 --- /dev/null +++ b/app/common/services/constants.js @@ -0,0 +1,50 @@ +/** + * common Constant service + * + * @module app/common/services/constants + * @exports Constants + * @name Constants + + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.services') + .service('Constants', function () { + return { + LOGIN_CREDENTIALS: { + username: "test", + password: "testpass", + }, + API_CREDENTIALS: { + host: 'https://9.3.164.147' + }, + API_RESPONSE: { + ERROR_STATUS: 'error', + ERROR_MESSAGE: '401 Unauthorized', + SUCCESS_STATUS: 'ok', + SUCCESS_MESSAGE: '200 OK' + }, + CHASSIS_POWER_STATE: { + on: 'On', + off: 'Off' + }, + HOST_STATE_TEXT: { + on: 'Running', + off: 'Off', + booting: 'Quiesced', + unreachable: 'Unreachable' + }, + HOST_STATE: { + on: 1, + off: -1, + booting: 0, + unreachable: -2 + } + }; + }); + +})(window.angular);
\ No newline at end of file diff --git a/app/common/services/dataService.js b/app/common/services/dataService.js new file mode 100644 index 0000000..704df75 --- /dev/null +++ b/app/common/services/dataService.js @@ -0,0 +1,53 @@ +/** + * data service + * + * @module app/common/services/dataService + * @exports dataService + * @name dataService + + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.services') + .service('dataService', ['Constants', function (Constants) { + this.app_version = "openBMC V.0.0.1"; + this.server_health = 'Error'; + this.server_state = 'Unreachable'; + this.server_status = -2; + this.chassis_state = 'On'; + this.server_id = "Server 9.3.164.147"; + this.last_updated = new Date(); + + this.loading = false; + this.server_unreachable = false; + this.loading_message = ""; + this.showNavigation = false; + this.bodyStyle = {}; + this.path = ''; + + this.setPowerOnState = function(){ + this.server_state = Constants.HOST_STATE_TEXT.on; + this.server_status = Constants.HOST_STATE.on; + }, + + this.setPowerOffState = function(){ + this.server_state = Constants.HOST_STATE_TEXT.off; + this.server_status = Constants.HOST_STATE.off; + }, + + this.setBootingState = function(){ + this.server_state = Constants.HOST_STATE_TEXT.booting; + this.server_status = Constants.HOST_STATE.booting; + }, + + this.setUnreachableState = function(){ + this.server_state = Constants.HOST_STATE_TEXT.unreachable; + this.server_status = Constants.HOST_STATE.unreachable; + } + }]); + +})(window.angular);
\ No newline at end of file diff --git a/app/common/services/index.js b/app/common/services/index.js new file mode 100644 index 0000000..f54f6eb --- /dev/null +++ b/app/common/services/index.js @@ -0,0 +1,18 @@ +/** + * A module to contain common services + * + * @module app/common/services/index + * @exports app/common/services/index + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.services', [ + // Dependencies + // Basic resources + ]); + +})(window.angular); diff --git a/app/common/services/userModel.js b/app/common/services/userModel.js new file mode 100644 index 0000000..e44ba8c --- /dev/null +++ b/app/common/services/userModel.js @@ -0,0 +1,49 @@ +/** + * userModel + * + * @module app/common/services/userModel + * @exports userModel + * @name userModel + + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.common.services') + .service('userModel', ['APIUtils',function(APIUtils){ + return { + login : function(username, password, callback){ + APIUtils.login(username, password, function(response, error){ + if(response && + response.status == APIUtils.API_RESPONSE.SUCCESS_STATUS){ + sessionStorage.setItem('LOGIN_ID', username); + callback(true); + }else{ + callback(false, error); + } + }); + }, + isLoggedIn : function(){ + if(sessionStorage.getItem('LOGIN_ID') === null){ + return false; + } + return true; + }, + logout : function(callback){ + APIUtils.logout(function(response, error){ + if(response && + response.status == APIUtils.API_RESPONSE.SUCCESS_STATUS){ + sessionStorage.removeItem('LOGIN_ID'); + callback(true); + }else{ + callback(false, error); + } + }); + } + }; + }]); + +})(window.angular);
\ No newline at end of file diff --git a/app/common/styles/base/buttons.scss b/app/common/styles/base/buttons.scss new file mode 100644 index 0000000..7bad975 --- /dev/null +++ b/app/common/styles/base/buttons.scss @@ -0,0 +1,90 @@ +button, .button, input[type="submit"], .submit { + font-weight: bold; + font-size: 1em; + text-transform: none; + border-radius: 3px; + padding: .8rem 3.5rem .5rem; + height: auto; + border: 0; + &:hover { + cursor: pointer; + } +} + +.disabled { + button, .button, input[type="submit"] { + opacity: 0.2; + color: $btn__disabled-txt; + &:hover { + cursor: default; + background: transparent; + } + } +} + +.btn-primary, +input[type="submit"] { + color: $primebtn__text; + background: $primebtn__bg; + min-height: 50px; + &:hover { + background: lighten($primebtn__bg, 8%); + @include fastTransition-all; + } + &.disabled { + background: $btn__disabled-bg; + color: $btn__disabled-txt; + @include fastTransition-all; + &:hover { + cursor: default; + } + } + i { // button symbol + font-style: normal; + text-transform: none; + font-size: 1.5em; + transform: rotate(80deg); + display: inline-block; + } + img{ + width: 18px; + height: 18px; + display: inline-block; + } +} +.btn-secondary { + color: $secbtn__text; + background: transparent; + border: 2px solid $secbtn__border; + min-height: 50px; + &:hover { + background: $lightbg__accent; + cursor: pointer; + background: $secbtn__bg; + @include fastTransition-all; + } + &.disabled { + border: 2px solid $lightgrey; + background: $btn__disabled-bg; + @include fastTransition-all; + &:hover { + background: $btn__disabled-bg; + } + } + i { // button symbol + font-style: normal; + font-weight: 400; + text-transform: none; + font-size: 1.5em; + transform: rotate(80deg) translate(-2px); + display: inline-block; + vertical-align: middle; + } + img{ + width: 18px; + height: 18px; + display: inline-block; + margin-right: .5em; + margin-top: -3px; + } +} diff --git a/app/common/styles/base/colors.scss b/app/common/styles/base/colors.scss new file mode 100644 index 0000000..94ef663 --- /dev/null +++ b/app/common/styles/base/colors.scss @@ -0,0 +1,50 @@ +// Global +$white: #ffffff; +$black: #333; +$darkgrey: #666; +$lightgrey: #ccc; +$field__disabled: #d8d8d8; +$btn__disabled-txt: #a6a5a6; +$btn__disabled-bg: #d8d8d8; + +// Dark background +$darkbg__grey: #E3ECEC; +$darkbg__accent: #79a6f6; +$darkbg__primary: #19273c; +$active: #648FFF; + +// Light Background +$lightbg__grey: #b8c1c1; +$lightbg__accent: #d8e2fc; +$lightbg__primary: #3c6df0; + +// Primary Button colors +$primebtn__bg: #3c6df0; +$primebtn__text: $white; + +// Secondary Button colors +$secbtn__bg: #ebf0fc; +$secbtn__border: #3f71ec; +$secbtn__text: #3f71ec; + + +// Alerts +$alert__error: rgba(230, 35, 37, 0.3); +$alert__warning: rgba(255, 127, 0, 0.3); +$alert__message: rgba(203, 221, 235, 0.5); + +// Links +$links: #648FFF; +$links__hover: $lightbg__accent; +$links__visited: #5A3EC8; +$links__disabled: rgba(27, 40, 52, 0.70); + +// Navigation +$nav__top-level-color: #1a273b; +$nav__second-level-color: #e6e9ed; + +// Status colors +$error-color: #FF5C49; +$status-ok: #34bc6e; +$status-ok-light: #bcefce; +$status-warn: #ffb000;
\ No newline at end of file diff --git a/app/common/styles/base/core.scss b/app/common/styles/base/core.scss new file mode 100644 index 0000000..13ba9e6 --- /dev/null +++ b/app/common/styles/base/core.scss @@ -0,0 +1,24 @@ +html, body { + height: 100%; + @include fontFamily; + font-size: 16px; + color: $black; +} + +p { + margin-bottom: .8em; + transition: margin .05s; +} + +a { + color: $links; + &:visited { + color: $links__visited; + } + &:hover { + color: $links__hover; + } + :focus { + color: $links; + } +}
\ No newline at end of file diff --git a/app/common/styles/base/forms.scss b/app/common/styles/base/forms.scss new file mode 100644 index 0000000..f899e0c --- /dev/null +++ b/app/common/styles/base/forms.scss @@ -0,0 +1,129 @@ +label, legend { + font-size:1em; + font-weight: 300; + margin: 0; + &.disabled { + color: $lightbg__grey; + } + .error { + font-size: .9em; + } +} + +input[type='email'], +input[type='number'], +input[type='password'], +input[type='search'], +input[type='tel'], +input[type='url'], +input[type='text'], +textarea { + border-radius: 0px; + border: 1px solid $lightgrey; + height: 3.1em; + margin: 0 0 1em 0; + background: $white; + box-shadow: 0 0 0; + transition: none !important; + &:focus { + border-color: $lightbg__accent; + border-bottom: 5px solid $lightbg__primary; + } + &:disabled, .disabled { + background: $field__disabled; + border: 1px solid $lightbg__grey; + } + &.input__error { + border-color: $error-color; + } +} + +textarea { padding: .2em; + height: auto;} +input[type="submit"], .submit { + margin: 1em 0; + width: 100%; +} + +//Custom select +select{ + border-radius: 0px; + height:auto; + @include fastTransition-all; + @include bgImage__arrowDown-primary; + &:focus { + border-color: $lightbg__accent; + @include bgImage__arrowDown-accent; + box-shadow: 0 0 0; + } +} + +//Custom Checkbox and Radio +.checkbox-custom, +.radio-custom { + opacity: 0; + position: absolute; +} + +.checkbox-custom, .checkbox-custom-label, +.radio-custom, +.radio-custom-label { + display: inline-block; + vertical-align: middle; + margin: .1em 0 .1em -.5em; + padding: .5em; + cursor: pointer; +} + +.checkbox-custom-label, .radio-custom-label { + position: relative; + padding-left: 2em; + +} + +.checkbox-custom + .checkbox-custom-label:before, +.radio-custom + .radio-custom-label:before { + content: ''; + background: #fff; + border: 1px solid $lightbg__grey; + display: inline-block; + vertical-align: middle; + width: 16px; + height: 16px; + margin-right: 10px; + margin-top: -3px; + text-align: center; + position: absolute; + left: 9px; + top: 15px; +} + +.checkbox-custom:checked + .checkbox-custom-label:after { + content: '\2713'; + position: absolute; + left: 10px; + top: 3px; + font-size: 1em; + padding: 4px 0 0 2px; + color: $white; +} + +.checkbox-custom:checked + .checkbox-custom-label:before { + background: $lightbg__primary; + @include fastTransition-all; +} + +.radio-custom + .radio-custom-label:before { + border-radius: 50%; +} + +.radio-custom:checked + .radio-custom-label:before { + background: $lightbg__primary; + @include fastTransition-all; + box-shadow: inset 0px 0px 0px 2px #fff; +} + + +.checkbox-custom:focus + .checkbox-custom-label, .radio-custom:focus + .radio-custom-label { + outline: 1px solid $lightbg__grey; /* focus style */ +} diff --git a/app/common/styles/base/foundation b/app/common/styles/base/foundation new file mode 100644 index 0000000..0247f7f --- /dev/null +++ b/app/common/styles/base/foundation @@ -0,0 +1,1567 @@ +/** + * Foundation for Sites by ZURB + * Version 6.3.0 + * foundation.zurb.com + * Licensed under MIT Open Source + */ +/*! normalize-scss | MIT/GPLv2 License | bit.ly/normalize-scss */ +/* Document + ========================================================================== */ +/** + * 1. Change the default font family in all browsers (opinionated). + * 2. Correct the line height in all browsers. + * 3. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ +html { + font-family: sans-serif; + /* 1 */ + line-height: 1.15; + /* 2 */ + -ms-text-size-adjust: 100%; + /* 3 */ + -webkit-text-size-adjust: 100%; + /* 3 */ } + +/* Sections + ========================================================================== */ +/** + * Remove the margin in all browsers (opinionated). + */ +body { + margin: 0; } + +/** + * Add the correct display in IE 9-. + */ +article, +aside, +footer, +header, +nav, +section { + display: block; } + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ +h1 { + font-size: 2em; + margin: 0.67em 0; } + +/* Grouping content + ========================================================================== */ +/** + * Add the correct display in IE 9-. + */ +figcaption, +figure { + display: block; } + +/** + * Add the correct margin in IE 8. + */ +figure { + margin: 1em 40px; } + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ +hr { + box-sizing: content-box; + /* 1 */ + height: 0; + /* 1 */ + overflow: visible; + /* 2 */ } + +/** + * Add the correct display in IE. + */ +main { + display: block; } + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ +pre { + font-family: monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ } + +/* Links + ========================================================================== */ +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ +a { + background-color: transparent; + /* 1 */ + -webkit-text-decoration-skip: objects; + /* 2 */ } + +/** + * Remove the outline on focused links when they are also active or hovered + * in all browsers (opinionated). + */ +a:active, +a:hover { + outline-width: 0; } + +/* Text-level semantics + ========================================================================== */ +/** + * 1. Remove the bottom border in Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ +abbr[title] { + border-bottom: none; + /* 1 */ + text-decoration: underline; + /* 2 */ + text-decoration: underline dotted; + /* 2 */ } + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ +b, +strong { + font-weight: inherit; } + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ +b, +strong { + font-weight: bolder; } + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ +code, +kbd, +samp { + font-family: monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ } + +/** + * Add the correct font style in Android 4.3-. + */ +dfn { + font-style: italic; } + +/** + * Add the correct background and color in IE 9-. + */ +mark { + background-color: #ff0; + color: #000; } + +/** + * Add the correct font size in all browsers. + */ +small { + font-size: 80%; } + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sub { + bottom: -0.25em; } + +sup { + top: -0.5em; } + +/* Embedded content + ========================================================================== */ +/** + * Add the correct display in IE 9-. + */ +audio, +video { + display: inline-block; } + +/** + * Add the correct display in iOS 4-7. + */ +audio:not([controls]) { + display: none; + height: 0; } + +/** + * Remove the border on images inside links in IE 10-. + */ +img { + border-style: none; } + +/** + * Hide the overflow in IE. + */ +svg:not(:root) { + overflow: hidden; } + +/* Forms + ========================================================================== */ +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ + margin: 0; + /* 2 */ } + +/** + * Show the overflow in IE. + */ +button { + overflow: visible; } + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ +button, +select { + /* 1 */ + text-transform: none; } + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; + /* 2 */ } + +button, +[type="button"], +[type="reset"], +[type="submit"] { + /** + * Remove the inner border and padding in Firefox. + */ + /** + * Restore the focus styles unset by the previous rule. + */ } +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; } +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; } + +/** + * Show the overflow in Edge. + */ +input { + overflow: visible; } + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; } + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ +[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ + /** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ } +[type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ } + +/** + * Change the border, margin, and padding in all browsers (opinionated). + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ +legend { + box-sizing: border-box; + /* 1 */ + display: table; + /* 1 */ + max-width: 100%; + /* 1 */ + padding: 0; + /* 3 */ + color: inherit; + /* 2 */ + white-space: normal; + /* 1 */ } + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ +progress { + display: inline-block; + /* 1 */ + vertical-align: baseline; + /* 2 */ } + +/** + * Remove the default vertical scrollbar in IE. + */ +textarea { + overflow: auto; } + +/* Interactive + ========================================================================== */ +/* + * Add the correct display in Edge, IE, and Firefox. + */ +details { + display: block; } + +/* + * Add the correct display in all browsers. + */ +summary { + display: list-item; } + +/* + * Add the correct display in IE 9-. + */ +menu { + display: block; } + +/* Scripting + ========================================================================== */ +/** + * Add the correct display in IE 9-. + */ +canvas { + display: inline-block; } + +/** + * Add the correct display in IE. + */ +template { + display: none; } + +/* Hidden + ========================================================================== */ +/** + * Add the correct display in IE 10-. + */ +[hidden] { + display: none; } + +.foundation-mq { + font-family: "small=0em&medium=40em&large=64em&xlarge=75em&xxlarge=90em"; } + +html { + box-sizing: border-box; + font-size: 100%; } + +*, +*::before, +*::after { + box-sizing: inherit; } + +body { + margin: 0; + padding: 0; + background: #fefefe; + font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + font-weight: normal; + line-height: 1.5; + color: #0a0a0a; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } + +img { + display: inline-block; + vertical-align: middle; + max-width: 100%; + height: auto; + -ms-interpolation-mode: bicubic; } + +textarea { + height: auto; + min-height: 50px; + border-radius: 3px; } + +select { + width: 100%; + border-radius: 3px; } + +.map_canvas img, +.map_canvas embed, +.map_canvas object, +.mqa-display img, +.mqa-display embed, +.mqa-display object { + max-width: none !important; } + +button { + padding: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 0; + border-radius: 3px; + background: transparent; + line-height: 1; } +[data-whatinput='mouse'] button { + outline: 0; } + +.is-visible { + display: block !important; } + +.is-hidden { + display: none !important; } + +[type='text'], [type='password'], [type='date'], [type='datetime'], [type='datetime-local'], [type='month'], [type='week'], [type='email'], [type='number'], [type='search'], [type='tel'], [type='time'], [type='url'], [type='color'], +textarea { + display: block; + box-sizing: border-box; + width: 100%; + height: 2.4375rem; + margin: 0 0 1rem; + padding: 0.5rem; + border: 1px solid #cacaca; + border-radius: 3px; + background-color: #fefefe; + box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1); + font-family: inherit; + font-size: 1rem; + font-weight: normal; + color: #0a0a0a; + transition: box-shadow 0.5s, border-color 0.25s ease-in-out; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } +[type='text']:focus, [type='password']:focus, [type='date']:focus, [type='datetime']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='week']:focus, [type='email']:focus, [type='number']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='url']:focus, [type='color']:focus, +textarea:focus { + outline: none; + border: 1px solid #8a8a8a; + background-color: #fefefe; + box-shadow: 0 0 5px #cacaca; + transition: box-shadow 0.5s, border-color 0.25s ease-in-out; } + +textarea { + max-width: 100%; } +textarea[rows] { + height: auto; } + +input::-webkit-input-placeholder, +textarea::-webkit-input-placeholder { + color: #cacaca; } + +input::-moz-placeholder, +textarea::-moz-placeholder { + color: #cacaca; } + +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: #cacaca; } + +input::placeholder, +textarea::placeholder { + color: #cacaca; } + +input:disabled, input[readonly], +textarea:disabled, +textarea[readonly] { + background-color: #e6e6e6; + cursor: not-allowed; } + +[type='submit'], +[type='button'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 3px; } + +input[type='search'] { + box-sizing: border-box; } + +[type='file'], +[type='checkbox'], +[type='radio'] { + margin: 0 0 1rem; } + +[type='checkbox'] + label, +[type='radio'] + label { + display: inline-block; + vertical-align: baseline; + margin-left: 0.5rem; + margin-right: 1rem; + margin-bottom: 0; } +[type='checkbox'] + label[for], +[type='radio'] + label[for] { + cursor: pointer; } + +label > [type='checkbox'], +label > [type='radio'] { + margin-right: 0.5rem; } + +[type='file'] { + width: 100%; } + +label { + display: block; + margin: 0; + font-size: 0.875rem; + font-weight: normal; + line-height: 1.8; + color: #0a0a0a; } +label.middle { + margin: 0 0 1rem; + padding: 0.5625rem 0; } + +.help-text { + margin-top: -0.5rem; + font-size: 0.8125rem; + font-style: italic; + color: #0a0a0a; } + +.input-group { + display: table; + width: 100%; + margin-bottom: 1rem; } +.input-group > :first-child { + border-radius: 3px 0 0 3px; } +.input-group > :last-child > * { + border-radius: 0 3px 3px 0; } + +.input-group-label, .input-group-field, .input-group-button, .input-group-button a, +.input-group-button input, +.input-group-button button, +.input-group-button label { + margin: 0; + white-space: nowrap; + display: table-cell; + vertical-align: middle; } + +.input-group-label { + padding: 0 1rem; + border: 1px solid #cacaca; + background: #e6e6e6; + color: #0a0a0a; + text-align: center; + white-space: nowrap; + width: 1%; + height: 100%; } +.input-group-label:first-child { + border-right: 0; } +.input-group-label:last-child { + border-left: 0; } + +.input-group-field { + border-radius: 0; + height: 2.5rem; } + +.input-group-button { + padding-top: 0; + padding-bottom: 0; + text-align: center; + width: 1%; + height: 100%; } +.input-group-button a, +.input-group-button input, +.input-group-button button, +.input-group-button label { + height: 2.5rem; + padding-top: 0; + padding-bottom: 0; + font-size: 1rem; } + +.input-group .input-group-button { + display: table-cell; } + +fieldset { + margin: 0; + padding: 0; + border: 0; } + +legend { + max-width: 100%; + margin-bottom: 0.5rem; } + +.fieldset { + margin: 1.125rem 0; + padding: 1.25rem; + border: 1px solid #cacaca; } +.fieldset legend { + margin: 0; + margin-left: -0.1875rem; + padding: 0 0.1875rem; + background: #fefefe; } + +select { + height: 2.4375rem; + margin: 0 0 1rem; + padding: 0.5rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 1px solid #cacaca; + border-radius: 3px; + background-color: #fefefe; + font-family: inherit; + font-size: 1rem; + line-height: normal; + color: #0a0a0a; + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28138, 138, 138%29'></polygon></svg>"); + background-origin: content-box; + background-position: right -1rem center; + background-repeat: no-repeat; + background-size: 9px 6px; + padding-right: 1.5rem; + transition: box-shadow 0.5s, border-color 0.25s ease-in-out; } +@media screen and (min-width: 0\0) { + select { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg=="); } } +select:focus { + outline: none; + border: 1px solid #8a8a8a; + background-color: #fefefe; + box-shadow: 0 0 5px #cacaca; + transition: box-shadow 0.5s, border-color 0.25s ease-in-out; } +select:disabled { + background-color: #e6e6e6; + cursor: not-allowed; } +select::-ms-expand { + display: none; } +select[multiple] { + height: auto; + background-image: none; } + +.is-invalid-input:not(:focus) { + border-color: #c60f13; + background-color: #f8e6e7; } +.is-invalid-input:not(:focus)::-webkit-input-placeholder { + color: #c60f13; } +.is-invalid-input:not(:focus)::-moz-placeholder { + color: #c60f13; } +.is-invalid-input:not(:focus):-ms-input-placeholder { + color: #c60f13; } +.is-invalid-input:not(:focus)::placeholder { + color: #c60f13; } + +.is-invalid-label { + color: #c60f13; } + +.form-error { + display: none; + margin-top: -0.5rem; + margin-bottom: 1rem; + font-size: 0.75rem; + font-weight: bold; + color: #c60f13; } +.form-error.is-visible { + display: block; } + +.hide { + display: none !important; } + +.invisible { + visibility: hidden; } + +@media screen and (max-width: 39.9375em) { + .hide-for-small-only { + display: none !important; } } + +@media screen and (max-width: 0em), screen and (min-width: 40em) { + .show-for-small-only { + display: none !important; } } + +@media print, screen and (min-width: 40em) { + .hide-for-medium { + display: none !important; } } + +@media screen and (max-width: 39.9375em) { + .show-for-medium { + display: none !important; } } + +@media screen and (min-width: 40em) and (max-width: 63.9375em) { + .hide-for-medium-only { + display: none !important; } } + +@media screen and (max-width: 39.9375em), screen and (min-width: 64em) { + .show-for-medium-only { + display: none !important; } } + +@media print, screen and (min-width: 64em) { + .hide-for-large { + display: none !important; } } + +@media screen and (max-width: 63.9375em) { + .show-for-large { + display: none !important; } } + +@media screen and (min-width: 64em) and (max-width: 74.9375em) { + .hide-for-large-only { + display: none !important; } } + +@media screen and (max-width: 63.9375em), screen and (min-width: 75em) { + .show-for-large-only { + display: none !important; } } + +.show-for-sr, +.show-on-focus { + position: absolute !important; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); } + +.show-on-focus:active, .show-on-focus:focus { + position: static !important; + width: auto; + height: auto; + overflow: visible; + clip: auto; } + +.show-for-landscape, +.hide-for-portrait { + display: block !important; } +@media screen and (orientation: landscape) { + .show-for-landscape, + .hide-for-portrait { + display: block !important; } } +@media screen and (orientation: portrait) { + .show-for-landscape, + .hide-for-portrait { + display: none !important; } } + +.hide-for-landscape, +.show-for-portrait { + display: none !important; } +@media screen and (orientation: landscape) { + .hide-for-landscape, + .show-for-portrait { + display: none !important; } } +@media screen and (orientation: portrait) { + .hide-for-landscape, + .show-for-portrait { + display: block !important; } } + +.row { + max-width: 100rem; + margin-right: auto; + margin-left: auto; } +.row::before, .row::after { + display: table; + content: ' '; } +.row::after { + clear: both; } +.row.collapse > .column, .row.collapse > .columns { + padding-right: 0; + padding-left: 0; } +.row .row { + margin-right: -0.75rem; + margin-left: -0.75rem; } +@media print, screen and (min-width: 40em) { + .row .row { + margin-right: -0.75rem; + margin-left: -0.75rem; } } +@media print, screen and (min-width: 64em) { + .row .row { + margin-right: -0.75rem; + margin-left: -0.75rem; } } +.row .row.collapse { + margin-right: 0; + margin-left: 0; } +.row.expanded { + max-width: none; } +.row.expanded .row { + margin-right: auto; + margin-left: auto; } + +.column, .columns { + width: 100%; + float: left; + padding-right: 0.75rem; + padding-left: 0.75rem; } +.column:last-child:not(:first-child), .columns:last-child:not(:first-child) { + float: right; } +.column.end:last-child:last-child, .end.columns:last-child:last-child { + float: left; } + +.column.row.row, .row.row.columns { + float: none; } + +.row .column.row.row, .row .row.row.columns { + margin-right: 0; + margin-left: 0; + padding-right: 0; + padding-left: 0; } + +.small-1 { + width: 8.33333%; } + +.small-push-1 { + position: relative; + left: 8.33333%; } + +.small-pull-1 { + position: relative; + left: -8.33333%; } + +.small-offset-0 { + margin-left: 0%; } + +.small-2 { + width: 16.66667%; } + +.small-push-2 { + position: relative; + left: 16.66667%; } + +.small-pull-2 { + position: relative; + left: -16.66667%; } + +.small-offset-1 { + margin-left: 8.33333%; } + +.small-3 { + width: 25%; } + +.small-push-3 { + position: relative; + left: 25%; } + +.small-pull-3 { + position: relative; + left: -25%; } + +.small-offset-2 { + margin-left: 16.66667%; } + +.small-4 { + width: 33.33333%; } + +.small-push-4 { + position: relative; + left: 33.33333%; } + +.small-pull-4 { + position: relative; + left: -33.33333%; } + +.small-offset-3 { + margin-left: 25%; } + +.small-5 { + width: 41.66667%; } + +.small-push-5 { + position: relative; + left: 41.66667%; } + +.small-pull-5 { + position: relative; + left: -41.66667%; } + +.small-offset-4 { + margin-left: 33.33333%; } + +.small-6 { + width: 50%; } + +.small-push-6 { + position: relative; + left: 50%; } + +.small-pull-6 { + position: relative; + left: -50%; } + +.small-offset-5 { + margin-left: 41.66667%; } + +.small-7 { + width: 58.33333%; } + +.small-push-7 { + position: relative; + left: 58.33333%; } + +.small-pull-7 { + position: relative; + left: -58.33333%; } + +.small-offset-6 { + margin-left: 50%; } + +.small-8 { + width: 66.66667%; } + +.small-push-8 { + position: relative; + left: 66.66667%; } + +.small-pull-8 { + position: relative; + left: -66.66667%; } + +.small-offset-7 { + margin-left: 58.33333%; } + +.small-9 { + width: 75%; } + +.small-push-9 { + position: relative; + left: 75%; } + +.small-pull-9 { + position: relative; + left: -75%; } + +.small-offset-8 { + margin-left: 66.66667%; } + +.small-10 { + width: 83.33333%; } + +.small-push-10 { + position: relative; + left: 83.33333%; } + +.small-pull-10 { + position: relative; + left: -83.33333%; } + +.small-offset-9 { + margin-left: 75%; } + +.small-11 { + width: 91.66667%; } + +.small-push-11 { + position: relative; + left: 91.66667%; } + +.small-pull-11 { + position: relative; + left: -91.66667%; } + +.small-offset-10 { + margin-left: 83.33333%; } + +.small-12 { + width: 100%; } + +.small-offset-11 { + margin-left: 91.66667%; } + +.small-up-1 > .column, .small-up-1 > .columns { + float: left; + width: 100%; } +.small-up-1 > .column:nth-of-type(1n), .small-up-1 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-1 > .column:nth-of-type(1n+1), .small-up-1 > .columns:nth-of-type(1n+1) { + clear: both; } +.small-up-1 > .column:last-child, .small-up-1 > .columns:last-child { + float: left; } + +.small-up-2 > .column, .small-up-2 > .columns { + float: left; + width: 50%; } +.small-up-2 > .column:nth-of-type(1n), .small-up-2 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-2 > .column:nth-of-type(2n+1), .small-up-2 > .columns:nth-of-type(2n+1) { + clear: both; } +.small-up-2 > .column:last-child, .small-up-2 > .columns:last-child { + float: left; } + +.small-up-3 > .column, .small-up-3 > .columns { + float: left; + width: 33.33333%; } +.small-up-3 > .column:nth-of-type(1n), .small-up-3 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-3 > .column:nth-of-type(3n+1), .small-up-3 > .columns:nth-of-type(3n+1) { + clear: both; } +.small-up-3 > .column:last-child, .small-up-3 > .columns:last-child { + float: left; } + +.small-up-4 > .column, .small-up-4 > .columns { + float: left; + width: 25%; } +.small-up-4 > .column:nth-of-type(1n), .small-up-4 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-4 > .column:nth-of-type(4n+1), .small-up-4 > .columns:nth-of-type(4n+1) { + clear: both; } +.small-up-4 > .column:last-child, .small-up-4 > .columns:last-child { + float: left; } + +.small-up-5 > .column, .small-up-5 > .columns { + float: left; + width: 20%; } +.small-up-5 > .column:nth-of-type(1n), .small-up-5 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-5 > .column:nth-of-type(5n+1), .small-up-5 > .columns:nth-of-type(5n+1) { + clear: both; } +.small-up-5 > .column:last-child, .small-up-5 > .columns:last-child { + float: left; } + +.small-up-6 > .column, .small-up-6 > .columns { + float: left; + width: 16.66667%; } +.small-up-6 > .column:nth-of-type(1n), .small-up-6 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-6 > .column:nth-of-type(6n+1), .small-up-6 > .columns:nth-of-type(6n+1) { + clear: both; } +.small-up-6 > .column:last-child, .small-up-6 > .columns:last-child { + float: left; } + +.small-up-7 > .column, .small-up-7 > .columns { + float: left; + width: 14.28571%; } +.small-up-7 > .column:nth-of-type(1n), .small-up-7 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-7 > .column:nth-of-type(7n+1), .small-up-7 > .columns:nth-of-type(7n+1) { + clear: both; } +.small-up-7 > .column:last-child, .small-up-7 > .columns:last-child { + float: left; } + +.small-up-8 > .column, .small-up-8 > .columns { + float: left; + width: 12.5%; } +.small-up-8 > .column:nth-of-type(1n), .small-up-8 > .columns:nth-of-type(1n) { + clear: none; } +.small-up-8 > .column:nth-of-type(8n+1), .small-up-8 > .columns:nth-of-type(8n+1) { + clear: both; } +.small-up-8 > .column:last-child, .small-up-8 > .columns:last-child { + float: left; } + +.small-collapse > .column, .small-collapse > .columns { + padding-right: 0; + padding-left: 0; } + +.small-collapse .row { + margin-right: 0; + margin-left: 0; } + +.expanded.row .small-collapse.row { + margin-right: 0; + margin-left: 0; } + +.small-uncollapse > .column, .small-uncollapse > .columns { + padding-right: 0.75rem; + padding-left: 0.75rem; } + +.small-centered { + margin-right: auto; + margin-left: auto; } +.small-centered, .small-centered:last-child:not(:first-child) { + float: none; + clear: both; } + +.small-uncentered, +.small-push-0, +.small-pull-0 { + position: static; + float: left; + margin-right: 0; + margin-left: 0; } + +@media print, screen and (min-width: 40em) { + .medium-1 { + width: 8.33333%; } + .medium-push-1 { + position: relative; + left: 8.33333%; } + .medium-pull-1 { + position: relative; + left: -8.33333%; } + .medium-offset-0 { + margin-left: 0%; } + .medium-2 { + width: 16.66667%; } + .medium-push-2 { + position: relative; + left: 16.66667%; } + .medium-pull-2 { + position: relative; + left: -16.66667%; } + .medium-offset-1 { + margin-left: 8.33333%; } + .medium-3 { + width: 25%; } + .medium-push-3 { + position: relative; + left: 25%; } + .medium-pull-3 { + position: relative; + left: -25%; } + .medium-offset-2 { + margin-left: 16.66667%; } + .medium-4 { + width: 33.33333%; } + .medium-push-4 { + position: relative; + left: 33.33333%; } + .medium-pull-4 { + position: relative; + left: -33.33333%; } + .medium-offset-3 { + margin-left: 25%; } + .medium-5 { + width: 41.66667%; } + .medium-push-5 { + position: relative; + left: 41.66667%; } + .medium-pull-5 { + position: relative; + left: -41.66667%; } + .medium-offset-4 { + margin-left: 33.33333%; } + .medium-6 { + width: 50%; } + .medium-push-6 { + position: relative; + left: 50%; } + .medium-pull-6 { + position: relative; + left: -50%; } + .medium-offset-5 { + margin-left: 41.66667%; } + .medium-7 { + width: 58.33333%; } + .medium-push-7 { + position: relative; + left: 58.33333%; } + .medium-pull-7 { + position: relative; + left: -58.33333%; } + .medium-offset-6 { + margin-left: 50%; } + .medium-8 { + width: 66.66667%; } + .medium-push-8 { + position: relative; + left: 66.66667%; } + .medium-pull-8 { + position: relative; + left: -66.66667%; } + .medium-offset-7 { + margin-left: 58.33333%; } + .medium-9 { + width: 75%; } + .medium-push-9 { + position: relative; + left: 75%; } + .medium-pull-9 { + position: relative; + left: -75%; } + .medium-offset-8 { + margin-left: 66.66667%; } + .medium-10 { + width: 83.33333%; } + .medium-push-10 { + position: relative; + left: 83.33333%; } + .medium-pull-10 { + position: relative; + left: -83.33333%; } + .medium-offset-9 { + margin-left: 75%; } + .medium-11 { + width: 91.66667%; } + .medium-push-11 { + position: relative; + left: 91.66667%; } + .medium-pull-11 { + position: relative; + left: -91.66667%; } + .medium-offset-10 { + margin-left: 83.33333%; } + .medium-12 { + width: 100%; } + .medium-offset-11 { + margin-left: 91.66667%; } + .medium-up-1 > .column, .medium-up-1 > .columns { + float: left; + width: 100%; } + .medium-up-1 > .column:nth-of-type(1n), .medium-up-1 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-1 > .column:nth-of-type(1n+1), .medium-up-1 > .columns:nth-of-type(1n+1) { + clear: both; } + .medium-up-1 > .column:last-child, .medium-up-1 > .columns:last-child { + float: left; } + .medium-up-2 > .column, .medium-up-2 > .columns { + float: left; + width: 50%; } + .medium-up-2 > .column:nth-of-type(1n), .medium-up-2 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-2 > .column:nth-of-type(2n+1), .medium-up-2 > .columns:nth-of-type(2n+1) { + clear: both; } + .medium-up-2 > .column:last-child, .medium-up-2 > .columns:last-child { + float: left; } + .medium-up-3 > .column, .medium-up-3 > .columns { + float: left; + width: 33.33333%; } + .medium-up-3 > .column:nth-of-type(1n), .medium-up-3 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-3 > .column:nth-of-type(3n+1), .medium-up-3 > .columns:nth-of-type(3n+1) { + clear: both; } + .medium-up-3 > .column:last-child, .medium-up-3 > .columns:last-child { + float: left; } + .medium-up-4 > .column, .medium-up-4 > .columns { + float: left; + width: 25%; } + .medium-up-4 > .column:nth-of-type(1n), .medium-up-4 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-4 > .column:nth-of-type(4n+1), .medium-up-4 > .columns:nth-of-type(4n+1) { + clear: both; } + .medium-up-4 > .column:last-child, .medium-up-4 > .columns:last-child { + float: left; } + .medium-up-5 > .column, .medium-up-5 > .columns { + float: left; + width: 20%; } + .medium-up-5 > .column:nth-of-type(1n), .medium-up-5 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-5 > .column:nth-of-type(5n+1), .medium-up-5 > .columns:nth-of-type(5n+1) { + clear: both; } + .medium-up-5 > .column:last-child, .medium-up-5 > .columns:last-child { + float: left; } + .medium-up-6 > .column, .medium-up-6 > .columns { + float: left; + width: 16.66667%; } + .medium-up-6 > .column:nth-of-type(1n), .medium-up-6 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-6 > .column:nth-of-type(6n+1), .medium-up-6 > .columns:nth-of-type(6n+1) { + clear: both; } + .medium-up-6 > .column:last-child, .medium-up-6 > .columns:last-child { + float: left; } + .medium-up-7 > .column, .medium-up-7 > .columns { + float: left; + width: 14.28571%; } + .medium-up-7 > .column:nth-of-type(1n), .medium-up-7 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-7 > .column:nth-of-type(7n+1), .medium-up-7 > .columns:nth-of-type(7n+1) { + clear: both; } + .medium-up-7 > .column:last-child, .medium-up-7 > .columns:last-child { + float: left; } + .medium-up-8 > .column, .medium-up-8 > .columns { + float: left; + width: 12.5%; } + .medium-up-8 > .column:nth-of-type(1n), .medium-up-8 > .columns:nth-of-type(1n) { + clear: none; } + .medium-up-8 > .column:nth-of-type(8n+1), .medium-up-8 > .columns:nth-of-type(8n+1) { + clear: both; } + .medium-up-8 > .column:last-child, .medium-up-8 > .columns:last-child { + float: left; } + .medium-collapse > .column, .medium-collapse > .columns { + padding-right: 0; + padding-left: 0; } + .medium-collapse .row { + margin-right: 0; + margin-left: 0; } + .expanded.row .medium-collapse.row { + margin-right: 0; + margin-left: 0; } + .medium-uncollapse > .column, .medium-uncollapse > .columns { + padding-right: 0.75rem; + padding-left: 0.75rem; } + .medium-centered { + margin-right: auto; + margin-left: auto; } + .medium-centered, .medium-centered:last-child:not(:first-child) { + float: none; + clear: both; } + .medium-uncentered, + .medium-push-0, + .medium-pull-0 { + position: static; + float: left; + margin-right: 0; + margin-left: 0; } } + +@media print, screen and (min-width: 64em) { + .large-1 { + width: 8.33333%; } + .large-push-1 { + position: relative; + left: 8.33333%; } + .large-pull-1 { + position: relative; + left: -8.33333%; } + .large-offset-0 { + margin-left: 0%; } + .large-2 { + width: 16.66667%; } + .large-push-2 { + position: relative; + left: 16.66667%; } + .large-pull-2 { + position: relative; + left: -16.66667%; } + .large-offset-1 { + margin-left: 8.33333%; } + .large-3 { + width: 25%; } + .large-push-3 { + position: relative; + left: 25%; } + .large-pull-3 { + position: relative; + left: -25%; } + .large-offset-2 { + margin-left: 16.66667%; } + .large-4 { + width: 33.33333%; } + .large-push-4 { + position: relative; + left: 33.33333%; } + .large-pull-4 { + position: relative; + left: -33.33333%; } + .large-offset-3 { + margin-left: 25%; } + .large-5 { + width: 41.66667%; } + .large-push-5 { + position: relative; + left: 41.66667%; } + .large-pull-5 { + position: relative; + left: -41.66667%; } + .large-offset-4 { + margin-left: 33.33333%; } + .large-6 { + width: 50%; } + .large-push-6 { + position: relative; + left: 50%; } + .large-pull-6 { + position: relative; + left: -50%; } + .large-offset-5 { + margin-left: 41.66667%; } + .large-7 { + width: 58.33333%; } + .large-push-7 { + position: relative; + left: 58.33333%; } + .large-pull-7 { + position: relative; + left: -58.33333%; } + .large-offset-6 { + margin-left: 50%; } + .large-8 { + width: 66.66667%; } + .large-push-8 { + position: relative; + left: 66.66667%; } + .large-pull-8 { + position: relative; + left: -66.66667%; } + .large-offset-7 { + margin-left: 58.33333%; } + .large-9 { + width: 75%; } + .large-push-9 { + position: relative; + left: 75%; } + .large-pull-9 { + position: relative; + left: -75%; } + .large-offset-8 { + margin-left: 66.66667%; } + .large-10 { + width: 83.33333%; } + .large-push-10 { + position: relative; + left: 83.33333%; } + .large-pull-10 { + position: relative; + left: -83.33333%; } + .large-offset-9 { + margin-left: 75%; } + .large-11 { + width: 91.66667%; } + .large-push-11 { + position: relative; + left: 91.66667%; } + .large-pull-11 { + position: relative; + left: -91.66667%; } + .large-offset-10 { + margin-left: 83.33333%; } + .large-12 { + width: 100%; } + .large-offset-11 { + margin-left: 91.66667%; } + .large-up-1 > .column, .large-up-1 > .columns { + float: left; + width: 100%; } + .large-up-1 > .column:nth-of-type(1n), .large-up-1 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-1 > .column:nth-of-type(1n+1), .large-up-1 > .columns:nth-of-type(1n+1) { + clear: both; } + .large-up-1 > .column:last-child, .large-up-1 > .columns:last-child { + float: left; } + .large-up-2 > .column, .large-up-2 > .columns { + float: left; + width: 50%; } + .large-up-2 > .column:nth-of-type(1n), .large-up-2 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-2 > .column:nth-of-type(2n+1), .large-up-2 > .columns:nth-of-type(2n+1) { + clear: both; } + .large-up-2 > .column:last-child, .large-up-2 > .columns:last-child { + float: left; } + .large-up-3 > .column, .large-up-3 > .columns { + float: left; + width: 33.33333%; } + .large-up-3 > .column:nth-of-type(1n), .large-up-3 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-3 > .column:nth-of-type(3n+1), .large-up-3 > .columns:nth-of-type(3n+1) { + clear: both; } + .large-up-3 > .column:last-child, .large-up-3 > .columns:last-child { + float: left; } + .large-up-4 > .column, .large-up-4 > .columns { + float: left; + width: 25%; } + .large-up-4 > .column:nth-of-type(1n), .large-up-4 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-4 > .column:nth-of-type(4n+1), .large-up-4 > .columns:nth-of-type(4n+1) { + clear: both; } + .large-up-4 > .column:last-child, .large-up-4 > .columns:last-child { + float: left; } + .large-up-5 > .column, .large-up-5 > .columns { + float: left; + width: 20%; } + .large-up-5 > .column:nth-of-type(1n), .large-up-5 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-5 > .column:nth-of-type(5n+1), .large-up-5 > .columns:nth-of-type(5n+1) { + clear: both; } + .large-up-5 > .column:last-child, .large-up-5 > .columns:last-child { + float: left; } + .large-up-6 > .column, .large-up-6 > .columns { + float: left; + width: 16.66667%; } + .large-up-6 > .column:nth-of-type(1n), .large-up-6 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-6 > .column:nth-of-type(6n+1), .large-up-6 > .columns:nth-of-type(6n+1) { + clear: both; } + .large-up-6 > .column:last-child, .large-up-6 > .columns:last-child { + float: left; } + .large-up-7 > .column, .large-up-7 > .columns { + float: left; + width: 14.28571%; } + .large-up-7 > .column:nth-of-type(1n), .large-up-7 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-7 > .column:nth-of-type(7n+1), .large-up-7 > .columns:nth-of-type(7n+1) { + clear: both; } + .large-up-7 > .column:last-child, .large-up-7 > .columns:last-child { + float: left; } + .large-up-8 > .column, .large-up-8 > .columns { + float: left; + width: 12.5%; } + .large-up-8 > .column:nth-of-type(1n), .large-up-8 > .columns:nth-of-type(1n) { + clear: none; } + .large-up-8 > .column:nth-of-type(8n+1), .large-up-8 > .columns:nth-of-type(8n+1) { + clear: both; } + .large-up-8 > .column:last-child, .large-up-8 > .columns:last-child { + float: left; } + .large-collapse > .column, .large-collapse > .columns { + padding-right: 0; + padding-left: 0; } + .large-collapse .row { + margin-right: 0; + margin-left: 0; } + .expanded.row .large-collapse.row { + margin-right: 0; + margin-left: 0; } + .large-uncollapse > .column, .large-uncollapse > .columns { + padding-right: 0.75rem; + padding-left: 0.75rem; } + .large-centered { + margin-right: auto; + margin-left: auto; } + .large-centered, .large-centered:last-child:not(:first-child) { + float: none; + clear: both; } + .large-uncentered, + .large-push-0, + .large-pull-0 { + position: static; + float: left; + margin-right: 0; + margin-left: 0; } } + +.column-block { + margin-bottom: 1.5rem; } +.column-block > :last-child { + margin-bottom: 0; } diff --git a/app/common/styles/base/index.scss b/app/common/styles/base/index.scss new file mode 100644 index 0000000..5e10e93 --- /dev/null +++ b/app/common/styles/base/index.scss @@ -0,0 +1,9 @@ +@import 'variables'; +@import 'foundation'; +@import 'colors'; +@import 'mixins'; +@import 'core'; +@import 'typography'; +@import 'utility'; +@import 'buttons'; +@import 'forms'; diff --git a/app/common/styles/base/mixins.scss b/app/common/styles/base/mixins.scss new file mode 100644 index 0000000..71d8f39 --- /dev/null +++ b/app/common/styles/base/mixins.scss @@ -0,0 +1,97 @@ +//Breakpoints mixin +@mixin mediaQuery($breakpoint) { + @if $breakpoint == "x-small" { + @media (min-width: 25.000em) { //400px + @content; + } + } + @if $breakpoint == "small" { + @media (min-width: 47.938em) { //767px + @content; + } + } @else if $breakpoint == "medium" { + @media (min-width: 64.000em) { //1024px + @content; + } + } @else if $breakpoint == "large" { + @media (min-width: 85.375em) { //1366px + @content; + } + } @else if $breakpoint == "x-large" { + @media (min-width: 100.000em) { //1600px + @content; + } + } +} + +//Fonts mixin +@mixin fontFamily { + font-family: Helvetica, Arial, Verdana, sans-serif; + font-weight: 200; +} + +//Transitions mixin +@mixin fastTransition-all { + transition: all .5s ease; +} + +@mixin slowTransition-all { + transition: all 1.5s ease; +} + +//Custom SVG arrow +@mixin bgImage__arrowDown-primary { + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: #19273c'></polygon></svg>"); +} + +@mixin bgImage__arrowDown-accent { + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: #3c6df0'></polygon></svg>"); +} + +@mixin bgImage__arrowDown-grey { + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: #b8c1c1'></polygon></svg>"); +} + +@mixin indeterminate-bar { + background-image: repeating-linear-gradient(-45deg, rgba(251, 234, 174, 0.35), rgba(251, 234, 174, 0.35) 25px, rgba(244, 176, 0, 0.45) 25px, rgba(244, 176, 0, 0.45) 50px); + -webkit-animation: progress 2s linear infinite; + -moz-animation: progress 2s linear infinite; + animation: progress 2s linear infinite; + background-size: 130% 100%; + + @-webkit-keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -70px 0; + } + } + + @-moz-keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -70px 0; + } + } + + @-ms-keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -70px 0; + } + } + + @keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -70px 0; + } + } +} diff --git a/app/common/styles/base/typography.scss b/app/common/styles/base/typography.scss new file mode 100644 index 0000000..461304c --- /dev/null +++ b/app/common/styles/base/typography.scss @@ -0,0 +1,47 @@ +// Typography + +b, +strong { + font-weight: bold; +} + +p { + margin-top: 0; + line-height: 1.5rem; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 300; + margin: 0 0 .8rem; + line-height: 1; + color: #333; +} + +h1, .h1 { + font-size: 3rem; + font-weight: 500; +} + +h2, .h2 { + font-size: 1.8rem; +} + +h3, .h3 { + font-size: 1.3rem; + letter-spacing: -.08rem; +} + +h4, .h4 { + font-size: 1.125rem; + letter-spacing: -.04rem; +} + +h5, .h5 { + font-size: 1rem; + letter-spacing: -.05rem; +} diff --git a/app/common/styles/base/utility.scss b/app/common/styles/base/utility.scss new file mode 100644 index 0000000..8f17403 --- /dev/null +++ b/app/common/styles/base/utility.scss @@ -0,0 +1,73 @@ +.inline { + display: inline-block; +} + +.disabled { + color: $lightbg__grey; +} + +.float-right { + float: right; +} +.clear-float { + clear: both; +} +.error { + color: $error-color; +} + +.hide { + display: none; +} + +.show { + display: block; +} + +.close { + color: $lightbg__primary; + font-size: 1.5em; + padding: 1em; + box-sizing: border-box; + line-height: 0; + display: flex; + justify-content: center; + flex-direction: column; + background: transparent; + border: 0px; + margin: 0; + &:hover { + color: $lightbg__accent; + background: transparent; + } +} + +.fixed { + position:fixed; + top:130px; + z-index:200; +} + +.no-margin { + margin: 0px !important; +} + +.no-padding { + padding:0px !important; +} + +.no-bottom-margin { + margin-bottom: 0px !important; +} + +.no-top-margin { + margin-top: 0px !important; +} + +.btm-border-grey { + border-bottom: 1px solid $lightbg__grey; +} + +.transitionAll { + transition: all .5s ease; +}
\ No newline at end of file diff --git a/app/common/styles/base/variables.scss b/app/common/styles/base/variables.scss new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/app/common/styles/base/variables.scss diff --git a/app/common/styles/components/form-elements.scss b/app/common/styles/components/form-elements.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/common/styles/components/form-elements.scss diff --git a/app/common/styles/components/index.scss b/app/common/styles/components/index.scss new file mode 100644 index 0000000..fc36b38 --- /dev/null +++ b/app/common/styles/components/index.scss @@ -0,0 +1,2 @@ +@import "./form-elements.scss"; +@import "./table.scss";
\ No newline at end of file diff --git a/app/common/styles/components/table.scss b/app/common/styles/components/table.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/common/styles/components/table.scss diff --git a/app/common/styles/directives/app-header.scss b/app/common/styles/directives/app-header.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/common/styles/directives/app-header.scss diff --git a/app/common/styles/directives/app-navigation.scss b/app/common/styles/directives/app-navigation.scss new file mode 100644 index 0000000..29932e4 --- /dev/null +++ b/app/common/styles/directives/app-navigation.scss @@ -0,0 +1,142 @@ +$nav__toplvlWidth: 120px; +$nav__seclvlWidth: 240px; + +// Top level navigation +#nav__top-level { + background: $nav__top-level-color; + height: 100%; + position: fixed; + left: 0; + top: 0; + bottom: 0; + z-index: 99; + list-style-type: none; + margin: 0; + padding: 0; + width: $nav__toplvlWidth; + li { + margin: 0; + } + .button, button, a { + background: transparent; + height: auto; + border: 0; + color: $white; + fill: $white; + width: 100%; + padding: 1em; + display: block; + text-align: center; + margin-bottom: 0; + white-space: normal; + border-radius: 0; + .nav__icon { + color: $white; + max-height: 40px; + stroke-width: .5; + margin-bottom: -.5em; + } + a { + margin-bottom: 5px; + } + span { + margin: 1em 0 0 0; + display: block; + font-size: .9em; + font-weight: normal; + line-height: 1rem; + } + + .nav__icon-help__outer { + fill: transparent; + stroke: $white; + stroke-miterlimit: 10; + stroke-width: 1px; + } + .nav__icon-help__Inner { + fill: $white; + } + &:hover { + background: $nav__second-level-color; + fill: $black; + color: $black; + padding: 1em; + border-radius: 0; + .nav__icon-help__outer { + stroke: $black; + } + .nav__icon-help__inner { + fill: $lightbg__primary; + } + } + } + .opened { + background: $nav__second-level-color; + fill: $black; + color: $black; + .nav__icon-help__outer { + stroke: $lightbg__primary; + } + } +} + +// Second Level Navigation +.nav__second-level { + position: fixed; + background: $nav__second-level-color; + top: 0; + bottom: 0; + left: -$nav__toplvlWidth; + width: $nav__seclvlWidth; + z-index: 97; + padding: 0; + margin: 0; + display: none; + list-style-type: none; + @include fastTransition-all; + @include mediaQuery(medium) { + left: $nav__toplvlWidth; + &.btn-overview { + display: block; + } + } + &.opened { + left: $nav__toplvlWidth; + display: block; + @include fastTransition-all; + } + + a { + padding: 1.2em 1em 1.2em 1em; + display: block; + color: $black; + text-decoration: none; + position: relative; + font-weight: 400; + } + + li { + a:after{ + content: '\203A'; + position: absolute; + font-size: 2em; + font-weight: 700; + top: 50%; + right: .6em; + transform: translateY(-59%); + color: #4b5d78; + opacity: 0; + } + &.active {background: $white;} + &.active, + &:focus, + &:hover { + a {color: #4b5d78;} + a:after { + opacity: 1; + right: .3em; + @include fastTransition-all; + } + } + } +}
\ No newline at end of file diff --git a/app/common/styles/directives/confirm.scss b/app/common/styles/directives/confirm.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/common/styles/directives/confirm.scss diff --git a/app/common/styles/directives/index.scss b/app/common/styles/directives/index.scss new file mode 100644 index 0000000..a70c007 --- /dev/null +++ b/app/common/styles/directives/index.scss @@ -0,0 +1,3 @@ +@import "./app-header.scss"; +@import "./app-navigation.scss"; +@import "./confirm.scss";
\ No newline at end of file diff --git a/app/common/styles/elements/alerts.scss b/app/common/styles/elements/alerts.scss new file mode 100644 index 0000000..76624b1 --- /dev/null +++ b/app/common/styles/elements/alerts.scss @@ -0,0 +1,67 @@ +//Fixed alerts + +.alert__error, +.alert__warning, +.alert__message { + color: $darkbg__primary; + padding: 1em; + margin: .5em 0; + position: relative; + display: flex; + justify-content: center; + flex-direction: column; + .close { + color: $lightbg__primary; + position: absolute; + right: 0%; + top: 50%; + transform: translateY(-50%); + font-size: 1.5em; + padding: 1em; + box-sizing: border-box; + line-height: 0; + display: flex; + justify-content: center; + flex-direction: column; + background: transparent; + border: 0px; + margin: 0; + &:hover { + color: $lightbg__accent; + } + } +} + +.alert__error { + background: $alert__error; + +} + +.alert__warning { + background: $alert__warning; + +} +.alert__message { + background: $alert__message; +} + +// Power confirmation buttons +.power__confirm-buttons { + .btn-primary { + background: transparent; + border: 2px solid $white; + padding: 1em 2.2em; + margin: 0 10px; + border-radius: 4px; + &:focus, + &:hover { + background: $primebtn__bg; + border: 2px solid $primebtn__bg; + } + } + @include mediaQuery(large) { + float: right; + } +} + + diff --git a/app/common/styles/elements/index.scss b/app/common/styles/elements/index.scss new file mode 100644 index 0000000..7d04da2 --- /dev/null +++ b/app/common/styles/elements/index.scss @@ -0,0 +1,5 @@ + +@import "toggle-switch"; +@import "status"; +@import "alerts"; +@import "inline-confirm";
\ No newline at end of file diff --git a/app/common/styles/elements/inline-confirm.scss b/app/common/styles/elements/inline-confirm.scss new file mode 100644 index 0000000..cb4c7e3 --- /dev/null +++ b/app/common/styles/elements/inline-confirm.scss @@ -0,0 +1,54 @@ + +// inline confirmation message +.inline__confirm { + position: absolute; + top: 0; + transform: translateY(-103%); + width: 100%; + height: 100%; + margin-left: -1.8em; + z-index: 5; + background: $darkbg__primary; + color: $white; + padding: 2em 2em 1.55em 2em; + overflow: hidden; + &.active { + transform: translateY(0); + @include fastTransition-all; + } +} + +// Power confirmation buttons +.inline__confirm-buttons { + .btn-primary { + background: transparent; + border: 2px solid $white; + padding: 1em 2.2em; + margin: 0 10px; + border-radius: 4px; + &:focus, + &:hover { + background: $primebtn__bg; + border: 2px solid $primebtn__bg; + } + } + @include mediaQuery(large) { + float: right; + } +} + +// confirmation message +.inline__confirm-message { + display: inline-block; +} + +// Power confirmation message icon +.inline__confirm-message i::before { + content: '\26A0'; + color: $status-warn; + display: inline-block; + font-size: 2em; + vertical-align: middle; + font-style: normal; + margin-right: 15px; +}
\ No newline at end of file diff --git a/app/common/styles/elements/status.scss b/app/common/styles/elements/status.scss new file mode 100644 index 0000000..7d9b57a --- /dev/null +++ b/app/common/styles/elements/status.scss @@ -0,0 +1,61 @@ +//status light states +@mixin status-light { + content: '\25CF'; + display: inline-block; + font-size: 2em; + color: $status-ok; + margin-right: .1em; + transform: translateY(2px); + @include slowTransition-all; +} + +@mixin status-light__good { + @include status-light; + color: $status-ok; + +} + +@mixin status-light__error { + @include status-light; + color: $error-color; + +} + +@mixin status-light__disabled { + @include status-light; + color: $darkgrey; + +} + +@mixin status-light__warn { + @include status-light; + color: $status-warn; + +} + +.status-light__disabled { + color: $darkgrey; + &::before { + @include status-light__disabled; + } +} + +.status-light__good { + &::before { + @include status-light__good; + } +} + +.status-light__error { + color: $error-color; + &::before { + @include status-light__error; + } +} + +.status-light__warn { + color: $status-warn; + &::before { + @include status-light__warn; + } +}
\ No newline at end of file diff --git a/app/common/styles/elements/toggle-switch.scss b/app/common/styles/elements/toggle-switch.scss new file mode 100644 index 0000000..88b1def --- /dev/null +++ b/app/common/styles/elements/toggle-switch.scss @@ -0,0 +1,85 @@ +@mixin marginTransition { + -webkit-transition: margin 0.4s, background 0.4s; + -moz-transition: margin 0.4s, background 0.4s; + -o-transition: margin 0.4s, background 0.4s; + transition: margin 0.4s, background 0.4s; +} + +@mixin borderRadius { + -webkit-border-radius: 60px; + -moz-border-radius: 60px; + border-radius: 60px; +} + +.toggle-switch { + position: absolute; + left: 20px; + height: 1px; + width: 1px; + opacity: 0; +} + +.toggle-switch + label { + display: block; + position: relative; + cursor: pointer; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.toggle label { + text-indent: -9999px; + transform: translateY(-50%); // keeps switch centered +} + +input.toggle-switch__round-flat + label { + padding: 2px; + width: 50px; + height: 30px; + background-color: $lightgrey; + @include borderRadius; + @include marginTransition; +} + +input.toggle-switch__round-flat:focus + label { + box-shadow: 0 0 4px 4px $darkbg__accent; +} +input.toggle-switch__round-flat + label:before, input.toggle-switch__round-flat + label:after { + display: block; + position: absolute; + content: ""; +} +input.toggle-switch__round-flat + label:before { + top: 2px; + left: 2px; + bottom: 2px; + right: 2px; + background-color: $white; + @include borderRadius; + @include marginTransition; +} +input.toggle-switch__round-flat + label:after { + top: 4px; + left: 4px; + bottom: 4px; + width: 20px; + background-color: $lightgrey; + -webkit-border-radius: 52px; + -moz-border-radius: 52px; + -ms-border-radius: 52px; + border-radius: 52px; + @include marginTransition; +} +input.toggle-switch__round-flat:checked + label { + background-color: $primebtn__bg; +} +input.toggle-switch__round-flat:checked + label:before { + background-color: $lightbg__accent; +} +input.toggle-switch__round-flat:checked + label:after { + margin-left: 20px; + background-color: $primebtn__bg; +}
\ No newline at end of file diff --git a/app/common/styles/index.scss b/app/common/styles/index.scss new file mode 100644 index 0000000..13804a7 --- /dev/null +++ b/app/common/styles/index.scss @@ -0,0 +1,6 @@ + +@import "./base/index.scss"; +@import "./elements/index.scss"; +@import "./components/index.scss"; +@import "./layout/index.scss"; +@import "./directives/index.scss";
\ No newline at end of file diff --git a/app/common/styles/layout/content.scss b/app/common/styles/layout/content.scss new file mode 100644 index 0000000..b06175b --- /dev/null +++ b/app/common/styles/layout/content.scss @@ -0,0 +1,11 @@ +// Content layout styles + +.content__container { + margin-left: $nav__toplvlWidth; + padding: 1em .1em; + transition: left 1s ease; + @include mediaQuery(x-small){ + margin-left: $nav__toplvlWidth + $nav__seclvlWidth; + padding: 1rem 2rem; + } +} diff --git a/app/common/styles/layout/header.scss b/app/common/styles/layout/header.scss new file mode 100644 index 0000000..9b7dff5 --- /dev/null +++ b/app/common/styles/layout/header.scss @@ -0,0 +1,163 @@ +$logoHeight: 30px; +$logoMaxHeight: 100px; +$logoMaxWidth: 125px; + +#header__wrapper { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; +} + +.app__version { + margin-left: 1em; + display: none; + @include mediaQuery(x-small) { + display: inline-block; + position: absolute; + top: 50%; + transform: translateY(-50%); + } +} + +header { + position: relative; + background: #000; + color: $white; + overflow: hidden; +} + +.header__logout { + float: right; + display: inline-block; + color: $white; + font-size: .9em; + text-decoration: none; + padding: 1em; + &:visited { + color: $white; + } +} + +.header__functions-wrapper { + color: $white; + background: $darkbg__primary; + padding: 0 1.1em; + box-sizing: border-box; + display: block; + position: relative; + overflow: hidden; + min-height: 5em; + #header__server-name { + //margin-top: .8em; + font-size: 1.5em; + font-weight: 500; + color: $white; + padding: .9em; + height: 100%; + background: transparent; + max-width: 350px; + white-space: nowrap; + } + .logo__wrapper { + padding-top: .5em; + //position: absolute; + //top: 50%; + //transform: translateY(-50%); + } + + #header__logo { + vertical-align: middle; + margin: 1em; + float: left; + height: $logoHeight; //required for <SVG> logos - can remove if using img + max-height: $logoMaxHeight; + max-width: $logoMaxWidth; + width: auto; + } + #header__funct-icon { + display: block; + font-size: 2.3em; + color: $white; + padding: 0; + &:before { + content: '\2261'; + line-height: .5; + font-size: 2em; + padding: 0 .3em; + } + @include mediaQuery(medium) { + display: none; + } + } + + .header__functions { + position: absolute; + top: 0; + right: 0; + bottom: 0; + background: $darkbg__primary; + z-index: 100; + @include fastTransition-all; + span { + display: block; + color: $white; + font-size: 1em; + } + a, p, button { + display: block; + float: left; + text-decoration: none; + border-left: 1px solid #384456; + color: $lightgrey; + margin: 0; + padding: 1.250em 1.688em; + height: 100%; + font-size: 0.875em; + line-height: 1; + > span { + font-size: 1rem; + font-weight: bold; + } + } + } + + // hide/show header functions based on screen size + .header__functions > #header__server-health { + display: none; + @include mediaQuery(small) { + display: block; + } + } + + .header__functions > .header__refresh { + display: none; + @include mediaQuery(medium) { + display: block; + } + } + + .header__functions { + .header__refresh { + color: $lightgrey; + line-height: 1.8; + margin-top: -4px; + } + } + .header__server-power, + .header__page-refresh { + &:hover { + background: rgba(60, 109, 240, .4); + } + } + .header__page-refresh { + img { + stroke: $white; + height: 22px; + width: 24px; + } + } +} + +// end header__functions-wrapper diff --git a/app/common/styles/layout/index.scss b/app/common/styles/layout/index.scss new file mode 100644 index 0000000..f78538c --- /dev/null +++ b/app/common/styles/layout/index.scss @@ -0,0 +1,3 @@ +@import 'header'; +@import 'navigation'; +@import 'content'; diff --git a/app/common/styles/layout/navigation.scss b/app/common/styles/layout/navigation.scss new file mode 100644 index 0000000..6a2bbfe --- /dev/null +++ b/app/common/styles/layout/navigation.scss @@ -0,0 +1,142 @@ +$nav__toplvlWidth: 120px; +$nav__seclvlWidth: 240px; + +// Top level navigation +#nav__top-level { + background: $nav__top-level-color; + height: 100%; + position: fixed; + left: 0; + top: 0; + bottom: 0; + z-index: 99; + list-style-type: none; + margin: 0; + padding: 0; + width: $nav__toplvlWidth; + li { + margin: 0; + } + .button, button, a { + background: transparent; + height: auto; + border: 0; + color: $white; + fill: $white; + width: 100%; + padding: 1em; + display: block; + text-align: center; + margin-bottom: 0; + white-space: normal; + border-radius: 0; + .nav__icon { + color: $white; + max-height: 40px; + stroke-width: .5; + margin-bottom: -.5em; + } + a { + margin-bottom: 5px; + } + span { + margin: 1em 0 0 0; + display: block; + font-size: .9em; + font-weight: normal; + line-height: 1rem; + } + + .nav__icon-help__outer { + fill: transparent; + stroke: $white; + stroke-miterlimit: 10; + stroke-width: 1px; + } + .nav__icon-help__Inner { + fill: $white; + } + &:hover { + background: $nav__second-level-color; + fill: $black; + color: $black; + padding: 1em; + border-radius: 0; + .nav__icon-help__outer { + stroke: $black; + } + .nav__icon-help__inner { + fill: $lightbg__primary; + } + } + } + .opened { + background: $nav__second-level-color; + fill: $black; + color: $black; + .nav__icon-help__outer { + stroke: $lightbg__primary; + } + } +} + +// Second Level Navigation +.nav__second-level { + position: fixed; + background: $nav__second-level-color; + top: 0; + bottom: 0; + left: -$nav__toplvlWidth; + width: $nav__seclvlWidth; + z-index: 97; + padding: 0; + margin: 0; + display: none; + list-style-type: none; + @include fastTransition-all; + @include mediaQuery(medium) { + left: $nav__toplvlWidth; + &.btn-overview { + display: block; + } + } + &.opened { + left: $nav__toplvlWidth; + display: block; + @include fastTransition-all; + } + + a { + padding: 1.2em 1em 1.2em 1em; + display: block; + color: $black; + text-decoration: none; + position: relative; + font-weight: 400; + } + + li { + a:after{ + content: '\203A'; + position: absolute; + font-size: 2em; + font-weight: 700; + top: 50%; + right: .6em; + transform: translateY(-59%); + color: #4b5d78; + opacity: 0; + } + &.active {background: $white;} + &.active, + &:focus, + &:hover { + a {color: #4b5d78;} + a:after { + opacity: 1; + right: .3em; + @include fastTransition-all; + } + } + } +} diff --git a/app/constants/environment-constants.js b/app/constants/environment-constants.js new file mode 100644 index 0000000..e93182a --- /dev/null +++ b/app/constants/environment-constants.js @@ -0,0 +1,30 @@ +/** + * A module with constants for the REST API + * + * @module app/constants + * @exports app/constants + * + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.constants') + .constant('EnvironmentConstants', { + 'inDevelopmentMode': true, + 'RestConstants': { + }, + FLASH_MESSAGE : { + duration: 2000, + classes: { + warning: 'message-warning', + info: 'message-info', + error: 'message-error', + success: 'message-success' + } + } + }); + +})(window.angular); diff --git a/app/constants/index.js b/app/constants/index.js new file mode 100644 index 0000000..c27d81d --- /dev/null +++ b/app/constants/index.js @@ -0,0 +1,17 @@ +/** + * A module with constants for the application + * + * @module app/constants/index + * @exports app/constants/index + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.constants', []) + .constant('AppConstants', { + }); + +})(window.angular); diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..fd308fc --- /dev/null +++ b/app/index.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html lang="en" ng-app="app"> +<head> + <base href="/"> + <meta charset="UTF-8"> + <title>openBMC</title> + <link rel="icon" href="favicon.ico?v=2"/> + <!-- build:css styles/app.min.css --> + <link rel="stylesheet" href="styles/index.css"> + <!-- endbuild --> +</head> +<body ng-style="dataService.bodyStyle" ng-attr-id="{{dataService.path == '/login' ? 'login': ''}}"> + + <app-header ng-if="dataService.showNavigation" path="dataService.path"></app-header> + <app-navigation ng-if="dataService.showNavigation" path="dataService.path" show-navigation=" dataService.showNavigation"></app-navigation> + + <main ng-view ng-class="{'content__container': dataService.path != '/login', 'login__wrapper': dataService.path == '/login'}"> + </main> + + <!-- build:js scripts/vendor.min.js --> + <script src="../bower_components/angular/angular.min.js"></script> + <script src="../bower_components/angular-route/angular-route.min.js"></script> + <!-- endbuild --> + + <!-- build:js scripts/app.min.js --> + <script src="index.js"></script> + <script src="templates.js"></script> + <script src="vendors.js"></script> + + <script src="constants/index.js"></script> + <script src="constants/environment-constants.js"></script> + + <script src="common/services/index.js"></script> + <script src="common/services/constants.js"></script> + <script src="common/services/dataService.js"></script> + <script src="common/services/api-utils.js"></script> + <script src="common/services/userModel.js"></script> + <script src="common/services/apiInterceptor.js"></script> + + <script src="common/filters/index.js"></script> + + <script src="common/directives/index.js"></script> + <script src="common/directives/app-header.js"></script> + <script src="common/directives/app-navigation.js"></script> + <script src="common/directives/confirm.js"></script> + + <script src="login/index.js"></script> + <script src="login/controllers/login-controller.js"></script> + <script src="overview/index.js"></script> + <script src="overview/controllers/bmc-reboot-controller.js"></script> + <script src="overview/controllers/power-operations-controller.js"></script> + <script src="overview/controllers/system-overview-controller.js"></script> + <script src="overview/controllers/unit-id-controller.js"></script> + <script src="overview/controllers/log-controller.js"></script> + <!-- endbuild --> +</body> + +</html> diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..f5c2692 --- /dev/null +++ b/app/index.js @@ -0,0 +1,83 @@ +/** + * A module which contains the definition of the application and the base of configuration + * + * @module app/index/services/index + * @exports app/index + * + * @author Developer Developer + * @version 0.10.0 + * @since 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app', [ + // Dependencies + 'ngRoute', + // Basic resources + 'app.constants', + 'app.templates', + 'app.vendors', + 'app.common.services', + 'app.common.directives', + 'app.common.filters', + // Model resources + 'app.login', + 'app.overview' + ]) + // Route configuration + .config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) { + $locationProvider.hashPrefix(''); + $routeProvider + .otherwise({ + 'redirectTo': '/login' + }); + }]) + .config(['$httpProvider', function($httpProvider){ + //console.log($httpProvider.interceptors); + $httpProvider.interceptors.push('apiInterceptor'); + }]) + .run(['$rootScope', '$location', 'dataService', 'userModel', + function($rootScope, $location, dataService, userModel){ + $rootScope.dataService = dataService; + dataService.path = $location.path(); + $rootScope.$on('$routeChangeStart', function(event, next, current){ + + if(next.$$route == null || next.$$route == undefined) return; + if(next.$$route.authenticated){ + if(!userModel.isLoggedIn()){ + $location.path('/login'); + } + } + + if(next.$$route.originalPath == '/' || + next.$$route.originalPath == '/login'){ + if(userModel.isLoggedIn()){ + if(current && current.$$route){ + $location.path(current.$$route.originalPath); + }else{ + $location.path('/overview/system'); + } + } + } + }); + $rootScope.$on('$locationChangeSuccess', function(event){ + var path = $location.path(); + dataService.path = path; + if(['/','/login','/logout'].indexOf(path) == -1){ + dataService.showNavigation = true; + }else{ + dataService.showNavigation = false; + } + }); + + $rootScope.$on('timedout-user', function(){ + sessionStorage.removeItem('LOGIN_ID'); + $location.path('/login'); + }); + } + ]); + +})(window.angular); diff --git a/app/login/controllers/login-controller.html b/app/login/controllers/login-controller.html new file mode 100644 index 0000000..a249726 --- /dev/null +++ b/app/login/controllers/login-controller.html @@ -0,0 +1,36 @@ +<div> + <div class="row"> + <div class="columns large-6"> + <img src="assets/images/logo.svg" class="login__logo" alt="IBM logo" role="img"/> + </div> + <div class="columns large-6"> + </div> + </div> + <div class="row"> + <div class="columns large-6 login__desc"> + <h1>OpenBMC Advanced System Management</h1> + <ul class="login__server-info"> + <li><p class="login__info-label">Build version</p><p>X.XX.XXX</p></li> + <li><p class="login__info-label">Server ID</p><p>XXXXXXXXXXXXXXXX</p></li> + <li><p class="login__info-label">Server model</p><p>Power XX-XXXXX</p></li> + <!-- ping server to see if powered on. Change status-light and txt accordingly. Status message is planned to be hardcoded message were isplay via local JS. --> + <li><p class="login__info-label">Server power</p><p class="status-light__disabled">Indeterminate</p></li> + <li><p class="login__info-label">Status message</p><!--<p>BMC was reset by user</p>--></li> + </ul> + </div> + <div class="columns large-6 disabled"> + <form id="login__form" role="form" action="" > + <label for="username">Username</label> + <input type="text" id="username" name="username" required ng-model="username" ng-class="{error: error}" ng-keydown="tryLogin(username, password, $event)" ng-disabled="dataService.loading"> + + <label for="password">Password</label> + <input type="password" id="password" name="password" class="" required ng-model="password" ng-keydown="tryLogin(username, password, $event)" ng-disabled="dataService.loading"> + + <input id="login__submit" class="btn-primary submit" type="button" value="Log in" role="button" ng-click="login(username, password)" ng-class="{error: error}" ng-disabled="dataService.loading"> + + <p class="login__error-msg" role="alert" ng-if="error">Incorrect username or password</p> + <p class="login__error-msg" role="alert" ng-if="dataService.server_unreachable">Server Unreachable</p> + </form> + </div> + </div> +</div>
\ No newline at end of file diff --git a/app/login/controllers/login-controller.js b/app/login/controllers/login-controller.js new file mode 100644 index 0000000..b1a0d45 --- /dev/null +++ b/app/login/controllers/login-controller.js @@ -0,0 +1,52 @@ +/** + * Controller for the login page + * + * @module app/login/controllers/index + * @exports LoginController + * @name LoginController + * @version 0.1.0 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.login') + .controller('LoginController', [ + '$scope', + '$window', + 'APIUtils', + 'dataService', + 'userModel', + function($scope, $window, APIUtils, dataService, userModel){ + $scope.dataService = dataService; + + $scope.tryLogin = function(username, password, event){ + if(event.keyCode === 13){ + $scope.login(username, password); + } + }; + $scope.login = function(username, password){ + $scope.error = false; + + if(!username || username == "" || + !password || password == ""){ + return false; + }else{ + userModel.login(username, password, function(status, unreachable){ + if(status){ + $scope.$emit('user-logged-in',{}); + $window.location.hash = '#/system-overview'; + }else{ + if(!unreachable){ + $scope.error = true; + } + }; + }); + } + } + } + ] + ); + +})(angular); diff --git a/app/login/index.js b/app/login/index.js new file mode 100644 index 0000000..7e86ac4 --- /dev/null +++ b/app/login/index.js @@ -0,0 +1,28 @@ +/** + * A module for the login + * + * @module app/login/index + * @exports app/login/index + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.login', [ + 'ngRoute', + 'app.constants', + 'app.common.services' + ]) + // Route configuration + .config(['$routeProvider', function ($routeProvider) { + $routeProvider + .when('/login', { + 'templateUrl': 'login/controllers/login-controller.html', + 'controller': 'LoginController', + authenticated: false + }); + }]); + +})(window.angular); diff --git a/app/login/styles/index.scss b/app/login/styles/index.scss new file mode 100644 index 0000000..088f42a --- /dev/null +++ b/app/login/styles/index.scss @@ -0,0 +1,115 @@ +#login { + @include mediaQuery(medium) { + background: linear-gradient(to right, #FFFFFF 49.999%, #e6e9ed 50%); + @include fastTransition-all; + } +} + +.login__wrapper { + position: relative; + padding-top: 1em; + color: $black; + .row { + max-width: 1040px; + } + @include mediaQuery(small) { + left: 50%; + top: 50%; + transform: translateY(-50%); + margin-left: -50%; + } + @include mediaQuery(medium) { + left: 0; + margin-left: 0; + } + .login__logo { + max-width: 200px; + display: block; + margin-bottom: 2em; + @include mediaQuery(small) { + margin-bottom: 4em; + } + } + @include fastTransition-all; +} + +#login__form { + color: $black; + background: transparent; + padding: 1em 0; + display: block; + overflow: hidden; + @include mediaQuery(small) { + max-width: 50%; + } + @include mediaQuery(medium) { + padding: 0 0 0 5em; + max-width: 100%; + } + @include fastTransition-all; +} + +.login__desc { + text-align: left; + h1 { + font-size: 2em; + margin-bottom: 0; + font-weight: bold; + @include mediaQuery(small) { + max-width: 50%; + } + @include mediaQuery(medium) { + max-width: 100%; + } + } +} + +.login__server-info { + margin-top: 2em; + margin-left: 0; + padding-left: 0; + list-style-type: none; + @include mediaQuery(small) { + max-width: 50%; + } + @include mediaQuery(medium) { + max-width: 90%; + } + li { + padding: 1em 0; + border-top: 1px solid $lightgrey; + } + p { + margin-bottom: 0; + display: inline-block; + font-family: courier, helvetica, arial, sans-serif; + font-weight: 700; + } + .login__info-label { + display: inline-block; + min-width: 200px; + @include fontFamily; + } +} + +.login__status { + color: $status-ok; + &.error { + color: $error-color; + } +} + +.login__error-msg { + background: lighten($error-color, 20%); + padding: 1em; + text-align: center; + font-size: 1em; + border: 1px solid $error-color; + color: $black; + font-family: Courier, Helvetica, Arial, sans-serif; + font-weight: 700; + + @include mediaQuery(medium) { + max-width: 100%; + } +}
\ No newline at end of file diff --git a/app/overview/controllers/bmc-reboot-controller.html b/app/overview/controllers/bmc-reboot-controller.html new file mode 100644 index 0000000..22df0c2 --- /dev/null +++ b/app/overview/controllers/bmc-reboot-controller.html @@ -0,0 +1,25 @@ +<div id="bmc-reboot"> + <div class="row column"> + <h1>Reboot BMC</h1> + </div> + + <div class="row column btm-border-grey"> + <span class="bmc-reboot__status-log inline">BMC last reboot at {{dataService.last_updated |date:'h:mm:ss on MM/dd/yyyy'}}</span> + </div> + + <div class="row column btm-border-grey bmc-reboot-option" + ng-class="{transitionAll: confirm && immediately_confirm}"> + <ul> + <li>Rebooting the BMC causes your browser to lose contact with the BMC for several minutes.</li> + <li>When the BMC software has started, you can then log in again.</li> + <li>If the Log In button is not enabled after several minutes, you may need to point your browser to the + BMC’s address and log in again. + </li> + </ul> + <button id="bmc__reboot" class="btn-secondary" ng-click="bmcRebootConfirm()"><i>↻</i> Reboot BMC + </button> + <confirm title="Reboot the BMC" + message="Rebooting the BMC causes your browser to lose contact with the BMC for several minutes." + confirm="bmcReboot_confirm" cancel="bmcRebootCancel"></confirm> + </div> +</div> diff --git a/app/overview/controllers/bmc-reboot-controller.js b/app/overview/controllers/bmc-reboot-controller.js new file mode 100644 index 0000000..040b444 --- /dev/null +++ b/app/overview/controllers/bmc-reboot-controller.js @@ -0,0 +1,26 @@ +/** + * Controller for bmc-reboot + * + * @module app/overview + * @exports bmcRebootController + * @name bmcRebootController + * @version 0.1.0 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.overview') + .controller('bmcRebootController', [ + '$scope', + '$window', + 'APIUtils', + 'dataService', + function($scope, $window, APIUtils, dataService, userModel){ + $scope.dataService = dataService; + } + ] + ); + +})(angular); diff --git a/app/overview/controllers/log-controller.html b/app/overview/controllers/log-controller.html new file mode 100644 index 0000000..cf70c95 --- /dev/null +++ b/app/overview/controllers/log-controller.html @@ -0,0 +1,3 @@ +<div id="uid-log"> + Log contents here +</div>
\ No newline at end of file diff --git a/app/overview/controllers/log-controller.js b/app/overview/controllers/log-controller.js new file mode 100644 index 0000000..3b85605 --- /dev/null +++ b/app/overview/controllers/log-controller.js @@ -0,0 +1,26 @@ +/** + * Controller for log + * + * @module app/overview + * @exports logController + * @name logController + * @version 0.1.0 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.overview') + .controller('logController', [ + '$scope', + '$window', + 'APIUtils', + 'dataService', + function($scope, $window, APIUtils, dataService, userModel){ + $scope.dataService = dataService; + } + ] + ); + +})(angular); diff --git a/app/overview/controllers/power-operations-controller.html b/app/overview/controllers/power-operations-controller.html new file mode 100644 index 0000000..48d4357 --- /dev/null +++ b/app/overview/controllers/power-operations-controller.html @@ -0,0 +1,55 @@ +<div id="power-operations"> + + <div class="row column"> + <h1>Server power operation</h1> + </div> + + <!-- Current status and bar display the state of the server. Class 'power__state-off' is applied to bar and 'power__state' text switches to say "off"--> + <div class="row column"> + <div class="power__current-status "> + <h2 class="inline h4">Current status</h2> + <span class="power__status-log inline float-right">Server last reset at {{dataService.last_updated |date:'h:mm:ss on MM/dd/yyyy'}}</span> + </div> + </div> + <div class="row column"> + <div id="power-indicator-bar" class="power__indicator-bar" ng-class="{'power__state-on': dataService.server_state == 'Running', 'power__state-off': dataService.server_state == 'Off', 'power__state-indet': dataService.server_state == 'Quiesced'}"> + <p class="inline">{{dataService.server_id}}</p> + <h3 class="power__state inline float-right h3"><span>{{dataService.server_state}}</span></h3> + </div> + </div> + <div class="row column"> + <div class="row column"> + <h3 class="h4">Select a power operation</h3> + </div> + + <!-- Power on displays only when server is shutdown --> + <div class="row column power-option" ng-hide="dataService.server_state == 'Running'" ng-class="{disabled: (confirm && !power_confirm) || dataService.loading, transitionAll: confirm && power_confirm}"> + <button id="power__power-on" class="btn-secondary" ng-click="togglePower()" role="button">Power On</button> + <p>Attempts to power on the server</p> + + <!---<confirm title="power off" message="Power off the server" confirm="power_confirm" ng-show="power_confirm" callback="togglePower"></confirm>--> + </div> + + <!-- Power reboot/shutdown options : when server is off all of these are hidden. When one option is selected, the others are disabled. --> + <div class="row column power-option" ng-hide="dataService.server_state == 'Off'" ng-class="{disabled: (confirm && !warmboot_confirm) || dataService.loading, transitionAll: confirm && warmboot_confirm}"> + <button id="power__warm-boot" class="btn-secondary" ng-click="warmRebootConfirm()" role="button"><i>↻</i> Warm reboot</button> + <p>Attempts to perform an orderly shutdown before restarting the server</p> + <confirm title="Warm Reboot" message="perform an orderly shutdown" confirm="warmboot_confirm" ng-show="warmboot_confirm" callback="warmReboot"></confirm> + </div> + <div class="row column power-option" ng-hide="dataService.server_state == 'Off'" ng-class="{disabled: (confirm && !coldboot_confirm) || dataService.loading, transitionAll: confirm && coldboot_confirm}"> + <button id="power__cold-boot" class="btn-secondary" ng-click="coldRebootConfirm()" role="button"><i>↻</i> Cold reboot</button> + <p>Shuts down the server immediately, then restarts it</p> + <confirm title="Cold Reboot" message="Shutdown server immediately." confirm="coldboot_confirm" ng-show="coldboot_confirm" cancel="coldbootCancel" callback="coldReboot"></confirm> + </div> + <div class="row column power-option" ng-hide="dataService.server_state == 'Off'" ng-class="{disabled: (confirm && !orderly_confirm) || dataService.loading, transitionAll: confirm && orderly_confirm}"> + <button id="power__soft-shutdown" class="btn-secondary" ng-click="orderlyShutdownConfirm()" role="button"><img src="assets/images/icon-power.svg" />Orderly shutdown</button> + <p>Attempts to stop all software on the server before removing power</p> + <confirm title="Orderly shutdown" message="Attempts to stop all software orderly." confirm="orderly_confirm" ng-show="orderly_confirm" cancel="orderlyShutdownCancel" callback="orderlyShutdown"></confirm> + </div> + <div class="row column power-option" ng-hide="dataService.server_state == 'Off'" ng-class="{disabled: (confirm && !immediately_confirm) || dataService.loading, transitionAll: confirm && immediately_confirm}"> + <button id="power__hard-shutdown" class="btn-secondary" ng-click="immediateShutdownConfirm()" role="button"><img src="assets/images/icon-power.svg" />Immediate shutdown</button> + <p>Removes power from the server without waiting for software to stop</p> + <confirm title="Immediate shutdown" message="Removes power from the server immediately." confirm="immediately_confirm" ng-show="immediately_confirm" cancel="immediatelyShutdownCancel" callback="immediateShutdown"></confirm> + </div> + </div> +</div>
\ No newline at end of file diff --git a/app/overview/controllers/power-operations-controller.js b/app/overview/controllers/power-operations-controller.js new file mode 100644 index 0000000..1780da3 --- /dev/null +++ b/app/overview/controllers/power-operations-controller.js @@ -0,0 +1,134 @@ +/** + * Controller for power-operations + * + * @module app/overview + * @exports powerOperationsController + * @name powerOperationsController + * @version 0.1.0 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.overview') + .controller('powerOperationsController', [ + '$scope', + 'APIUtils', + 'dataService', + '$timeout', + function($scope, APIUtils, dataService, $timeout){ + $scope.dataService = dataService; + $scope.confirm = false; + $scope.power_confirm = false; + $scope.warmboot_confirm = false; + $scope.coldboot_confirm = false; + $scope.orderly_confirm = false; + $scope.immediately_confirm = false; + + //@TODO: call api and get proper state + $scope.toggleState = function(){ + dataService.server_state = (dataService.server_state == 'Running') ? 'Off': 'Running'; + } + + $scope.togglePower = function(){ + var method = (dataService.server_state == 'Running') ? 'hostPowerOff' : 'hostPowerOn'; + //@TODO: show progress or set class orange + APIUtils[method](function(response){ + //update state based on response + //error case? + if(response == null){ + console.log("Failed request."); + }else{ + //@TODO::need to get the server status + if(dataService.server_state == 'Running'){ + dataService.setPowerOffState(); + }else{ + dataService.setPowerOnState(); + } + } + }); + } + $scope.powerOnConfirm = function(){ + if($scope.confirm) { + return; + } + $scope.confirm = true; + $scope.power_confirm = true; + }; + $scope.warmReboot = function(){ + //@TODO:show progress + dataService.setBootingState(); + APIUtils.hostPowerOff(function(response){ + if(response){ + APIUtils.hostPowerOn(function(response){ + if(response){ + dataService.setPowerOnState(); + }else{ + //@TODO:show error message + } + }); + }else{ + } + }); + }; + $scope.testState = function(){ + $timeout(function(){ + dataService.setPowerOffState(); + $timeout(function(){ + dataService.setPowerOnState(); + }, 2000); + }, 1000); + }; + $scope.warmRebootConfirm = function(){ + if($scope.confirm) { + return; + } + $scope.confirm = true; + $scope.warmboot_confirm = true; + }; + + $scope.coldReboot = function(){ + $scope.warmReboot(); + }; + $scope.coldRebootConfirm = function(){ + if($scope.confirm) { + return; + } + $scope.confirm = true; + $scope.coldboot_confirm = true; + }; + + $scope.orderlyShutdown = function(){ + //@TODO:show progress + APIUtils.hostPowerOff(function(response){ + if(response){ + dataService.setPowerOffState(); + }else{ + //@TODO:hide progress & show error message + } + }); + }; + $scope.orderlyShutdownConfirm = function(){ + if($scope.confirm) { + return; + } + $scope.confirm = true; + $scope.orderly_confirm = true; + }; + + $scope.immediateShutdown = function(){ + $scope.orderlyShutdown(); + }; + $scope.immediateShutdownConfirm = function(){ + if($scope.confirm) { + return; + } + $scope.confirm = true; + $scope.immediately_confirm = true; + }; + } + ] + ); + +})(angular); diff --git a/app/overview/controllers/system-overview-controller.html b/app/overview/controllers/system-overview-controller.html new file mode 100644 index 0000000..9f6a126 --- /dev/null +++ b/app/overview/controllers/system-overview-controller.html @@ -0,0 +1,4 @@ +<div class="row column"> + <h1>System Overview</h1> + <p>System overview will be here.</p> +</div>
\ No newline at end of file diff --git a/app/overview/controllers/system-overview-controller.js b/app/overview/controllers/system-overview-controller.js new file mode 100644 index 0000000..e3841cb --- /dev/null +++ b/app/overview/controllers/system-overview-controller.js @@ -0,0 +1,26 @@ +/** + * Controller for system overview + * + * @module app/overview + * @exports systemOverviewController + * @name systemOverviewController + * @version 0.1.0 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.overview') + .controller('systemOverviewController', [ + '$scope', + '$window', + 'APIUtils', + 'dataService', + function($scope, $window, APIUtils, dataService, userModel){ + $scope.dataService = dataService; + } + ] + ); + +})(angular); diff --git a/app/overview/controllers/unit-id-controller.html b/app/overview/controllers/unit-id-controller.html new file mode 100644 index 0000000..6e6ba72 --- /dev/null +++ b/app/overview/controllers/unit-id-controller.html @@ -0,0 +1,27 @@ +<div id="uid-switch"> + <div class="row column"> + <h1>Unit ID Indicator</h1> + </div> + + <div class="row column"> + <h2 class="h4">Unit ID control</h2> + </div> + + <div class="row column"> + <div class="btm-border-grey"> + <div class="toggle inline" ng-init="indicatorState = 'off'"> + <input id="toggle__switch-round" + class="toggle-switch toggle-switch__round-flat" + type="checkbox" + tabindex="0" + ng-click="indicatorState = { 'on': 'off', 'off':'on'}[indicatorState]" + > + <label for="toggle__switch-round" tabindex="0">Unit ID indicator is <span class="uid-switch__status">{{indicatorState}}</span></label> + </div> + <div class="uid-switch__label inline"> + <p>Unit ID indicator is <span class="uid-switch__status">{{indicatorState}}</span></p> + <p>Control unit indicator to identify server unit.</p> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/app/overview/controllers/unit-id-controller.js b/app/overview/controllers/unit-id-controller.js new file mode 100644 index 0000000..c59f411 --- /dev/null +++ b/app/overview/controllers/unit-id-controller.js @@ -0,0 +1,26 @@ +/** + * Controller for unit Id + * + * @module app/overview + * @exports unitIdController + * @name unitIdController + * @version 0.1.0 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.overview') + .controller('unitIdController', [ + '$scope', + '$window', + 'APIUtils', + 'dataService', + function($scope, $window, APIUtils, dataService, userModel){ + $scope.dataService = dataService; + } + ] + ); + +})(angular); diff --git a/app/overview/index.js b/app/overview/index.js new file mode 100644 index 0000000..bc37a37 --- /dev/null +++ b/app/overview/index.js @@ -0,0 +1,48 @@ +/** + * A module for the overview + * + * @module app/overview/index + * @exports app/overview/index + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.overview', [ + 'ngRoute', + 'app.constants', + 'app.common.services' + ]) + // Route configuration + .config(['$routeProvider', function ($routeProvider) { + $routeProvider + .when('/overview/bmc-reboot', { + 'templateUrl': 'overview/controllers/bmc-reboot-controller.html', + 'controller': 'bmcRebootController', + authenticated: true + }) + .when('/overview/log', { + 'templateUrl': 'overview/controllers/log-controller.html', + 'controller': 'logController', + authenticated: true + }) + .when('/overview/power-operations', { + 'templateUrl': 'overview/controllers/power-operations-controller.html', + 'controller': 'powerOperationsController', + authenticated: true + }) + .when('/overview/unit-id', { + 'templateUrl': 'overview/controllers/unit-id-controller.html', + 'controller': 'unitIdController', + authenticated: true + }) + .when('/overview/system', { + 'templateUrl': 'overview/controllers/system-overview-controller.html', + 'controller': 'systemOverviewController', + authenticated: true + }); + }]); + +})(window.angular); diff --git a/app/overview/styles/bmc-reboot.scss b/app/overview/styles/bmc-reboot.scss new file mode 100644 index 0000000..23d1167 --- /dev/null +++ b/app/overview/styles/bmc-reboot.scss @@ -0,0 +1,27 @@ +// BMC Reboot + +#bmc-reboot { + ul { + margin: 0; + padding: 0; + margin-left: 1.3em; + } + .bmc-reboot__status-log { + color: $darkgrey; + font-weight: 500; + margin-top: 1em; + padding-bottom: .7em; + } + + .bmc-reboot-option { + position: relative; + overflow: hidden; + padding-top: 1.5em; + padding-left: 1.8em; + button { + margin-bottom: 1.5em; + margin-top: 1.5em; + } + } + +} diff --git a/app/overview/styles/index.scss b/app/overview/styles/index.scss new file mode 100644 index 0000000..43fca4b --- /dev/null +++ b/app/overview/styles/index.scss @@ -0,0 +1,5 @@ +@import "./bmc-reboot.scss"; +@import "./log.scss"; +@import "./power-operations.scss"; +@import "./system-overview.scss"; +@import "./unit-id.scss";
\ No newline at end of file diff --git a/app/overview/styles/log.scss b/app/overview/styles/log.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/overview/styles/log.scss diff --git a/app/overview/styles/power-operations.scss b/app/overview/styles/power-operations.scss new file mode 100644 index 0000000..1a517be --- /dev/null +++ b/app/overview/styles/power-operations.scss @@ -0,0 +1,102 @@ +// Power Operations SCSS + +#power-operations { + + .h4 { + font-weight: bold; + } + + // Power op time stamp + .power__status-log { + color: $darkgrey; + font-weight:500; + } + + // Power Curernt status wrapper + .power__current-status { + border-bottom: 1px solid $lightbg__grey; + margin: 2.625em 0 1.2em 0; + .h4 { + padding: 0; + margin: 0 0 .5em 0; + } + } + + // Power state indicator on/off + .power__state { + font-weight: 700; + margin-top: -.3em; + span:before { + content: '\25CF'; + display: inline-block; + font-size: 1.8em; + color: $darkgrey; + margin-right: .1em; + vertical-align: middle; + transform: translateY(-4px); + @include slowTransition-all; + } + } + + // Power bar indicator + .power__indicator-bar { + font-weight: bold; + padding: 1.4em 3em 0; + margin-bottom: 3.750em; + background-size: 200% 100%; + background-image: linear-gradient(to right, $status-ok-light 50%, $lightgrey 50%); + background-position: 100%; + transition: background-position 2s ease; + overflow: hidden; + p { + padding: 0; + margin: 0; + } + &.power__state-on { + background-position: -200%; + .power__state { + span:before { + color: $status-ok; + } + } + } + &.power__state-off { + background-position: -100%; + color: $darkgrey; + .power__state { + span:before { + color: $darkgrey; + } + } + } + &.power__state-indet { + color: $darkgrey; + @include indeterminate-bar; + .power__state { + span:before { + color: $status-warn; + } + } + } + } + + // Power button options + .power-option { + border-top: 1px solid $lightgrey; + padding: 1.8em 0 1em 0; + position: relative; + overflow: hidden; + min-height: 1px; + min-width: 100%; + .btn-secondary { + margin-bottom: .5em; + @include mediaQuery(small) { + min-width: 300px; + } + } + @include mediaQuery(small){ + padding: 1.8em 0 1em 1.8em; + } + } + +} //end power-operations
\ No newline at end of file diff --git a/app/overview/styles/system-overview.scss b/app/overview/styles/system-overview.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/overview/styles/system-overview.scss diff --git a/app/overview/styles/unit-id.scss b/app/overview/styles/unit-id.scss new file mode 100644 index 0000000..cd7a9fe --- /dev/null +++ b/app/overview/styles/unit-id.scss @@ -0,0 +1,19 @@ +// UI Light + +#uid-switch { + .switch {margin-left: 1.7em;} + .uid-switch__label { + padding-bottom: 1.5em; + } + .uid-switch__label p { + margin: 0; + &:first-child {font-weight: 700;} + } + .h4 { + border-bottom: 1px solid $lightbg__grey; + margin: 2.625em 0 1.2em 0; + padding: 0 0 .85em; + font-weight: 700; + } + +}
\ No newline at end of file diff --git a/app/styles/index.scss b/app/styles/index.scss new file mode 100644 index 0000000..32112de --- /dev/null +++ b/app/styles/index.scss @@ -0,0 +1,5 @@ +$charset: "UTF-8"; + +@import "../common/styles/index.scss"; +@import "../login/styles/index.scss"; +@import "../overview/styles/index.scss";
\ No newline at end of file diff --git a/app/templates.js b/app/templates.js new file mode 100644 index 0000000..399c280 --- /dev/null +++ b/app/templates.js @@ -0,0 +1,19 @@ +/** + * A module which contains all compiled templates for the angular application + * + * @module app/templates + * @exports app/templates + * @version 0.0.1 + * @since 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + /** + * Used for gulp template cache plugin. + */ + angular + .module('app.templates', []); + +})(window.angular); diff --git a/app/vendors.js b/app/vendors.js new file mode 100644 index 0000000..31f501e --- /dev/null +++ b/app/vendors.js @@ -0,0 +1,14 @@ +/** + * A module to easily inject vendors dependencies as angular service + * + * @module app/vendors + * @exports app/vendors + * @version 0.0.1 + */ + +window.angular && (function (angular) { + 'use strict'; + + angular + .module('app.vendors', []) +})(window.angular); |