mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 13:41:21 +00:00
GUACAMOLE-723: Add interface for switching between multiple active connections.
This commit is contained in:
@@ -24,22 +24,25 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
function clientController($scope, $routeParams, $injector) {
|
||||
|
||||
// Required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var ManagedClient = $injector.get('ManagedClient');
|
||||
var ManagedClientState = $injector.get('ManagedClientState');
|
||||
var ManagedFilesystem = $injector.get('ManagedFilesystem');
|
||||
var ScrollState = $injector.get('ScrollState');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var clipboardService = $injector.get('clipboardService');
|
||||
var guacClientManager = $injector.get('guacClientManager');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var iconService = $injector.get('iconService');
|
||||
var preferenceService = $injector.get('preferenceService');
|
||||
var requestService = $injector.get('requestService');
|
||||
var tunnelService = $injector.get('tunnelService');
|
||||
var userPageService = $injector.get('userPageService');
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var clipboardService = $injector.get('clipboardService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
var guacClientManager = $injector.get('guacClientManager');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var iconService = $injector.get('iconService');
|
||||
var preferenceService = $injector.get('preferenceService');
|
||||
var requestService = $injector.get('requestService');
|
||||
var tunnelService = $injector.get('tunnelService');
|
||||
var userPageService = $injector.get('userPageService');
|
||||
|
||||
/**
|
||||
* The minimum number of pixels a drag gesture must move to result in the
|
||||
@@ -264,6 +267,55 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
*/
|
||||
$scope.client = guacClientManager.getManagedClient($routeParams.id, $routeParams.params);
|
||||
|
||||
/**
|
||||
* All active clients which are not the current client ($scope.client).
|
||||
* Each key is the ID of the connection used by that client.
|
||||
*
|
||||
* @type Object.<String, ManagedClient>
|
||||
*/
|
||||
$scope.otherClients = (function getOtherClients(clients) {
|
||||
var otherClients = angular.extend({}, clients);
|
||||
delete otherClients[$scope.client.id];
|
||||
return otherClients;
|
||||
})(guacClientManager.getManagedClients());
|
||||
|
||||
/**
|
||||
* Map of data source identifier to the root connection group of that data
|
||||
* source, or null if the connection group hierarchy has not yet been
|
||||
* loaded.
|
||||
*
|
||||
* @type Object.<String, ConnectionGroup>
|
||||
*/
|
||||
$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'
|
||||
];
|
||||
|
||||
// Retrieve root groups and all descendants
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
authenticationService.getAvailableDataSources(),
|
||||
ConnectionGroup.ROOT_IDENTIFIER
|
||||
)
|
||||
.then(function rootGroupsRetrieved(rootConnectionGroups) {
|
||||
$scope.rootConnectionGroups = rootConnectionGroups;
|
||||
}, requestService.WARN);
|
||||
|
||||
/**
|
||||
* Map of all available sharing profiles for the current connection by
|
||||
* their identifiers. If this information is not yet available, or no such
|
||||
@@ -440,6 +492,12 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
|
||||
});
|
||||
|
||||
// Update last used timestamp when the active client changes
|
||||
$scope.$watch('client', function clientChanged(client) {
|
||||
if (client)
|
||||
client.lastUsed = new Date().getTime();
|
||||
});
|
||||
|
||||
// Update page icon when thumbnail changes
|
||||
$scope.$watch('client.thumbnail.canvas', function thumbnailChanged(canvas) {
|
||||
iconService.setIcons(canvas);
|
||||
|
@@ -282,6 +282,9 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Size of newly-attached client may be different
|
||||
$scope.mainElementResized();
|
||||
|
||||
});
|
||||
|
||||
// Update actual view scrollLeft when scroll properties change
|
||||
|
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A toolbar/panel which displays a list of active Guacamole connections. The
|
||||
* panel is fixed to the bottom-right corner of its container and can be
|
||||
* manually hidden/exposed by the user.
|
||||
*/
|
||||
angular.module('client').directive('guacClientPanel', ['$injector', function guacClientPanel($injector) {
|
||||
|
||||
// Required services
|
||||
var guacClientManager = $injector.get('guacClientManager');
|
||||
var sessionStorageFactory = $injector.get('sessionStorageFactory');
|
||||
|
||||
// Required types
|
||||
var ManagedClientState = $injector.get('ManagedClientState');
|
||||
|
||||
/**
|
||||
* Getter/setter for the boolean flag controlling whether the client panel
|
||||
* is currently hidden. This flag is maintained in session-local storage to
|
||||
* allow the state of the panel to persist despite navigation within the
|
||||
* same tab. When hidden, the panel will be collapsed against the right
|
||||
* side of the container. By default, the panel is visible.
|
||||
*
|
||||
* @type Function
|
||||
*/
|
||||
var panelHidden = sessionStorageFactory.create(false);
|
||||
|
||||
return {
|
||||
// Element only
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The ManagedClient instances associated with the active
|
||||
* connections to be displayed within this panel.
|
||||
*
|
||||
* @type ManagedClient[]|Object.<String, ManagedClient>
|
||||
*/
|
||||
clients : '='
|
||||
|
||||
},
|
||||
templateUrl: 'app/client/templates/guacClientPanel.html',
|
||||
controller: ['$scope', '$element', function guacClientPanelController($scope, $element) {
|
||||
|
||||
/**
|
||||
* The DOM element containing the scrollable portion of the client
|
||||
* panel.
|
||||
*
|
||||
* @type Element
|
||||
*/
|
||||
var scrollableArea = $element.find('.client-panel-connection-list')[0];
|
||||
|
||||
/**
|
||||
* On-scope reference to session-local storage of the flag
|
||||
* controlling whether then panel is hidden.
|
||||
*/
|
||||
$scope.panelHidden = panelHidden;
|
||||
|
||||
/**
|
||||
* Returns whether this panel currently has any clients associated
|
||||
* with it.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if at least one client is associated with this panel,
|
||||
* false otherwise.
|
||||
*/
|
||||
$scope.hasClients = function hasClients() {
|
||||
return !!_.find($scope.clients, $scope.isManaged);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the status of the given client has changed in a
|
||||
* way that requires the user's attention. This may be due to an
|
||||
* error, or due to a server-initiated disconnect.
|
||||
*
|
||||
* @param {ManagedClient} client
|
||||
* The client to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given client requires the user's attention,
|
||||
* false otherwise.
|
||||
*/
|
||||
$scope.hasStatusUpdate = function hasStatusUpdate(client) {
|
||||
|
||||
// Test whether the client has encountered an error
|
||||
switch (client.clientState.connectionState) {
|
||||
case ManagedClientState.ConnectionState.CONNECTION_ERROR:
|
||||
case ManagedClientState.ConnectionState.TUNNEL_ERROR:
|
||||
case ManagedClientState.ConnectionState.DISCONNECTED:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the given client is currently being managed by
|
||||
* the guacClientManager service.
|
||||
*
|
||||
* @param {ManagedClient} client
|
||||
* The client to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given client is being managed by the
|
||||
* guacClientManager service, false otherwise.
|
||||
*/
|
||||
$scope.isManaged = function isManaged(client) {
|
||||
return !!guacClientManager.getManagedClients()[client.id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates an orderly disconnect of the given client. The client
|
||||
* is removed from management such that attempting to connect to
|
||||
* the same connection will result in a new connection being
|
||||
* established, rather than displaying a notification that the
|
||||
* connection has ended.
|
||||
*
|
||||
* @param {type} client
|
||||
* @returns {undefined}
|
||||
*/
|
||||
$scope.disconnect = function disconnect(client) {
|
||||
client.client.disconnect();
|
||||
guacClientManager.removeManagedClient(client.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles whether the client panel is currently hidden.
|
||||
*/
|
||||
$scope.togglePanel = function togglePanel() {
|
||||
panelHidden(!panelHidden());
|
||||
};
|
||||
|
||||
// Override vertical scrolling, scrolling horizontally instead
|
||||
scrollableArea.addEventListener('wheel', function reorientVerticalScroll(e) {
|
||||
|
||||
var deltaMultiplier = {
|
||||
/* DOM_DELTA_PIXEL */ 0x00: 1,
|
||||
/* DOM_DELTA_LINE */ 0x01: 15,
|
||||
/* DOM_DELTA_PAGE */ 0x02: scrollableArea.offsetWidth
|
||||
};
|
||||
|
||||
if (e.deltaY) {
|
||||
this.scrollLeft += e.deltaY * (deltaMultiplier[e.deltaMode] || deltaMultiplier(0x01));
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}]
|
||||
};
|
||||
}]);
|
@@ -65,6 +65,29 @@
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#guac-menu .header h2 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#guac-menu .header h2 .menu-dropdown {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#guac-menu .header h2 .menu-contents {
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
#guac-menu .header .filter input {
|
||||
border-bottom: 1px solid rgba(0,0,0,0.125);
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
#guac-menu .header .filter {
|
||||
margin-bottom: 0.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#guac-menu #mouse-settings .choice {
|
||||
text-align: center;
|
||||
}
|
||||
|
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
|
||||
.keyboard-container {
|
||||
|
||||
display: none;
|
||||
text-align: center;
|
||||
|
||||
width: 100%;
|
||||
@@ -29,4 +31,9 @@
|
||||
opacity: 0.85;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
}
|
||||
|
||||
.keyboard-container.open {
|
||||
display: block;
|
||||
}
|
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#other-connections .client-panel {
|
||||
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
transition: max-width 0.125s, width 0.125s;
|
||||
|
||||
/* Render above modal status */
|
||||
z-index: 20;
|
||||
|
||||
}
|
||||
|
||||
#other-connections .client-panel.has-clients {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#other-connections .client-panel.hidden {
|
||||
max-width: 16px;
|
||||
}
|
||||
|
||||
#other-connections .client-panel-handle {
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 16px;
|
||||
z-index: 1;
|
||||
|
||||
background-color: white;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-image: url(images/arrows/right.png);
|
||||
opacity: 0.5;
|
||||
|
||||
}
|
||||
|
||||
#other-connections .client-panel-handle:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#other-connections .client-panel.hidden .client-panel-handle {
|
||||
background-image: url(images/arrows/left.png);
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection-list {
|
||||
|
||||
text-align: right;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 16px;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection {
|
||||
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
margin: 0.5em;
|
||||
border: 1px solid white;
|
||||
background: black;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
|
||||
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.25s;
|
||||
|
||||
max-height: 128px;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection .thumbnail-main img {
|
||||
max-width: none;
|
||||
max-height: 128px;
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection a[href]::before {
|
||||
|
||||
display: block;
|
||||
content: ' ';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
background: url('images/warning-white.png');
|
||||
background-size: 48px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: black;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s;
|
||||
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection.needs-attention a[href]::before {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#other-connections button.close-other-connection {
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
|
||||
opacity: 0.5;
|
||||
line-height: 1;
|
||||
|
||||
}
|
||||
|
||||
#other-connections button.close-other-connection:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#other-connections button.close-other-connection img {
|
||||
background: #A43;
|
||||
border-radius: 18px;
|
||||
max-width: 18px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#other-connections button.close-other-connection:hover img {
|
||||
background: #C54;
|
||||
}
|
||||
|
||||
#other-connections .client-panel.hidden .client-panel-connection-list {
|
||||
/* Hide scrollbar when panel is hidden (will be visible through panel
|
||||
* show/hide button otherwise) */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#other-connections .client-panel.hidden .client-panel-connection {
|
||||
/* Hide thumbnails when panel is hidden (will be visible through panel
|
||||
* show/hide button otherwise) */
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection .name {
|
||||
|
||||
position: absolute;
|
||||
padding: 0.25em 0.5em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
|
||||
text-align: left;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
}
|
||||
|
||||
#other-connections .client-panel-connection:hover {
|
||||
opacity: 1;
|
||||
}
|
26
guacamole/src/main/webapp/app/client/styles/text-input.css
Normal file
26
guacamole/src/main/webapp/app/client/styles/text-input.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
.text-input-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.text-input-container.open {
|
||||
display: block;
|
||||
}
|
@@ -8,21 +8,26 @@
|
||||
<!-- Central portion of view -->
|
||||
<div class="client-body" guac-touch-drag="clientDrag" guac-touch-pinch="clientPinch">
|
||||
|
||||
<!-- Client -->
|
||||
<!-- Client for current connection -->
|
||||
<guac-client client="client"></guac-client>
|
||||
|
||||
<!-- All other active connections -->
|
||||
<div id="other-connections">
|
||||
<guac-client-panel clients="otherClients"></guac-client-panel>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom portion of view -->
|
||||
<div class="client-bottom">
|
||||
|
||||
<!-- Text input -->
|
||||
<div class="text-input-container" ng-show="showTextInput">
|
||||
<div class="text-input-container" ng-class="{ open : showTextInput }">
|
||||
<guac-text-input needs-focus="showTextInput"></guac-text-input>
|
||||
</div>
|
||||
|
||||
<!-- On-screen keyboard -->
|
||||
<div class="keyboard-container" ng-show="showOSK">
|
||||
<div class="keyboard-container" ng-class="{ open : showOSK }">
|
||||
<guac-osk layout="'CLIENT.URL_OSK_LAYOUT' | translate"></guac-osk>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +52,25 @@
|
||||
|
||||
<!-- Stationary header -->
|
||||
<div class="header">
|
||||
<h2>{{client.name}}</h2>
|
||||
<h2 ng-hide="rootConnectionGroups">{{client.name}}</h2>
|
||||
<h2 ng-show="rootConnectionGroups">
|
||||
<guac-menu menu-title="client.name">
|
||||
<div class="all-connections">
|
||||
<guac-group-list-filter connection-groups="rootConnectionGroups"
|
||||
filtered-connection-groups="filteredRootConnectionGroups"
|
||||
placeholder="'CLIENT.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||
connection-properties="filteredConnectionProperties"
|
||||
connection-group-properties="filteredConnectionGroupProperties"></guac-group-list-filter>
|
||||
<guac-group-list
|
||||
connection-groups="filteredRootConnectionGroups"
|
||||
templates="{
|
||||
'connection' : 'app/client/templates/connection.html',
|
||||
'connection-group' : 'app/client/templates/connectionGroup.html'
|
||||
}"
|
||||
page-size="10"></guac-group-list>
|
||||
</div>
|
||||
</guac-menu>
|
||||
</h2>
|
||||
<div class="share-menu" ng-show="canShareConnection()">
|
||||
<guac-menu menu-title="'CLIENT.ACTION_SHARE' | translate">
|
||||
<ul ng-repeat="sharingProfile in sharingProfiles">
|
||||
|
@@ -0,0 +1,4 @@
|
||||
<a class="connection" ng-href="#/client/{{ item.getClientIdentifier() }}">
|
||||
<div class="icon type" ng-class="item.protocol"></div>
|
||||
<span class="name">{{item.name}}</span>
|
||||
</a>
|
@@ -0,0 +1,4 @@
|
||||
<span class="connection-group name">
|
||||
<a ng-show="item.balancing" ng-href="#/client/{{ item.getClientIdentifier() }}">{{item.name}}</a>
|
||||
<span ng-show="!item.balancing">{{item.name}}</span>
|
||||
</span>
|
@@ -0,0 +1,32 @@
|
||||
<div class="client-panel"
|
||||
ng-class="{ 'has-clients': hasClients(), 'hidden' : panelHidden() }">
|
||||
|
||||
<!-- Toggle panel visibility -->
|
||||
<div class="client-panel-handle" ng-click="togglePanel()"></div>
|
||||
|
||||
<!-- List of connection thumbnails -->
|
||||
<ul class="client-panel-connection-list">
|
||||
<li ng-repeat="client in clients | toArray | orderBy: [ '-value.lastUsed', 'value.title' ]"
|
||||
ng-class="{ 'needs-attention' : hasStatusUpdate(client.value) }"
|
||||
ng-show="isManaged(client.value)"
|
||||
class="client-panel-connection">
|
||||
|
||||
<!-- Close connection -->
|
||||
<button class="close-other-connection" ng-click="disconnect(client.value)">
|
||||
<img ng-attr-alt="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}"
|
||||
ng-attr-title="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}"
|
||||
src="images/x.png">
|
||||
</button>
|
||||
|
||||
<!-- Thumbnail -->
|
||||
<a href="#/client/{{client.value.id}}">
|
||||
<div class="thumbnail">
|
||||
<guac-thumbnail client="client.value"></guac-thumbnail>
|
||||
</div>
|
||||
<div class="name">{{ client.value.title }}</div>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
@@ -79,6 +79,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
*/
|
||||
this.id = template.id;
|
||||
|
||||
/**
|
||||
* The time that the connection was last brought to the foreground of
|
||||
* the current tab, as the number of milliseconds elapsed since
|
||||
* midnight of January 1, 1970 UTC. If the connection has not yet been
|
||||
* viewed, this will be 0.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.lastUsed = template.lastUsed || 0;
|
||||
|
||||
/**
|
||||
* The actual underlying Guacamole client.
|
||||
*
|
||||
|
@@ -21,4 +21,8 @@
|
||||
* Module for displaying the contents of a connection group, allowing the user
|
||||
* to select individual connections or groups.
|
||||
*/
|
||||
angular.module('groupList', ['list', 'rest']);
|
||||
angular.module('groupList', [
|
||||
'navigation',
|
||||
'list',
|
||||
'rest'
|
||||
]);
|
||||
|
@@ -20,7 +20,11 @@
|
||||
/**
|
||||
* Provides the GroupListItem class definition.
|
||||
*/
|
||||
angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', function defineGroupListItem(ConnectionGroup) {
|
||||
angular.module('groupList').factory('GroupListItem', ['$injector', function defineGroupListItem($injector) {
|
||||
|
||||
// Required types
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
|
||||
/**
|
||||
* Creates a new GroupListItem, initializing the properties of that
|
||||
@@ -109,14 +113,50 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
|
||||
/**
|
||||
* Returns the number of currently active users for this connection,
|
||||
* connection group, or sharing profile, if known.
|
||||
* connection group, or sharing profile, if known. If unknown, null may
|
||||
* be returned.
|
||||
*
|
||||
* @type Number
|
||||
* @returns {Number}
|
||||
* The number of currently active users for this connection,
|
||||
* connection group, or sharing profile.
|
||||
*/
|
||||
this.getActiveConnections = template.getActiveConnections || (function getActiveConnections() {
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the unique string identifier that must be used when
|
||||
* connecting to a connection or connection group represented by this
|
||||
* GroupListItem.
|
||||
*
|
||||
* @returns {String}
|
||||
* The client identifier associated with the connection or
|
||||
* connection group represented by this GroupListItem, or null if
|
||||
* this GroupListItem cannot have an associated client identifier.
|
||||
*/
|
||||
this.getClientIdentifier = template.getClientIdentifier || function getClientIdentifier() {
|
||||
|
||||
// If the item is a connection, generate a connection identifier
|
||||
if (this.type === GroupListItem.Type.CONNECTION)
|
||||
return ClientIdentifier.toString({
|
||||
dataSource : this.dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
id : this.identifier
|
||||
});
|
||||
|
||||
// If the item is a connection group, generate a connection group identifier
|
||||
if (this.type === GroupListItem.Type.CONNECTION_GROUP)
|
||||
return ClientIdentifier.toString({
|
||||
dataSource : this.dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION_GROUP,
|
||||
id : this.identifier
|
||||
});
|
||||
|
||||
// Otherwise, no such identifier can exist
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The connection, connection group, or sharing profile whose data is
|
||||
* exposed within this GroupListItem. If the type of this GroupListItem
|
||||
|
@@ -25,7 +25,6 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
|
||||
|
||||
// Get required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
var GroupListItem = $injector.get('GroupListItem');
|
||||
|
||||
// Get required services
|
||||
@@ -74,51 +73,6 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Object passed to the guacGroupList directive, providing context-specific
|
||||
* functions or data.
|
||||
*/
|
||||
$scope.context = {
|
||||
|
||||
/**
|
||||
* Returns the unique string identifier which must be used when
|
||||
* connecting to a connection or connection group represented by the
|
||||
* given GroupListItem.
|
||||
*
|
||||
* @param {GroupListItem} item
|
||||
* The GroupListItem to determine the client identifier of.
|
||||
*
|
||||
* @returns {String}
|
||||
* The client identifier associated with the connection or
|
||||
* connection group represented by the given GroupListItem, or null
|
||||
* if the GroupListItem cannot have an associated client
|
||||
* identifier.
|
||||
*/
|
||||
getClientIdentifier : function getClientIdentifier(item) {
|
||||
|
||||
// If the item is a connection, generate a connection identifier
|
||||
if (item.type === GroupListItem.Type.CONNECTION)
|
||||
return ClientIdentifier.toString({
|
||||
dataSource : item.dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
id : item.identifier
|
||||
});
|
||||
|
||||
// If the item is a connection group, generate a connection group identifier
|
||||
if (item.type === GroupListItem.Type.CONNECTION_GROUP)
|
||||
return ClientIdentifier.toString({
|
||||
dataSource : item.dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION_GROUP,
|
||||
id : item.identifier
|
||||
});
|
||||
|
||||
// Otherwise, no such identifier can exist
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Retrieve root groups and all descendants
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<a class="home-connection"
|
||||
ng-href="#/client/{{context.getClientIdentifier(item)}}"
|
||||
ng-href="#/client/{{ item.getClientIdentifier() }}"
|
||||
ng-class="{active: item.getActiveConnections()}">
|
||||
|
||||
<!-- Connection icon -->
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<span class="home-connection-group name">
|
||||
<a ng-show="item.balancing" ng-href="#/client/{{context.getClientIdentifier(item)}}">{{item.name}}</a>
|
||||
<a ng-show="item.balancing" ng-href="#/client/{{ getClientIdentifier() }}">{{item.name}}</a>
|
||||
<span ng-show="!item.balancing">{{item.name}}</span>
|
||||
</span>
|
||||
|
@@ -23,7 +23,6 @@
|
||||
</div>
|
||||
<div class="all-connections">
|
||||
<guac-group-list
|
||||
context="context"
|
||||
connection-groups="filteredRootConnectionGroups"
|
||||
templates="{
|
||||
'connection' : 'app/home/templates/connection.html',
|
||||
|
@@ -52,6 +52,14 @@ angular.module('navigation').directive('guacMenu', [function guacMenu() {
|
||||
*/
|
||||
var element = $element[0];
|
||||
|
||||
/**
|
||||
* The element containing the menu contents that display when the
|
||||
* menu is open.
|
||||
*
|
||||
* @type Element
|
||||
*/
|
||||
var contents = $element.find('.menu-contents')[0];
|
||||
|
||||
/**
|
||||
* The main document object.
|
||||
*
|
||||
@@ -85,6 +93,11 @@ angular.module('navigation').directive('guacMenu', [function guacMenu() {
|
||||
e.stopPropagation();
|
||||
}, false);
|
||||
|
||||
// Prevent click within menu contents from toggling menu visibility
|
||||
contents.addEventListener('click', function clickInsideMenuContents(e) {
|
||||
e.stopPropagation();
|
||||
}, false);
|
||||
|
||||
}] // end controller
|
||||
|
||||
};
|
||||
|
BIN
guacamole/src/main/webapp/images/arrows/left.png
Normal file
BIN
guacamole/src/main/webapp/images/arrows/left.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 246 B |
BIN
guacamole/src/main/webapp/images/warning-white.png
Normal file
BIN
guacamole/src/main/webapp/images/warning-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
@@ -91,6 +91,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Die maximale Anzahl gleichzeiter Dateiübertragungen erreicht. Bitte warte bis laufende Dateiübertagungen abgeschlossen sind und versuche es erneut.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "Die Verbindung wurde aufgrund eines interen Fehlers im Guacamole Server beendet. Sollte dieses Problem weiterhin bestehen informieren Sie den Systemadministrator oder überprüfen Sie die Protokolle.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Kopierter oder ausgeschnittener Text aus Guacamole wird hier angezeigt. Änderungen am Text werden direkt auf die entfernte Zwischenablage angewandt.",
|
||||
"HELP_INPUT_METHOD_NONE" : "Keine Eingabemethode in Verwendung. Tastatureingaben werden von der Hardwaretastatur akzeptiert.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Bildschirmeingaben und die eingebettete Guacamole Bildschrimtastatur werden akzeptiert. Die Bildschirmtastatur gestattet Tastenkombinationen die ansonsten unmöglich sind (z.B.: Strg-Alt-Del).",
|
||||
|
@@ -106,6 +106,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Too many files are currently being transferred. Please wait for existing transfers to complete, and then try again.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "An internal error has occurred within the Guacamole server, and the connection has been terminated. If the problem persists, please notify your system administrator, or check your system logs.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Text copied/cut within Guacamole will appear here. Changes to the text below will affect the remote clipboard.",
|
||||
"HELP_INPUT_METHOD_NONE" : "No input method is used. Keyboard input is accepted from a connected, physical keyboard.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Display and accept input from the built-in Guacamole on-screen keyboard. The on-screen keyboard allows typing of key combinations that may otherwise be impossible (such as Ctrl-Alt-Del).",
|
||||
|
@@ -101,6 +101,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Se estan transfiriendo muchos ficheros actualmente. Por favor espere a que finalicen las transferencias de fichero existentes e intente de nuevo.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "Ha ocurrido un error interno en el servidor Guacamole y la conexión ha finalizado. Si el problema persiste, por favor notifíquelo al administrador o compruebe los registros del sistema.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Aquí aparecerá el texto copiado/cortado en Guacamole. Los cambios en el texto de abajo afectaran al portapapeles remoto.",
|
||||
"HELP_INPUT_METHOD_NONE" : "No se está usando un método de entrada. La entrada de teclado se acepta desde un teclado físico conectado.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Muestra y acepta entrada desde el teclado en pantalla incorporado de Guacamole. El teclado en pantalla permite escribir combinaciones que serían imposible de otro modo (como Ctrl-Alt-Sup).",
|
||||
|
@@ -91,6 +91,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Trop de fichiers sont actuellement transférés. Merci d'attendre que les transferts en cours soient terminés et de réessayer plus tard.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "Une erreur interne est apparue dans le serveur Guacamole et la connexion a été fermée. Si le problème persiste, merci de notifier l'administrateur ou de regarder les journaux système.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Texte copié/coupé dans Guacamole apparaîtra ici. Changer le texte ci dessous affectera le presse-papiers distant.",
|
||||
"HELP_INPUT_METHOD_NONE" : "Aucune méthode de saisie utilisée. Clavier accepté depuis un clavier physique connecté.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Affiche et utilise la saisie du clavier virtuel intégré dans Guacamole. Le clavier virtuel permet d'utiliser des combinaisons de touches autrement impossibles (comme Ctrl-Alt-Supp).",
|
||||
|
@@ -87,6 +87,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Ci sono troppi file in coda per il trasferimento. Attendi che siano completati i trasferimenti in atto e riprova.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "Si è verificato un errore sul server e la connessione è stata chiusa. Riprova o contatta il tuo amministratore di sistema.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Il testo copiato/tagliato appare qui. I cambiamenti effettuati al testo qui sotto saranno riportati negli appunti remoti.",
|
||||
"HELP_INPUT_METHOD_NONE" : "Non c'è nessun metodo di immissione. L'input da tastiera è accettato da una tastiera fisica connessa.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Mostra e accetta input dalla tastiera su schermo. La tastiera su schermo ti permette di scrivere combinazioni di tasti altrimenti mipossibli (ad esempio Ctrl-Alt-Canc).",
|
||||
|
@@ -91,6 +91,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Er worden momenteel te veel bestanden overdragen. Gelieve te wachten tot de bestaande bestandsoverdracht is voltooid, en probeer het opnieuw.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "Er is een interne fout opgetreden op de Guacamole server, en de verbinding is beëindigd. Als het probleem aanhoudt, neem dan contact op met uw systeembeheerder of kijk in uw systeem logs.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Tekst gekopieerd / geknipt binnen Guacamole zal hier verschijnen. Wijzigingen in onderstaande tekst zal externe klembord beïnvloeden.",
|
||||
"HELP_INPUT_METHOD_NONE" : "Geen invoer methode gebruiken. Toetsenbord invoer wordt geaccepteerd van een aangesloten, fysiek toetsenbord.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Weergave en accepteren van invoer via het ingebouwde Guacamole on-screen toetsenbord. Dit toetsenbord op het scherm maakt toetscombinaties mogelijk die anders onmogelijk zijn (zoals Ctrl-Alt-Del).",
|
||||
|
@@ -93,6 +93,8 @@
|
||||
"ERROR_UPLOAD_31D" : "For mange filer blir overført. Vent til aktive overføringer fullfører og prøv igjen.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "En intern feil har oppstått i Guacamole og forbindelsen er terminert. Kontakt systemadministrator dersom problemet fortsetter eller sjekk systemloggene dine.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Tekst som er kopiert eller klippet i Guacamole vises her. Endringer i teksten under vil påvirke den eksterne utklippstavlen.",
|
||||
"HELP_INPUT_METHOD_NONE" : "Ingen innenhet er brukt. Tastetrykk fra et fysisk tilkoblet tastatur blir akseptert.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Vis og aksepter tastetrykk fra det innebygde skjermtastaturet i Guacamole. Skjermtastaturet tillater tasting av tastekombinasjoner som ellers kan være umulig (f.eks. Ctrl-Alt-Del).",
|
||||
|
@@ -88,6 +88,8 @@
|
||||
"ERROR_UPLOAD_31D" : "Слишком много файлов передается в настоящий момент. Подождите завершения текущих передач и повторите попытку снова.",
|
||||
"ERROR_UPLOAD_DEFAULT" : "Соединение было прервано из-за внутренней ошибки сервера. Пожалуйста, попробуйте повторить попытку позднее или обратитесь к администратору.",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "Текст, скопированный или вырезанный внутри сеанса, появится в этом поле. Изменение текста также отразиться на буфере обмена удаленного рабочего стола.",
|
||||
"HELP_INPUT_METHOD_NONE" : "Не выбран метод ввода. Ввод разрешен для физической клавиатуры.",
|
||||
"HELP_INPUT_METHOD_OSK" : "Отображать и принимать ввод со встроенной экранной клавиатуры. Экранная клавиатура позволяет вводить любые комбинации, недоступные в других режимах (например Alt-Ctrl-Del).",
|
||||
|
@@ -101,6 +101,8 @@
|
||||
"ERROR_UPLOAD_31D" : "正在同时传输太多文件。请等待当前的传输任务完成后,再重试。",
|
||||
"ERROR_UPLOAD_DEFAULT" : "本连接因为Guacamole服务器出现了内部错误而被终止。如果问题持续,请通知您的系统管理员,或检查您的系统日志。",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HELP_CLIPBOARD" : "复制/剪切的文本将出现在这里。对下面文本内容所作的修改将会影响远程电脑上的剪贴板。",
|
||||
"HELP_INPUT_METHOD_NONE" : "没有选择任何输入法。将从连接的物理键盘接受键盘输入。",
|
||||
"HELP_INPUT_METHOD_OSK" : "显示并从内建的Guacamole屏幕键盘接受输入。屏幕键盘可以输入平常无法输入的按键组合(如Ctrl-Alt-Del等)。",
|
||||
|
Reference in New Issue
Block a user