From e38d4a089a7422b2fbe8c710e47f062e597927c7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Dec 2015 16:39:43 -0800 Subject: [PATCH 1/3] GUAC-1406: Add directive for filtering connection group hierarchies. --- .../directives/guacGroupListFilter.js | 212 ++++++++++++++++++ .../templates/guacGroupListFilter.html | 27 +++ 2 files changed, 239 insertions(+) create mode 100644 guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js create mode 100644 guacamole/src/main/webapp/app/groupList/templates/guacGroupListFilter.html diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js new file mode 100644 index 000000000..156a04121 --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which provides a filtering text input field which automatically + * produces a filtered subset of the given connection groups. + */ +angular.module('groupList').directive('guacGroupListFilter', [function guacGroupListFilter() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The property to which a subset of the provided map of connection + * groups will be assigned. + * + * @type Array + */ + filteredConnectionGroups : '=', + + /** + * The placeholder text to display within the filter input field + * when no filter has been provided. + * + * @type String + */ + placeholder : '&', + + /** + * The connection groups to filter, as a map of data source + * identifier to corresponding root group. A subset of this map + * will be exposed as filteredConnectionGroups. + * + * @type Object. + */ + connectionGroups : '&', + + /** + * An array of expressions to filter against for each connection in + * the hierarchy of connections and groups in the provided map. + * These expressions must be Angular expressions which resolve to + * properties on the connections in the provided map. + * + * @type String[] + */ + connectionProperties : '&', + + /** + * An array of expressions to filter against for each connection group + * in the hierarchy of connections and groups in the provided map. + * These expressions must be Angular expressions which resolve to + * properties on the connection groups in the provided map. + * + * @type String[] + */ + connectionGroupProperties : '&' + + }, + + templateUrl: 'app/groupList/templates/guacGroupListFilter.html', + controller: ['$scope', '$injector', function guacGroupListFilterController($scope, $injector) { + + // Required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var FilterPattern = $injector.get('FilterPattern'); + + /** + * The pattern object to use when filtering connections. + * + * @type FilterPattern + */ + var connectionFilterPattern = new FilterPattern($scope.connectionProperties()); + + /** + * The pattern object to use when filtering connection groups. + * + * @type FilterPattern + */ + var connectionGroupFilterPattern = new FilterPattern($scope.connectionGroupProperties()); + + /** + * The filter search string to use to restrict the displayed + * connection groups. + * + * @type String + */ + $scope.searchString = null; + + /** + * Flattens the connection group hierarchy of the given connection + * group such that all descendants are copied as immediate + * children. The hierarchy of nested connection groups is otherwise + * completely preserved. A connection or connection group nested + * two or more levels deep within the hierarchy will thus appear + * within the returned connection group in two places: in its + * original location AND as an immediate child. + * + * @param {ConnectionGroup} connectionGroup + * The connection group whose descendents should be copied as + * first-level children. + * + * @returns {ConnectionGroup} + * A new connection group completely identical to the provided + * connection group, except that absolutely all descendents + * have been copied into the first level of children. + */ + var flattenConnectionGroup = function flattenConnectionGroup(connectionGroup) { + + // Replace connection group with shallow copy + connectionGroup = new ConnectionGroup(connectionGroup); + + // Ensure child arrays are defined and independent copies + connectionGroup.childConnections = angular.copy(connectionGroup.childConnections) || []; + connectionGroup.childConnectionGroups = angular.copy(connectionGroup.childConnectionGroups) || []; + + // Flatten all children to the top-level group + angular.forEach(connectionGroup.childConnectionGroups, function flattenChild(child) { + + var flattenedChild = flattenConnectionGroup(child); + + // Merge all child connections + Array.prototype.push.apply( + connectionGroup.childConnections, + flattenedChild.childConnections + ); + + // Merge all child connection groups + Array.prototype.push.apply( + connectionGroup.childConnectionGroups, + flattenedChild.childConnectionGroups + ); + + }); + + return connectionGroup; + + }; + + /** + * Applies the current filter predicate, filtering all provided + * connection groups and storing the result in + * filteredConnectionGroups. + */ + var updateFilteredConnectionGroups = function updateFilteredConnectionGroups() { + + // Do not apply any filtering (and do not flatten) if no + // search string is provided + if (!$scope.searchString) { + $scope.filteredConnectionGroups = $scope.connectionGroups() || {}; + return; + } + + // Clear all current filtered groups + $scope.filteredConnectionGroups = {}; + + // Re-filter any provided groups + var connectionGroups = $scope.connectionGroups(); + if (connectionGroups) { + angular.forEach(connectionGroups, function updateFilteredConnectionGroup(connectionGroup, dataSource) { + + // Flatten hierarchy of connection group + var filteredGroup = flattenConnectionGroup(connectionGroup); + + // Filter all direct children + filteredGroup.childConnections = filteredGroup.childConnections.filter(connectionFilterPattern.predicate); + filteredGroup.childConnectionGroups = filteredGroup.childConnectionGroups.filter(connectionGroupFilterPattern.predicate); + + // Store now-filtered root + $scope.filteredConnectionGroups[dataSource] = filteredGroup; + + }); + } + + }; + + // Recompile and refilter when pattern is changed + $scope.$watch('searchString', function searchStringChanged(searchString) { + connectionFilterPattern.compile(searchString); + connectionGroupFilterPattern.compile(searchString); + updateFilteredConnectionGroups(); + }); + + // Refilter when items change + $scope.$watchCollection($scope.connectionGroups, function itemsChanged() { + updateFilteredConnectionGroups(); + }); + + }] + + }; +}]); diff --git a/guacamole/src/main/webapp/app/groupList/templates/guacGroupListFilter.html b/guacamole/src/main/webapp/app/groupList/templates/guacGroupListFilter.html new file mode 100644 index 000000000..9ac3a82a9 --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/templates/guacGroupListFilter.html @@ -0,0 +1,27 @@ +
+ + + + + +
From edc3f1921635deb620f735fb6dd9e16a1bc38712 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Dec 2015 16:48:25 -0800 Subject: [PATCH 2/3] GUAC-1406: Add filtering to connection admin UI. --- .../directives/guacSettingsConnections.js | 19 ++++++++++++ .../templates/settingsConnections.html | 30 +++++++++++++------ .../src/main/webapp/translations/de.json | 2 ++ .../src/main/webapp/translations/en.json | 2 ++ .../src/main/webapp/translations/fr.json | 2 ++ .../src/main/webapp/translations/it.json | 2 ++ .../src/main/webapp/translations/nl.json | 2 ++ .../src/main/webapp/translations/ru.json | 2 ++ 8 files changed, 52 insertions(+), 9 deletions(-) diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index d088cbc31..20b5e7d87 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -90,6 +90,25 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe */ $scope.permissions = null; + /** + * Array of all connection properties that are filterable. + * + * @type String[] + */ + $scope.filteredConnectionProperties = [ + 'name', + 'protocol' + ]; + + /** + * Array of all connection group properties that are filterable. + * + * @type String[] + */ + $scope.filteredConnectionGroupProperties = [ + 'name' + ]; + /** * Returns whether critical data has completed being loaded. * diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html index efecf6f8a..ca617364a 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html @@ -24,16 +24,28 @@

{{'SETTINGS_CONNECTIONS.HELP_CONNECTIONS' | translate}}

- -
+ + @@ -41,7 +53,7 @@
diff --git a/guacamole/src/main/webapp/translations/de.json b/guacamole/src/main/webapp/translations/de.json index db6bb3cc5..cd8990583 100644 --- a/guacamole/src/main/webapp/translations/de.json +++ b/guacamole/src/main/webapp/translations/de.json @@ -490,6 +490,8 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "HELP_CONNECTIONS" : "Klicke oder Tippe auf eine Verbindung um diese zu verwalten. Abhänig von Ihrer Zugriffsebene können Verbindungen hinzugefügt, gelöscht oder Parameter (Protokol, Hostname, Port, etc.) geändert werden.", "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index f84368c47..4e6987ca6 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -515,6 +515,8 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.", "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", diff --git a/guacamole/src/main/webapp/translations/fr.json b/guacamole/src/main/webapp/translations/fr.json index 7c9de9576..679df2344 100644 --- a/guacamole/src/main/webapp/translations/fr.json +++ b/guacamole/src/main/webapp/translations/fr.json @@ -453,6 +453,8 @@ "DIALOG_HEADER_ERROR" : "Erreur", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "HELP_CONNECTIONS" : "Cliquer ou appuyer sur une connexion en dessous pour la gérer. Selon vos permissions, les connexions peuvent être ajoutées, supprimées, leur propriétés (protocole, nom d'hôte, port, etc) changées.", "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", diff --git a/guacamole/src/main/webapp/translations/it.json b/guacamole/src/main/webapp/translations/it.json index ab1d6eee9..611c7bc2c 100644 --- a/guacamole/src/main/webapp/translations/it.json +++ b/guacamole/src/main/webapp/translations/it.json @@ -450,6 +450,8 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "HELP_CONNECTIONS" : "Fai click o tap sulla connessione qui sotto per gestire quella connessione. In base al tuo livello di accesso, le connessioni possono essere craete, eliminate, e le relative proprietà (protocol, hostname, port, etc.) possono essere cambiate.", "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", diff --git a/guacamole/src/main/webapp/translations/nl.json b/guacamole/src/main/webapp/translations/nl.json index b2eb185dc..8c0d5d894 100644 --- a/guacamole/src/main/webapp/translations/nl.json +++ b/guacamole/src/main/webapp/translations/nl.json @@ -512,6 +512,8 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "HELP_CONNECTIONS" : "Klik of tik op een verbinding hieronder om die verbinding te beheren. Afhankelijk van uw toegangsniveau kunnen verbindingen worden toegevoegd en verwijderd en hun eigenschappen (protocol, hostname, port, etc.) worden gewijzigd. ", "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index 9b4c8cd11..5a6fed077 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -421,6 +421,8 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "HELP_CONNECTIONS" : "Нажмите на подключение, чтобы управлять им. В зависимости от прав доступа возможно добавление и удаление подключений, а также изменение их свойств (протокол, название сервера, порт и пр.).", "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", From e7d89fc6590d4e8ef0fe0e7e0e2107d5467aa049 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 5 Dec 2015 22:30:42 -0800 Subject: [PATCH 3/3] GUAC-1406: Add filtering to main connection list of the home screen. --- .../app/home/controllers/homeController.js | 18 ++++++++++++++++++ .../src/main/webapp/app/home/styles/home.css | 15 +++++++++++++++ .../main/webapp/app/home/templates/home.html | 13 ++++++++++--- guacamole/src/main/webapp/translations/de.json | 2 ++ guacamole/src/main/webapp/translations/en.json | 2 ++ guacamole/src/main/webapp/translations/fr.json | 2 ++ guacamole/src/main/webapp/translations/it.json | 2 ++ guacamole/src/main/webapp/translations/nl.json | 2 ++ guacamole/src/main/webapp/translations/ru.json | 2 ++ 9 files changed, 55 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index 12eb1c25e..1360ff593 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -44,6 +44,24 @@ angular.module('home').controller('homeController', ['$scope', '$injector', */ $scope.rootConnectionGroups = null; + /** + * Array of all connection properties that are filterable. + * + * @type String[] + */ + $scope.filteredConnectionProperties = [ + 'name' + ]; + + /** + * Array of all connection group properties that are filterable. + * + * @type String[] + */ + $scope.filteredConnectionGroupProperties = [ + 'name' + ]; + /** * Returns whether critical data has completed being loaded. * diff --git a/guacamole/src/main/webapp/app/home/styles/home.css b/guacamole/src/main/webapp/app/home/styles/home.css index d34ca6f1d..f055bd314 100644 --- a/guacamole/src/main/webapp/app/home/styles/home.css +++ b/guacamole/src/main/webapp/app/home/styles/home.css @@ -53,3 +53,18 @@ div.recent-connections div.connection { max-width: 75%; overflow: hidden; } + +.connection-list-ui .header .filter { + margin: 0; + padding: 0.75em 0.5em; +} + +.connection-list-ui .header .filter input { + -moz-border-radius: 0; + -webkit-border-radius: 0; + -khtml-border-radius: 0; + border-radius: 0; + border: none; + border-left: 1px solid rgba(0, 0, 0, 0.125); + background-color: transparent; +} diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html index a2c362ac5..d38d5da6a 100644 --- a/guacamole/src/main/webapp/app/home/templates/home.html +++ b/guacamole/src/main/webapp/app/home/templates/home.html @@ -1,5 +1,5 @@ -

{{'HOME.SECTION_HEADER_ALL_CONNECTIONS' | translate}}

+
+

{{'HOME.SECTION_HEADER_ALL_CONNECTIONS' | translate}}

+ +
diff --git a/guacamole/src/main/webapp/translations/de.json b/guacamole/src/main/webapp/translations/de.json index cd8990583..5940dcd2e 100644 --- a/guacamole/src/main/webapp/translations/de.json +++ b/guacamole/src/main/webapp/translations/de.json @@ -148,6 +148,8 @@ "HOME" : { + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "Keine aktiven Verbindungen.", diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 4e6987ca6..caf3cbdbd 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -148,6 +148,8 @@ "HOME" : { + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "No recent connections.", diff --git a/guacamole/src/main/webapp/translations/fr.json b/guacamole/src/main/webapp/translations/fr.json index 679df2344..e1a835120 100644 --- a/guacamole/src/main/webapp/translations/fr.json +++ b/guacamole/src/main/webapp/translations/fr.json @@ -139,6 +139,8 @@ "HOME" : { + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "Pas de connexion récente.", diff --git a/guacamole/src/main/webapp/translations/it.json b/guacamole/src/main/webapp/translations/it.json index 611c7bc2c..3c71c0841 100644 --- a/guacamole/src/main/webapp/translations/it.json +++ b/guacamole/src/main/webapp/translations/it.json @@ -137,6 +137,8 @@ "HOME" : { + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "Nessuna connessione recente.", diff --git a/guacamole/src/main/webapp/translations/nl.json b/guacamole/src/main/webapp/translations/nl.json index 8c0d5d894..111b99ae8 100644 --- a/guacamole/src/main/webapp/translations/nl.json +++ b/guacamole/src/main/webapp/translations/nl.json @@ -148,6 +148,8 @@ "HOME" : { + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "Geen recente verbindingen.", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index 5a6fed077..822faf72b 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -137,6 +137,8 @@ "HOME" : { + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "Нет недавних подключения.",