GUACAMOLE-723: Add interface for switching between multiple active connections.

This commit is contained in:
Virtually Nick
2019-04-06 14:09:49 -04:00
committed by GitHub
30 changed files with 661 additions and 67 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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();
}
});
}]
};
}]);

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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;
}

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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.
*

View File

@@ -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'
]);

View File

@@ -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

View File

@@ -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,

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -23,7 +23,6 @@
</div>
<div class="all-connections">
<guac-group-list
context="context"
connection-groups="filteredRootConnectionGroups"
templates="{
'connection' : 'app/home/templates/connection.html',

View File

@@ -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
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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).",

View File

@@ -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等。",