Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()

// Required services
var activeConnectionService = $injector.get('activeConnectionService');
var authenticationService = $injector.get('authenticationService');
var dataSourceService = $injector.get('dataSourceService');
var requestService = $injector.get('requestService');

Expand All @@ -109,6 +110,14 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
*/
var connectionCount = {};

/**
* Like connectionCount, but restricted to the connections owned
* by the currently-authenticated user.
*
* @type Object.<String, Object.<String, Number>>
*/
var userConnectionCount = {};

/**
* A list of all items which should appear at the root level. As
* connections and connection groups from multiple data sources may
Expand Down Expand Up @@ -137,6 +146,38 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
return connectionCount[dataSource][connection.identifier];
};

/**
* Returns the number of connections by the current user through
* the given connection group, by summing across all descendants.
*
* @param {String} dataSource
* @param {ConnectionGroup} connectionGroup
* @returns {Number}
*/
var countUserActiveConnectionGroups = function countUserActiveConnectionGroups(dataSource, connectionGroup) {
let count = 0;
const userConnections = userConnectionCount[dataSource];

if (!userConnections)
return 0;

// Count active connections through child connections
if (connectionGroup.childConnections) {
connectionGroup.childConnections.forEach(function(child) {
count += userConnections[child.identifier] ?? 0;
});
}

// Recursively count active connections through child connection groups
if (connectionGroup.childConnectionGroups) {
connectionGroup.childConnectionGroups.forEach(function(child) {
count += countUserActiveConnectionGroups(dataSource, child);
});
}

return count;
};

/**
* Returns whether a @link{GroupListItem} of the given type can be
* displayed. If there is no template associated with the given
Expand Down Expand Up @@ -184,7 +225,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
rootItem = GroupListItem.fromConnectionGroup(dataSource, connectionGroup,
$scope.isVisible(GroupListItem.Type.CONNECTION),
$scope.isVisible(GroupListItem.Type.SHARING_PROFILE),
countActiveConnections);
countActiveConnections, null, countUserActiveConnectionGroups);

// If root group is to be shown, add it as a root item
if ($scope.showRootGroup)
Expand All @@ -206,8 +247,13 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
)
.then(function activeConnectionsRetrieved(activeConnectionMap) {

const currentUsername = authenticationService.getCurrentUsername();

// Within each data source, count each active connection by identifier
angular.forEach(activeConnectionMap, function addActiveConnections(activeConnections, dataSource) {

userConnectionCount[dataSource] ??= {};

angular.forEach(activeConnections, function addActiveConnection(activeConnection) {

// If counter already exists, increment
Expand All @@ -219,6 +265,12 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
else
connectionCount[dataSource][identifier] = 1;

// Track connections belonging to the current user
if (activeConnection.username === currentUsername) {
userConnectionCount[dataSource][identifier] ??= 0;
userConnectionCount[dataSource][identifier]++;
}

});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ angular.module('groupList').factory('GroupListItem', ['$injector', function defi
return null;
});

/**
* Returns the number of available connections for this connection
* group.
*
* @returns {Number}
* The number of available connections for this connection group.
*/
this.getAvailableConnections = template.getAvailableConnections || (function getAvailableConnections() {
return null;
});

/**
* Returns the unique string identifier that must be used when
* connecting to a connection or connection group represented by this
Expand Down Expand Up @@ -178,6 +189,18 @@ angular.module('groupList').factory('GroupListItem', ['$injector', function defi

};

/**
* Returns the unique maximum number of connections that can be active
* for this connection group, if known. If unknown, null may be returned.
*
* @returns {Number}
* The unique maximum number of connections that can be active for
* this connection group.
*/
this.getEffectiveMaxConnections = template.getEffectiveMaxConnections || (function getEffectiveMaxConnections() {
return null;
});

/**
* The connection, connection group, or sharing profile whose data is
* exposed within this GroupListItem. If the type of this GroupListItem
Expand Down Expand Up @@ -312,7 +335,8 @@ angular.module('groupList').factory('GroupListItem', ['$injector', function defi
*/
GroupListItem.fromConnectionGroup = function fromConnectionGroup(dataSource,
connectionGroup, includeConnections, includeSharingProfiles,
countActiveConnections, countActiveConnectionGroups) {
countActiveConnections, countActiveConnectionGroups,
countUserActiveConnectionGroups) {

var children = [];

Expand All @@ -329,7 +353,8 @@ angular.module('groupList').factory('GroupListItem', ['$injector', function defi
connectionGroup.childConnectionGroups.forEach(function addChildGroup(child) {
children.push(GroupListItem.fromConnectionGroup(dataSource,
child, includeConnections, includeSharingProfiles,
countActiveConnections, countActiveConnectionGroups));
countActiveConnections, countActiveConnectionGroups,
countUserActiveConnectionGroups));
});
}

Expand Down Expand Up @@ -360,6 +385,73 @@ angular.module('groupList').factory('GroupListItem', ['$injector', function defi

},

// Available slots considering both global and per-user limits
getAvailableConnections : function getAvailableConnections() {
// Get total active connections across all children
let totalActive = 0;
connectionGroup.childConnections.forEach(function addChildConnection(child) {
if (!countActiveConnections)
return;

totalActive += countActiveConnections(dataSource, child) ?? 0;
});

// Get current user's active connections across all children
if (!countUserActiveConnectionGroups)
return totalActive;
const userActive = countUserActiveConnectionGroups(dataSource, connectionGroup) ?? 0;

return this.getEffectiveMaxConnections() - Math.max(totalActive, userActive);
},

// Effective max slots from this user's perspective
getEffectiveMaxConnections : function getEffectiveMaxConnections() {

/**
* Parses the given value as an integer, returning Infinity if the
* value is not a valid integer.
*
* @param {String} value
* The value to parse as an integer.
*
* @returns {Number}
* The parsed integer, or Infinity if the given value is not a
* valid integer.
*/
const parseIntOrInfinity = (value) => {
const parsed = Number.parseInt(value, 10);
return Number.isNaN(parsed) ? Infinity : parsed;
};

// Group limits
const attrs = connectionGroup.attributes ?? {};
const maxGlobal = parseIntOrInfinity(attrs['max-connections']);
const maxPerUser = parseIntOrInfinity(attrs['max-connections-per-user']);

// Per-connection limits are summed across all children
let maxPerConnection = Infinity;
connectionGroup.childConnections.forEach(function addChildConnection(child) {
const childAttrs = child.attributes ?? {};
const childMaxGlobal = parseIntOrInfinity(childAttrs['max-connections']);
const childMaxPerUser = parseIntOrInfinity(childAttrs['max-connections-per-user']);

// If neither limit is set, this child does not affect the effective max
if (childMaxGlobal === Infinity && childMaxPerUser === Infinity)
return;

maxPerConnection = maxPerConnection === Infinity ? 0 : maxPerConnection;
maxPerConnection += Math.min(childMaxGlobal, childMaxPerUser);
});

const minValue = Math.min(maxGlobal, maxPerUser, maxPerConnection);

// If the minimum value is Infinity, it means there are no effective
// limits, so return null
if (minValue === Infinity)
return null;

return minValue;
},

// Wrapped item
wrappedItem : connectionGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@
<!-- Connection group name -->
<span class="name">{{item.name}}</span>

<!-- Available vs. total connection slots (respects global and per-user limits) -->
<span class="activeUserCount"
ng-if="item.balancing && item.getEffectiveMaxConnections() !== null"
translate="HOME.INFO_AVAILABLE_CONNECTIONS"
translate-values="{AVAILABLE: item.getAvailableConnections(), TOTAL: item.getEffectiveMaxConnections()}"></span>

</a>
1 change: 1 addition & 0 deletions guacamole/src/main/frontend/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",

"INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT",
"INFO_AVAILABLE_CONNECTIONS" : "{AVAILABLE} of {TOTAL} {TOTAL, plural, one{connection} other{connections}} available",

"INFO_NO_RECENT_CONNECTIONS" : "No recent connections.",

Expand Down
1 change: 1 addition & 0 deletions guacamole/src/main/frontend/src/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",

"INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT",
"INFO_AVAILABLE_CONNECTIONS" : "{AVAILABLE} {AVAILABLE, plural, one{connexion disponible} other{connexions disponibles}} sur {TOTAL}",

"INFO_NO_RECENT_CONNECTIONS" : "Pas de connexion récente.",

Expand Down
Loading