From b333b44be29aa8055d65cf72492250c70f9cf51f Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Tue, 2 Jun 2015 11:47:16 +0200 Subject: [PATCH] Revealable text fields, both expanding and masking Certain fields in the details pane must be ellipsized and then expand themselves when clicked on. The ellipsizing should be driven by the width of the field, not the number of characters. Certain fields in the details pane should be masked as a courtesy, such as password environment variables. Note that this does not substitute for real security such as use of kubernetes secrets, instead of environment variables. Both ellipsized and masked fields are revealed in full when clicked on. This patch includes an implementation of this behavior. Some parts of this behavior are CSS driven, and an example CSS file is included. Fixes #16 Closes #21 --- README.md | 4 ++- dist/object-describer.js | 57 ++++++++++++++-------------------- object-describer.js | 34 ++++++++++---------- revealable.css | 46 +++++++++++++++++++++++++++ views/_collapse-long-text.html | 5 --- views/annotations.html | 4 +-- views/container-state.html | 2 +- views/container-statuses.html | 2 +- views/containers.html | 6 ++-- 9 files changed, 95 insertions(+), 65 deletions(-) create mode 100644 revealable.css delete mode 100644 views/_collapse-long-text.html diff --git a/README.md b/README.md index 3639f32..8d4dbd7 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Theme The example theme used in `index.html` is based on bootstrap, however bootstrap is not required as a bower dependency and you can change the theme however you want. +Certain functionality requires CSS, and CSS snippets have been provided. These are generally style agnostic. + Optional Features ----------------- @@ -98,4 +100,4 @@ There are several enhancements being considered for future implementation. Contributing ------------ -Git clone this repo and run `grunt serve`. While the server is running, any time changes are made to the JS or HTML files the build will run automatically. Before committing any changes run the `grunt build` task to make sure dist/object-describer.js has been updated and include the updated file in your commit. \ No newline at end of file +Git clone this repo and run `grunt serve`. While the server is running, any time changes are made to the JS or HTML files the build will run automatically. Before committing any changes run the `grunt build` task to make sure dist/object-describer.js has been updated and include the updated file in your commit. diff --git a/dist/object-describer.js b/dist/object-describer.js index 4933379..59ec9ed 100644 --- a/dist/object-describer.js +++ b/dist/object-describer.js @@ -141,7 +141,12 @@ angular.module('kubernetesUI') scope: { containers: '=' }, - templateUrl: 'views/containers.html' + templateUrl: 'views/containers.html', + link: function($scope, element, attrs) { + $scope.shouldMask = function(name) { + return name.toLowerCase().indexOf('password') !== -1; + }; + } } }) .directive("kubernetesObjectDescribeContainerStatuses", function() { @@ -162,26 +167,19 @@ angular.module('kubernetesUI') templateUrl: 'views/container-state.html' }; }) -.directive("collapseLongText", function() { +.directive("revealableText", function() { return { restrict: 'A', - scope: { - value: '@', - enableCollapse: '=?' // not intended to be passed in, it will be set depending on jquery availability - }, - controller: ["$scope", function($scope) { - // If jquery is available - $scope.enableCollapse = !!window.$; - }], link: function($scope, element, attrs) { - if ($scope.enableCollapse) { - $('.reveal-contents-link', element).click(function (evt) { - $(this).hide(); - $('.reveal-contents', element).show(); - }); - } - }, - templateUrl: 'views/_collapse-long-text.html' + element.addClass('revealable'); + $(element).on("mouseover", function() { + var clickable = element[0].scrollWidth > element[0].clientWidth || element.find(".masked")[0]; + element.toggleClass("clickable", clickable); + }); + $(element).on("click", function() { + element.toggleClass("revealed"); + }); + } } }) .filter("isEmptyObj", function() { @@ -193,28 +191,19 @@ angular.module('kubernetesUI') angular.module('kubernetesUI').run(['$templateCache', function($templateCache) { 'use strict'; - $templateCache.put('views/_collapse-long-text.html', - " 120\">{{value}}\n" + - " 120\">\n" + - " {{value.substring(0, 120)}}...\n" + - " {{value}}\n" + - "" - ); - - $templateCache.put('views/annotations.html', "

Annotations

\n" + " none\n" + "
\n" + "
{{annotationKey}}
\n" + - "
\n" + - "
" + "
{{annotationValue}}
\n" + + " \n" ); $templateCache.put('views/container-state.html', "none\n" + - "\n" + + "\n" + " \n" + " \n" + " Waiting\n" + @@ -240,7 +229,7 @@ angular.module('kubernetesUI').run(['$templateCache', function($templateCache) { "
none
\n" + "
\n" + "
Name
\n" + - "
{{containerStatus.name}}
\n" + + "
{{containerStatus.name}}
\n" + "
State
\n" + "
\n" + " \n" + @@ -261,9 +250,9 @@ angular.module('kubernetesUI').run(['$templateCache', function($templateCache) { "
none
\n" + "
\n" + "
Name
\n" + - "
{{container.name}}
\n" + + "
{{container.name}}
\n" + "
Image
\n" + - "
{{container.image}}
\n" + + "
{{container.image}}
\n" + "
none
\n" + "
Ports
\n" + "
\n" + @@ -275,7 +264,7 @@ angular.module('kubernetesUI').run(['$templateCache', function($templateCache) { "
Env vars
\n" + "
\n" + "
none
\n" + - "
\n" + + "
{{env.name}}={{env.value}}
\n" + "
\n" + "
\n" + "
\n" diff --git a/object-describer.js b/object-describer.js index 0d1151c..0144a5d 100644 --- a/object-describer.js +++ b/object-describer.js @@ -141,7 +141,12 @@ angular.module('kubernetesUI') scope: { containers: '=' }, - templateUrl: 'views/containers.html' + templateUrl: 'views/containers.html', + link: function($scope, element, attrs) { + $scope.shouldMask = function(name) { + return name.toLowerCase().indexOf('password') !== -1; + }; + } } }) .directive("kubernetesObjectDescribeContainerStatuses", function() { @@ -162,26 +167,19 @@ angular.module('kubernetesUI') templateUrl: 'views/container-state.html' }; }) -.directive("collapseLongText", function() { +.directive("revealableText", function() { return { restrict: 'A', - scope: { - value: '@', - enableCollapse: '=?' // not intended to be passed in, it will be set depending on jquery availability - }, - controller: ["$scope", function($scope) { - // If jquery is available - $scope.enableCollapse = !!window.$; - }], link: function($scope, element, attrs) { - if ($scope.enableCollapse) { - $('.reveal-contents-link', element).click(function (evt) { - $(this).hide(); - $('.reveal-contents', element).show(); - }); - } - }, - templateUrl: 'views/_collapse-long-text.html' + element.addClass('revealable'); + $(element).on("mouseover", function() { + var clickable = element[0].scrollWidth > element[0].clientWidth || element.find(".masked")[0]; + element.toggleClass("clickable", clickable); + }); + $(element).on("click", function() { + element.toggleClass("revealed"); + }); + } } }) .filter("isEmptyObj", function() { diff --git a/revealable.css b/revealable.css new file mode 100644 index 0000000..8fd00f3 --- /dev/null +++ b/revealable.css @@ -0,0 +1,46 @@ +.revealable { + white-space: nowrap !important; + text-overflow: ellipsis; + overflow: hidden !important; +} + +.revealable.clickable { + cursor: pointer; +} + +.revealed { + white-space: normal !important; + overflow: visible !important; + cursor: pointer; + overflow-wrap: break-word; +} + +.masked { + color: transparent; + position: relative; + text-ellipsis: none !important; + cursor: pointer; + max-width: 1; + display: inline-block; +} + +.masked:after { + content: "●●●●"; + position: absolute; + left: 0px; + font-weight: bold; + letter-spacing: 2px; + padding-left: 2px; + color: black; + cursor: pointer; + line-height: 1.5; + opacity: 0.5; +} + +.revealed .masked:after { + display: none; +} + +.revealed .masked { + color: black; +} diff --git a/views/_collapse-long-text.html b/views/_collapse-long-text.html deleted file mode 100644 index db24354..0000000 --- a/views/_collapse-long-text.html +++ /dev/null @@ -1,5 +0,0 @@ -{{value}} - - {{value.substring(0, 120)}}... - - \ No newline at end of file diff --git a/views/annotations.html b/views/annotations.html index 0ce79fa..3ea601d 100644 --- a/views/annotations.html +++ b/views/annotations.html @@ -2,5 +2,5 @@

Annotations

none
{{annotationKey}}
-
-
\ No newline at end of file +
{{annotationValue}}
+
diff --git a/views/container-state.html b/views/container-state.html index c341600..7d1cb20 100644 --- a/views/container-state.html +++ b/views/container-state.html @@ -1,5 +1,5 @@ none - + Waiting diff --git a/views/container-statuses.html b/views/container-statuses.html index 455f133..2243628 100644 --- a/views/container-statuses.html +++ b/views/container-statuses.html @@ -1,7 +1,7 @@
none
Name
-
{{containerStatus.name}}
+
{{containerStatus.name}}
State
diff --git a/views/containers.html b/views/containers.html index 48f47b1..51624e5 100644 --- a/views/containers.html +++ b/views/containers.html @@ -1,9 +1,9 @@
none
Name
-
{{container.name}}
+
{{container.name}}
Image
-
{{container.image}}
+
{{container.image}}
none
Ports
@@ -15,7 +15,7 @@
Env vars
none
-
+
{{env.name}}={{env.value}}