mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 21:27:40 +00:00
Merge pull request #294 from glyptodon/connection-filter
GUAC-1406: Implement connection filtering within admin UI and home screen
This commit is contained in:
@@ -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.<String, ConnectionGroup>
|
||||
*/
|
||||
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();
|
||||
});
|
||||
|
||||
}]
|
||||
|
||||
};
|
||||
}]);
|
@@ -0,0 +1,27 @@
|
||||
<div class="group-list-filter filter">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- Filter string -->
|
||||
<input class="search-string" placeholder="{{placeholder()}}" type="text" ng-model="searchString"/>
|
||||
|
||||
</div>
|
@@ -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.
|
||||
*
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright (C) 2014 Glyptodon LLC
|
||||
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
|
||||
@@ -34,11 +34,18 @@
|
||||
</div>
|
||||
|
||||
<!-- All connections for this user -->
|
||||
<h2 class="header">{{'HOME.SECTION_HEADER_ALL_CONNECTIONS' | translate}}</h2>
|
||||
<div class="header">
|
||||
<h2>{{'HOME.SECTION_HEADER_ALL_CONNECTIONS' | translate}}</h2>
|
||||
<guac-group-list-filter connection-groups="rootConnectionGroups"
|
||||
filtered-connection-groups="filteredRootConnectionGroups"
|
||||
placeholder="'HOME.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||
connection-properties="filteredConnectionProperties"
|
||||
connection-group-properties="filteredConnectionGroupProperties"></guac-group-list-filter>
|
||||
</div>
|
||||
<div class="all-connections">
|
||||
<guac-group-list
|
||||
context="context"
|
||||
connection-groups="rootConnectionGroups"
|
||||
connection-groups="filteredRootConnectionGroups"
|
||||
connection-template="'app/home/templates/connection.html'"
|
||||
connection-group-template="'app/home/templates/connectionGroup.html'"
|
||||
page-size="20"></guac-group-list>
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -24,16 +24,28 @@
|
||||
<!-- Connection management -->
|
||||
<p>{{'SETTINGS_CONNECTIONS.HELP_CONNECTIONS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
<!-- Connection management toolbar -->
|
||||
<div class="toolbar">
|
||||
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections()"
|
||||
href="#/manage/{{dataSource}}/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a>
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
|
||||
<a class="add-connection-group button"
|
||||
ng-show="canCreateConnectionGroups()"
|
||||
href="#/manage/{{dataSource}}/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections()"
|
||||
href="#/manage/{{dataSource}}/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a>
|
||||
|
||||
<a class="add-connection-group button"
|
||||
ng-show="canCreateConnectionGroups()"
|
||||
href="#/manage/{{dataSource}}/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Connection filter -->
|
||||
<guac-group-list-filter connection-groups="rootGroups"
|
||||
filtered-connection-groups="filteredRootGroups"
|
||||
placeholder="'SETTINGS_CONNECTIONS.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||
connection-properties="filteredConnectionProperties"
|
||||
connection-group-properties="filteredConnectionGroupProperties"></guac-group-list-filter>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -41,7 +53,7 @@
|
||||
<div class="connection-list">
|
||||
<guac-group-list
|
||||
page-size="25"
|
||||
connection-groups="rootGroups"
|
||||
connection-groups="filteredRootGroups"
|
||||
connection-template="'app/settings/templates/connection.html'"
|
||||
connection-group-template="'app/settings/templates/connectionGroup.html'"/>
|
||||
</div>
|
||||
|
@@ -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.",
|
||||
@@ -490,6 +492,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",
|
||||
|
@@ -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.",
|
||||
@@ -515,6 +517,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",
|
||||
|
@@ -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.",
|
||||
@@ -453,6 +455,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",
|
||||
|
@@ -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.",
|
||||
@@ -450,6 +452,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",
|
||||
|
@@ -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.",
|
||||
@@ -512,6 +514,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",
|
||||
|
@@ -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" : "Нет недавних подключения.",
|
||||
@@ -421,6 +423,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",
|
||||
|
Reference in New Issue
Block a user