summaryrefslogtreecommitdiffstats
path: root/app/common
diff options
context:
space:
mode:
authorbeccabroek <beccabroek@gmail.com>2018-11-07 12:22:31 -0600
committerGunnar Mills <gmills@us.ibm.com>2019-07-09 02:01:31 +0000
commit309b5da3750a3a5b189362e31013d2ab9404e806 (patch)
tree8ee262d8b65eb908f9f81e7337cba671a05be99b /app/common
parentd5bf6baa758cb3e822ed6e9842308915b202d44a (diff)
downloadphosphor-webui-309b5da3750a3a5b189362e31013d2ab9404e806.tar.gz
phosphor-webui-309b5da3750a3a5b189362e31013d2ab9404e806.zip
Create certificate management page
Displays certificates and the ability to add up to one of each type of certificate (as limited by the backend implementation). HTTPS certificate and LDAP client cert are implemented in this commit, with the ability to add more types as needed by adding them to the constants.js CERTIFICATE_TYPES array. Also provides the ability to replace a certificate once it is added. Resolves openbmc/phosphor-webui#43 Tested: loaded onto a witherspoon and able to view and replace both the HTTPS certificate and the LDAP certificate. GUI only allows to upload an LDAP certificate if one doesn't already exist. The GUI limits the user to one file per type as expected at this time and provides the appropriate validation messages. Alert messages appear above the table if the certificate is expired or within 30 days of expiring. Change-Id: I345267280ecd3cb257e9304886cde9ebb69b1240 Signed-off-by: beccabroek <beccabroek@gmail.com> Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Diffstat (limited to 'app/common')
-rw-r--r--app/common/directives/app-navigation.html4
-rw-r--r--app/common/directives/certificate.html57
-rw-r--r--app/common/directives/certificate.js55
-rw-r--r--app/common/services/api-utils.js45
-rw-r--r--app/common/services/constants.js11
-rw-r--r--app/common/styles/base/buttons.scss1
-rw-r--r--app/common/styles/base/colors.scss11
-rw-r--r--app/common/styles/elements/alerts.scss12
8 files changed, 193 insertions, 3 deletions
diff --git a/app/common/directives/app-navigation.html b/app/common/directives/app-navigation.html
index 96c2138..eabb137 100644
--- a/app/common/directives/app-navigation.html
+++ b/app/common/directives/app-navigation.html
@@ -84,6 +84,10 @@
<a href="#/configuration/network" ng-click="closeSubnav()"
tabindex="{{(showSubMenu && firstLevel == 'configuration') ? 0 : -1}}">Network settings</a>
</li>
+ <li ng-class="{'active': (path == '/configuration' || path == '/configuration/certificate')}">
+ <a href="#/configuration/certificate" ng-click="closeSubnav()"
+ tabindex="{{(showSubMenu && firstLevel == 'configuration') ? 0 : -1}}">Certificate management</a>
+ </li>
<li ng-class="{'active': (path == '/configuration' || path == '/configuration/snmp')}">
<a href="#/configuration/snmp" ng-click="closeSubnav()"
tabindex="{{(showSubMenu && firstLevel == 'configuration') ? 0 : -1}}">SNMP settings</a>
diff --git a/app/common/directives/certificate.html b/app/common/directives/certificate.html
new file mode 100644
index 0000000..a46de69
--- /dev/null
+++ b/app/common/directives/certificate.html
@@ -0,0 +1,57 @@
+<div class="table__row-value row column">
+ <div class="certificate__type-cell bold">
+ {{cert.Description}}
+ </div>
+ <div class="certificate__title-inline">
+ Valid from:
+ </div>
+ <div class="certificate__date-cell">
+ {{cert.ValidNotBefore | localeDate}}
+ </div>
+ <div class="certificate__title-inline">
+ Valid until:
+ </div>
+ <div class="certificate__status-cell">
+ <span class="inline"
+ ng-class="{'icon__warning' : cert.isExpiring , 'icon__critical' : cert.isExpired}"
+ ng-if="cert.isExpired || cert.isExpiring"></span>
+ </div>
+ <div class="certificate__date-cell">
+ {{cert.ValidNotAfter | localeDate}}
+ </div>
+ <div class="certificate__buttons-cell">
+ <button type="button" class="btn btn-tertiary certificate__button">
+ <icon file="icon-replace.svg" ng-click="cert.upload = true"
+ aria-label="Replace certificate"></icon>
+ </button>
+ </div>
+ <div ng-show="cert.upload === true" class="upload__certificate">
+ <div class="certificate__upload-chooser row">
+ <div class="small-1 column">
+ <button type="button">
+ <icon file="icon-close.svg" ng-click="cert.upload=false"></icon>
+ </button>
+ </div>
+ <div class="small-2 column">
+ <label for='upload_{{cert.Description + cert.Id}}'>
+ <input name="upload_{{cert.Description + cert.Id}}"
+ id="upload_{{cert.Description + cert.Id}}"
+ type="file" file="cert.file" class="hide"/>
+ <span class="btn btn-secondary">Choose file</span>
+ </label>
+ </div>
+ <div class="small-6 column">
+ <span ng-if="!cert.file">No file selected</span>
+ <span>{{cert.file.name}}</span>
+ <button type="button" ng-if="cert.file.name" ng-click="cert.file = '';">
+ <icon file="icon-close.svg"></icon>
+ </button>
+ </div>
+ <div class="small-3 column">
+ <button type="button" ng-class="{disabled:!cert.file}"
+ class="btn btn-primary"
+ ng-click="replaceCertificate(cert)">Replace</button>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/app/common/directives/certificate.js b/app/common/directives/certificate.js
new file mode 100644
index 0000000..63dc594
--- /dev/null
+++ b/app/common/directives/certificate.js
@@ -0,0 +1,55 @@
+window.angular && (function(angular) {
+ 'use strict';
+
+ angular.module('app.common.directives').directive('certificate', [
+ 'APIUtils',
+ function(APIUtils) {
+ return {
+ 'restrict': 'E',
+ 'template': require('./certificate.html'),
+ 'scope': {'cert': '=', 'reload': '&'},
+ 'controller': [
+ '$scope', 'APIUtils', 'toastService',
+ function($scope, APIUtils, toastService) {
+ var certificateType = 'PEM';
+ $scope.replaceCertificate = function(certificate) {
+ $scope.loading = true;
+ if (certificate.file.name.split('.').pop() !==
+ certificateType.toLowerCase()) {
+ toastService.error(
+ 'Certificate must be replaced with a .pem file.');
+ return;
+ }
+ var file =
+ document
+ .getElementById(
+ 'upload_' + certificate.Description + certificate.Id)
+ .files[0];
+ var reader = new FileReader();
+ reader.onloadend = function(e) {
+ var data = {};
+ data.CertificateString = e.target.result;
+ data.CertificateUri = {'@odata.id': certificate['@odata.id']};
+ data.CertificateType = certificateType;
+ APIUtils.replaceCertificate(data).then(
+ function(data) {
+ $scope.loading = false;
+ toastService.success(
+ certificate.Description + ' was replaced.');
+ $scope.reload();
+ },
+ function(error) {
+ console.log(error);
+ $scope.loading = false;
+ toastService.error(
+ 'Unable to replace ' + certificate.Description);
+ });
+ };
+ reader.readAsBinaryString(file);
+ };
+ }
+ ]
+ };
+ }
+ ]);
+})(window.angular);
diff --git a/app/common/services/api-utils.js b/app/common/services/api-utils.js
index e796f43..46d7d4b 100644
--- a/app/common/services/api-utils.js
+++ b/app/common/services/api-utils.js
@@ -1355,6 +1355,51 @@ window.angular && (function(angular) {
return response.data;
});
},
+ getCertificateLocations: function() {
+ return $http({
+ method: 'GET',
+ url: DataService.getHost() +
+ '/redfish/v1/CertificateService/CertificateLocations',
+ withCredentials: true
+ })
+ .then(function(response) {
+ return response.data;
+ });
+ },
+ getCertificate: function(location) {
+ return $http({
+ method: 'GET',
+ url: DataService.getHost() + location,
+ withCredentials: true
+ })
+ .then(function(response) {
+ return response.data;
+ });
+ },
+ addNewCertificate: function(file, type) {
+ return $http({
+ method: 'POST',
+ url: DataService.getHost() + type.location,
+ headers: {'Content-Type': 'application/x-pem-file'},
+ withCredentials: true,
+ data: file
+ })
+ .then(function(response) {
+ return response.data;
+ });
+ },
+ replaceCertificate: function(data) {
+ return $http({
+ method: 'POST',
+ url: DataService.getHost() +
+ '/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate',
+ withCredentials: true,
+ data: data
+ })
+ .then(function(response) {
+ return response.data;
+ });
+ },
getHardwares: function(callback) {
$http({
method: 'GET',
diff --git a/app/common/services/constants.js b/app/common/services/constants.js
index dd1012c..dabbc77 100644
--- a/app/common/services/constants.js
+++ b/app/common/services/constants.js
@@ -20,6 +20,17 @@ window.angular && (function(angular) {
SUCCESS_STATUS: 'ok',
SUCCESS_MESSAGE: '200 OK'
},
+ CERTIFICATE_TYPES: [
+ {
+ 'Description': 'HTTPS Certificate',
+ 'location':
+ '/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates'
+ },
+ {
+ 'Description': 'LDAP Certificate',
+ 'location': '/redfish/v1/AccountService/LDAP/Certificates'
+ }
+ ],
CHASSIS_POWER_STATE: {
on: 'On',
on_code: 'xyz.openbmc_project.State.Chassis.PowerState.On',
diff --git a/app/common/styles/base/buttons.scss b/app/common/styles/base/buttons.scss
index 9aeb725..a04aebd 100644
--- a/app/common/styles/base/buttons.scss
+++ b/app/common/styles/base/buttons.scss
@@ -63,6 +63,7 @@ button,
svg {
height: 1.2em;
width: auto;
+ max-width: 100%;
}
}
diff --git a/app/common/styles/base/colors.scss b/app/common/styles/base/colors.scss
index f5f93f0..bb07bd0 100644
--- a/app/common/styles/base/colors.scss
+++ b/app/common/styles/base/colors.scss
@@ -42,6 +42,11 @@ $darkblue: #1e3359;
$purple: #5A3EC8;
$field__disabled: #e6e6e6;
$field__focus: #3C6DEF;
+$btn__disabled-txt: #a6a5a6;
+$btn__disabled-bg: #d8d8d8;
+$btn__disabled-border: #CCCCCC;
+$primebtn__disabled-txt: #999999;
+$primebtn__disabled-bg: #CCCCCC;
// Dark background
$darkbg__grey: #E3ECEC;
@@ -89,7 +94,8 @@ $critical-lightbg: #da1416;
$critical-darkbg: #ff5c49;
$severity-medium-lightbg: #dc267f;
$medium-darkbg: #FF509E;
-$warning-lightbg: #ff836f;
+$warning-lightbg: #fff8e4;
+$warning-border: #ffdf99;
$warning-darkbg: #ffb000;
$low-lightbg: #c42cd6;
$normal: #00baa1;
@@ -125,5 +131,8 @@ $nav__top-level-color: #1a273b;
$nav__second-level-color: #e6e9ed;
$nav__second-level-text-color: #4b5d78;
+//Upload
+$upload__background: #f0f2f5;
+
//Loader
$loaderColor: $color--blue-50;
diff --git a/app/common/styles/elements/alerts.scss b/app/common/styles/elements/alerts.scss
index 6242257..8853c70 100644
--- a/app/common/styles/elements/alerts.scss
+++ b/app/common/styles/elements/alerts.scss
@@ -1,10 +1,18 @@
//Fixed alerts
-.alert-danger{
+.alert-danger {
background-color: $alert__danger;
border-color: $critical-lightbg;
border-radius: 0;
- color: #333;
+ color: $black;
+ text-align: left;
+}
+
+.alert-warning {
+ background-color: $warning-lightbg;
+ border-color: $warning-border;
+ border-radius: 0;
+ color: $black;
text-align: left;
}
OpenPOWER on IntegriCloud