GUAC-586: Add support for data sources to connection and connection group management.

This commit is contained in:
Michael Jumper
2015-09-02 16:09:29 -07:00
parent 361e985ae1
commit ddd144fc47
8 changed files with 139 additions and 79 deletions

View File

@@ -123,7 +123,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
}) })
// Management screen // Management screen
.when('/settings/:tab', { .when('/settings/:dataSource?/:tab', {
title : 'APP.NAME', title : 'APP.NAME',
bodyClassName : 'settings', bodyClassName : 'settings',
templateUrl : 'app/settings/templates/settings.html', templateUrl : 'app/settings/templates/settings.html',
@@ -132,7 +132,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
}) })
// Connection editor // Connection editor
.when('/manage/connections/:id?', { .when('/manage/:dataSource/connections/:id?', {
title : 'APP.NAME', title : 'APP.NAME',
bodyClassName : 'manage', bodyClassName : 'manage',
templateUrl : 'app/manage/templates/manageConnection.html', templateUrl : 'app/manage/templates/manageConnection.html',
@@ -141,7 +141,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
}) })
// Connection group editor // Connection group editor
.when('/manage/connectionGroups/:id?', { .when('/manage/:dataSource/connectionGroups/:id?', {
title : 'APP.NAME', title : 'APP.NAME',
bodyClassName : 'manage', bodyClassName : 'manage',
templateUrl : 'app/manage/templates/manageConnectionGroup.html', templateUrl : 'app/manage/templates/manageConnectionGroup.html',

View File

@@ -56,6 +56,14 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
} }
}; };
/**
* The unique identifier of the data source containing the connection being
* edited.
*
* @type String
*/
var dataSource = $routeParams.dataSource;
/** /**
* The identifier of the original connection from which this connection is * The identifier of the original connection from which this connection is
* being cloned. Only valid if this is a new connection. * being cloned. Only valid if this is a new connection.
@@ -178,19 +186,23 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
}; };
// Pull connection attribute schema // Pull connection attribute schema
schemaService.getConnectionAttributes().success(function attributesReceived(attributes) { schemaService.getConnectionAttributes(dataSource)
.success(function attributesReceived(attributes) {
$scope.attributes = attributes; $scope.attributes = attributes;
}); });
// Pull connection group hierarchy // Pull connection group hierarchy
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, connectionGroupService.getConnectionGroupTree(
[PermissionSet.ObjectPermissionType.ADMINISTER]) dataSource,
ConnectionGroup.ROOT_IDENTIFIER,
[PermissionSet.ObjectPermissionType.ADMINISTER]
)
.success(function connectionGroupReceived(rootGroup) { .success(function connectionGroupReceived(rootGroup) {
$scope.rootGroup = rootGroup; $scope.rootGroup = rootGroup;
}); });
// Query the user's permissions for the current connection // Query the user's permissions for the current connection
permissionService.getPermissions(authenticationService.getCurrentUsername()) permissionService.getPermissions(dataSource, authenticationService.getCurrentUsername())
.success(function permissionsReceived(permissions) { .success(function permissionsReceived(permissions) {
$scope.permissions = permissions; $scope.permissions = permissions;
@@ -220,7 +232,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
}); });
// Get protocol metadata // Get protocol metadata
schemaService.getProtocols().success(function protocolsReceived(protocols) { schemaService.getProtocols(dataSource)
.success(function protocolsReceived(protocols) {
$scope.protocols = protocols; $scope.protocols = protocols;
}); });
@@ -233,12 +246,14 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
if (identifier) { if (identifier) {
// Pull data from existing connection // Pull data from existing connection
connectionService.getConnection(identifier).success(function connectionRetrieved(connection) { connectionService.getConnection(dataSource, identifier)
.success(function connectionRetrieved(connection) {
$scope.connection = connection; $scope.connection = connection;
}); });
// Pull connection history // Pull connection history
connectionService.getConnectionHistory(identifier).success(function historyReceived(historyEntries) { connectionService.getConnectionHistory(dataSource, identifier)
.success(function historyReceived(historyEntries) {
// Wrap all history entries for sake of display // Wrap all history entries for sake of display
$scope.historyEntryWrappers = []; $scope.historyEntryWrappers = [];
@@ -249,7 +264,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
}); });
// Pull connection parameters // Pull connection parameters
connectionService.getConnectionParameters(identifier).success(function parametersReceived(parameters) { connectionService.getConnectionParameters(dataSource, identifier)
.success(function parametersReceived(parameters) {
$scope.parameters = parameters; $scope.parameters = parameters;
}); });
} }
@@ -258,7 +274,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
else if (cloneSourceIdentifier) { else if (cloneSourceIdentifier) {
// Pull data from cloned connection // Pull data from cloned connection
connectionService.getConnection(cloneSourceIdentifier).success(function connectionRetrieved(connection) { connectionService.getConnection(dataSource, cloneSourceIdentifier)
.success(function connectionRetrieved(connection) {
$scope.connection = connection; $scope.connection = connection;
// Clear the identifier field because this connection is new // Clear the identifier field because this connection is new
@@ -269,7 +286,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
$scope.historyEntryWrappers = []; $scope.historyEntryWrappers = [];
// Pull connection parameters from cloned connection // Pull connection parameters from cloned connection
connectionService.getConnectionParameters(cloneSourceIdentifier).success(function parametersReceived(parameters) { connectionService.getConnectionParameters(dataSource, cloneSourceIdentifier)
.success(function parametersReceived(parameters) {
$scope.parameters = parameters; $scope.parameters = parameters;
}); });
} }
@@ -332,7 +350,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
* Cancels all pending edits, returning to the management page. * Cancels all pending edits, returning to the management page.
*/ */
$scope.cancel = function cancel() { $scope.cancel = function cancel() {
$location.path('/settings/connections'); $location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
}; };
/** /**
@@ -340,7 +358,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
* which is prepopulated with the data from the connection currently being edited. * which is prepopulated with the data from the connection currently being edited.
*/ */
$scope.cloneConnection = function cloneConnection() { $scope.cloneConnection = function cloneConnection() {
$location.path('/manage/connections').search('clone', identifier); $location.path('/manage/' + encodeURIComponent(dataSource) + '/connections').search('clone', identifier);
}; };
/** /**
@@ -352,9 +370,9 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
$scope.connection.parameters = $scope.parameters; $scope.connection.parameters = $scope.parameters;
// Save the connection // Save the connection
connectionService.saveConnection($scope.connection) connectionService.saveConnection(dataSource, $scope.connection)
.success(function savedConnection() { .success(function savedConnection() {
$location.path('/settings/connections'); $location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
}) })
// Notify of any errors // Notify of any errors
@@ -402,9 +420,9 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
var deleteConnectionImmediately = function deleteConnectionImmediately() { var deleteConnectionImmediately = function deleteConnectionImmediately() {
// Delete the connection // Delete the connection
connectionService.deleteConnection($scope.connection) connectionService.deleteConnection(dataSource, $scope.connection)
.success(function deletedConnection() { .success(function deletedConnection() {
$location.path('/settings/connections'); $location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
}) })
// Notify of any errors // Notify of any errors

View File

@@ -51,6 +51,14 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
} }
}; };
/**
* The unique identifier of the data source containing the connection group
* being edited.
*
* @type String
*/
var dataSource = $routeParams.dataSource;
/** /**
* The identifier of the connection group being edited. If a new connection * The identifier of the connection group being edited. If a new connection
* group is being created, this will not be defined. * group is being created, this will not be defined.
@@ -123,12 +131,13 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
}; };
// Pull connection group attribute schema // Pull connection group attribute schema
schemaService.getConnectionGroupAttributes().success(function attributesReceived(attributes) { schemaService.getConnectionGroupAttributes(dataSource)
.success(function attributesReceived(attributes) {
$scope.attributes = attributes; $scope.attributes = attributes;
}); });
// Query the user's permissions for the current connection group // Query the user's permissions for the current connection group
permissionService.getPermissions(authenticationService.getCurrentUsername()) permissionService.getPermissions(dataSource, authenticationService.getCurrentUsername())
.success(function permissionsReceived(permissions) { .success(function permissionsReceived(permissions) {
$scope.permissions = permissions; $scope.permissions = permissions;
@@ -150,14 +159,19 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
// Pull connection group hierarchy // Pull connection group hierarchy
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER]) connectionGroupService.getConnectionGroupTree(
dataSource,
ConnectionGroup.ROOT_IDENTIFIER,
[PermissionSet.ObjectPermissionType.ADMINISTER]
)
.success(function connectionGroupReceived(rootGroup) { .success(function connectionGroupReceived(rootGroup) {
$scope.rootGroup = rootGroup; $scope.rootGroup = rootGroup;
}); });
// If we are editing an existing connection group, pull its data // If we are editing an existing connection group, pull its data
if (identifier) { if (identifier) {
connectionGroupService.getConnectionGroup(identifier).success(function connectionGroupReceived(connectionGroup) { connectionGroupService.getConnectionGroup(dataSource, identifier)
.success(function connectionGroupReceived(connectionGroup) {
$scope.connectionGroup = connectionGroup; $scope.connectionGroup = connectionGroup;
}); });
} }
@@ -187,7 +201,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
* Cancels all pending edits, returning to the management page. * Cancels all pending edits, returning to the management page.
*/ */
$scope.cancel = function cancel() { $scope.cancel = function cancel() {
$location.path('/settings/connections'); $location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
}; };
/** /**
@@ -197,9 +211,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
$scope.saveConnectionGroup = function saveConnectionGroup() { $scope.saveConnectionGroup = function saveConnectionGroup() {
// Save the connection // Save the connection
connectionGroupService.saveConnectionGroup($scope.connectionGroup) connectionGroupService.saveConnectionGroup(dataSource, $scope.connectionGroup)
.success(function savedConnectionGroup() { .success(function savedConnectionGroup() {
$location.path('/settings/connections'); $location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
}) })
// Notify of any errors // Notify of any errors
@@ -247,9 +261,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() { var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() {
// Delete the connection group // Delete the connection group
connectionGroupService.deleteConnectionGroup($scope.connectionGroup) connectionGroupService.deleteConnectionGroup(dataSource, $scope.connectionGroup)
.success(function deletedConnectionGroup() { .success(function deletedConnectionGroup() {
$location.path('/settings/connections'); $location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
}) })
// Notify of any errors // Notify of any errors

View File

@@ -38,6 +38,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
var connectionGroupService = $injector.get('connectionGroupService'); var connectionGroupService = $injector.get('connectionGroupService');
var dataSourceService = $injector.get('dataSourceService'); var dataSourceService = $injector.get('dataSourceService');
var permissionService = $injector.get('permissionService'); var permissionService = $injector.get('permissionService');
var translationStringService = $injector.get('translationStringService');
var service = {}; var service = {};
@@ -167,12 +168,12 @@ angular.module('navigation').factory('userPageService', ['$injector',
var pages = []; var pages = [];
var canManageUsers = false; var canManageUsers = [];
var canManageConnections = false; var canManageConnections = [];
var canManageSessions = false; var canManageSessions = [];
// Inspect the contents of each provided permission set // Inspect the contents of each provided permission set
angular.forEach(permissionSets, function inspectPermissions(permissions) { angular.forEach(permissionSets, function inspectPermissions(permissions, dataSource) {
permissions = angular.copy(permissions); permissions = angular.copy(permissions);
@@ -187,8 +188,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
authenticationService.getCurrentUsername()); authenticationService.getCurrentUsername());
// Determine whether the current user needs access to the user management UI // Determine whether the current user needs access to the user management UI
canManageUsers = canManageUsers || if (
// System permissions // System permissions
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER) || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER)
@@ -200,11 +200,12 @@ angular.module('navigation').factory('userPageService', ['$injector',
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
// Permission to administer users // Permission to administer users
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
)
canManageUsers.push(dataSource);
// Determine whether the current user needs access to the connection management UI // Determine whether the current user needs access to the connection management UI
canManageConnections = canManageConnections || if (
// System permissions // System permissions
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION) || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)
@@ -220,18 +221,21 @@ angular.module('navigation').factory('userPageService', ['$injector',
// Permission to administer connections or connection groups // Permission to administer connections or connection groups
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
)
canManageConnections.push(dataSource);
// Determine whether the current user needs access to the session management UI // Determine whether the current user needs access to the session management UI
canManageSessions = canManageSessions || if (
// A user must be a system administrator to manage sessions // A user must be a system administrator to manage sessions
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER); PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
)
canManageSessions.push(dataSource);
}); });
// If user can manage sessions, add link to sessions management page // If user can manage sessions, add link to sessions management page
if (canManageSessions) { if (canManageSessions.length) {
pages.push(new PageDefinition({ pages.push(new PageDefinition({
name : 'USER_MENU.ACTION_MANAGE_SESSIONS', name : 'USER_MENU.ACTION_MANAGE_SESSIONS',
url : '/settings/sessions' url : '/settings/sessions'
@@ -239,20 +243,23 @@ angular.module('navigation').factory('userPageService', ['$injector',
} }
// If user can manage users, add link to user management page // If user can manage users, add link to user management page
if (canManageUsers) { if (canManageUsers.length) {
pages.push(new PageDefinition({ pages.push(new PageDefinition({
name : 'USER_MENU.ACTION_MANAGE_USERS', name : 'USER_MENU.ACTION_MANAGE_USERS',
url : '/settings/users' url : '/settings/users'
})); }));
} }
// If user can manage connections, add link to connections management page // If user can manage connections, add links for connection management pages
if (canManageConnections) { angular.forEach(canManageConnections, function addConnectionManagementLink(dataSource) {
pages.push(new PageDefinition({ pages.push(new PageDefinition({
name : 'USER_MENU.ACTION_MANAGE_CONNECTIONS', name : [
url : '/settings/connections' 'USER_MENU.ACTION_MANAGE_CONNECTIONS',
translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME'
],
url : '/settings/' + encodeURIComponent(dataSource) + '/connections'
})); }));
} });
// Add link to user preferences (always accessible) // Add link to user preferences (always accessible)
pages.push(new PageDefinition({ pages.push(new PageDefinition({

View File

@@ -41,13 +41,18 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
var PermissionSet = $injector.get('PermissionSet'); var PermissionSet = $injector.get('PermissionSet');
// Required services // Required services
var $location = $injector.get('$location'); var $routeParams = $injector.get('$routeParams');
var authenticationService = $injector.get('authenticationService'); var authenticationService = $injector.get('authenticationService');
var connectionGroupService = $injector.get('connectionGroupService'); var connectionGroupService = $injector.get('connectionGroupService');
var dataSourceService = $injector.get('dataSourceService');
var guacNotification = $injector.get('guacNotification'); var guacNotification = $injector.get('guacNotification');
var permissionService = $injector.get('permissionService'); var permissionService = $injector.get('permissionService');
// Identifier of the current user /**
* The identifier of the current user.
*
* @type String
*/
var currentUsername = authenticationService.getCurrentUsername(); var currentUsername = authenticationService.getCurrentUsername();
/** /**
@@ -62,12 +67,19 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
} }
}; };
/**
* The identifier of the currently-selected data source.
*
* @type String
*/
$scope.dataSource = $routeParams.dataSource;
/** /**
* The root connection group of the connection group hierarchy. * The root connection group of the connection group hierarchy.
* *
* @type ConnectionGroup * @type Object.<String, ConnectionGroup>
*/ */
$scope.rootGroup = null; $scope.rootGroups = null;
/** /**
* Whether the current user can manage connections. If the current * Whether the current user can manage connections. If the current
@@ -98,7 +110,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
* All permissions associated with the current user, or null if the * All permissions associated with the current user, or null if the
* user's permissions have not yet been loaded. * user's permissions have not yet been loaded.
* *
* @type PermissionSet * @type Object.<String, PermissionSet>
*/ */
$scope.permissions = null; $scope.permissions = null;
@@ -112,19 +124,24 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
$scope.isLoaded = function isLoaded() { $scope.isLoaded = function isLoaded() {
return $scope.rootGroup !== null return $scope.rootGroup !== null
&& $scope.permissions !== null && $scope.permissions !== null;
&& $scope.canManageConnections !== null
&& $scope.canCreateConnections !== null
&& $scope.canCreateConnectionGroups !== null;
}; };
$scope.canManageConnections = true;
$scope.canCreateConnections = true;
$scope.canCreateConnectionGroups = true;
// Retrieve current permissions // Retrieve current permissions
permissionService.getPermissions(currentUsername) dataSourceService.apply(
.success(function permissionsRetrieved(permissions) { permissionService.getPermissions,
[$scope.dataSource],
currentUsername
)
.then(function permissionsRetrieved(permissions) {
$scope.permissions = permissions; $scope.permissions = permissions;
/*
// Ignore permission to update root group // Ignore permission to update root group
PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER);
@@ -154,14 +171,18 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
// Return to home if there's nothing to do here // Return to home if there's nothing to do here
if (!$scope.canManageConnections) if (!$scope.canManageConnections)
$location.path('/'); $location.path('/');
*/
}); });
// Retrieve all connections for which we have UPDATE or DELETE permission // Retrieve all connections for which we have UPDATE or DELETE permission
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, dataSourceService.apply(
[PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE]) connectionGroupService.getConnectionGroupTree,
.success(function connectionGroupReceived(rootGroup) { [$scope.dataSource],
$scope.rootGroup = rootGroup; ConnectionGroup.ROOT_IDENTIFIER,
[PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE]
)
.then(function connectionGroupsReceived(rootGroups) {
$scope.rootGroups = rootGroups;
}); });
}] }]

View File

@@ -1,4 +1,4 @@
<a ng-href="#/manage/connections/{{item.identifier}}"> <a ng-href="#/manage/{{item.dataSource}}/connections/{{item.identifier}}">
<!-- <!--
Copyright (C) 2014 Glyptodon LLC Copyright (C) 2014 Glyptodon LLC

View File

@@ -1,4 +1,4 @@
<a ng-href="#/manage/connectionGroups/{{item.identifier}}"> <a ng-href="#/manage/{{item.dataSource}}/connectionGroups/{{item.identifier}}">
<!-- <!--
Copyright (C) 2014 Glyptodon LLC Copyright (C) 2014 Glyptodon LLC

View File

@@ -29,11 +29,11 @@
<a class="add-connection button" <a class="add-connection button"
ng-show="canCreateConnections" ng-show="canCreateConnections"
href="#/manage/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a> href="#/manage/{{dataSource}}/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a>
<a class="add-connection-group button" <a class="add-connection-group button"
ng-show="canCreateConnectionGroups" ng-show="canCreateConnectionGroups"
href="#/manage/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a> href="#/manage/{{dataSource}}/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
</div> </div>
@@ -41,7 +41,7 @@
<div class="connection-list"> <div class="connection-list">
<guac-group-list <guac-group-list
page-size="25" page-size="25"
connection-group="rootGroup" connection-groups="rootGroups"
connection-template="'app/settings/templates/connection.html'" connection-template="'app/settings/templates/connection.html'"
connection-group-template="'app/settings/templates/connectionGroup.html'"/> connection-group-template="'app/settings/templates/connectionGroup.html'"/>
</div> </div>