Updated to Guacamole 0.9.1, now closes the session correctly

This commit is contained in:
Adolfo Gómez 2014-07-17 10:23:22 +00:00
parent c4958e808e
commit 6a841a86df
43 changed files with 4408 additions and 1134 deletions

View File

@ -7,7 +7,7 @@
<groupId>org.openuds.server</groupId>
<artifactId>transport</artifactId>
<packaging>war</packaging>
<version>1.2.1</version>
<version>1.5.0</version>
<name>Guacamole Transport</name>
<url>http://openuds.org/</url>
@ -70,7 +70,7 @@
<dependency>
<groupId>org.glyptodon.guacamole</groupId>
<artifactId>guacamole-common-js</artifactId>
<version>0.9.0</version>
<version>0.9.1</version>
<type>zip</type>
<scope>runtime</scope>
</dependency>

View File

@ -78,7 +78,7 @@ public class TunnelServlet
throw new GuacamoleException("Can't access required user credentials");
}
System.out.println("Got parameters from remote server");
System.out.println("Got parameters from remote server: " + data + ", " + width + "x" + height);
GuacamoleClientInformation info = new GuacamoleClientInformation();
info.setOptimalScreenWidth(Integer.parseInt(width));

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,44 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE HTML>
<!DOCTYPE html>
<!--
Guacamole - Clientless Remote Desktop
Copyright (C) 2010 Michael Jumper
Copyright (C) 2013 Glyptodon LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Adaptation for UDS use (c) 2013 Virtual Cable S.L.U, also distributed under
GNU Affero General Public License
All source code used to build this package can be obtained from http://openuds.org
Guacamole source code can be obtained from http://guac-dev.org/
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
<link rel="stylesheet" type="text/css" href="styles/ui.css"/>
<link rel="stylesheet" type="text/css" href="styles/client.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/>
<link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>UDS</title>
<title>Guacamole ${project.version}</title>
</head>
<body>
<div id="main">
<!-- Display -->
<div class="displayOuter">
<div class="displayMiddle">
@ -47,11 +47,92 @@
</div>
</div>
</div>
<!-- Text input target -->
<div id="text-input"><div id="text-input-field"><div id="sent-history"></div><textarea rows="1" id="target"></textarea></div><div id="text-input-buttons"><button class="key" data-keysym="0xFFE3" data-sticky="true">Ctrl</button><button class="key" data-keysym="0xFFE9" data-sticky="true">Alt</button><button class="key" data-keysym="0xFF1B">Esc</button><button class="key" data-keysym="0xFF09">Tab</button></div></div>
<!-- Dimensional clone of viewport -->
<div id="viewportClone"/>
<!-- Notification area -->
<div id="notificationArea"/>
<!-- Menu -->
<div id="menu">
<h2 id="menu-title">Guacamole ${project.version}</h2>
<h3>Clipboard</h3>
<div class="content" id="clipboard-settings">
<p class="description">Text copied/cut within Guacamole will appear here. Changes to the text below will affect the remote clipboard.</p>
<textarea rows="10" cols="40" id="clipboard"></textarea>
</div>
<h3>Input method</h3>
<div class="content" id="keyboard-settings">
<!-- No IME -->
<div class="choice">
<label><input name="input-method" type="radio" value="ime-none" checked="checked" id="ime-none"/> None</label>
<p class="caption"><label for="ime-none">No input method is used. Keyboard input is accepted from
a connected, physical keyboard.</label></p>
</div>
<!-- Text input -->
<div class="choice">
<div class="figure"><label for="ime-text"><img src="images/settings/tablet-keys.png" alt=""/></label></div>
<label><input name="input-method" type="radio" value="ime-text" id="ime-text"/> Text input</label>
<p class="caption"><label for="ime-text">
Allow typing of text, and emulate keyboard events based on the
typed text. This is necessary for devices such as mobile phones that lack a physical keyboard.</label></p>
</div>
<!-- Guac OSK -->
<div class="choice">
<label><input name="input-method" type="radio" value="ime-osk" id="ime-osk"/> On-screen keyboard</label>
<p class="caption"><label for="ime-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).</label></p>
</div>
</div>
<h3>Mouse emulation mode</h3>
<div class="content" id="mouse-settings">
<p class="description">Determines how the remote mouse behaves with respect to touches.</p>
<!-- Touchscreen -->
<div class="choice">
<input name="mouse-mode" type="radio" value="absolute" checked="checked" id="absolute"/>
<div class="figure">
<label for="absolute"><img src="images/settings/touchscreen.png" alt=""/></label>
<p class="caption"><label for="absolute">Tap to click. The click occurs at the location of the touch.</label></p>
</div>
</div>
<!-- Touchpad -->
<div class="choice">
<input name="mouse-mode" type="radio" value="relative" id="relative"/>
<div class="figure">
<label for="relative"><img src="images/settings/touchpad.png" alt=""/></label>
<p class="caption"><label for="relative">Drag to move the mouse pointer and tap to click. The click occurs at the location of the pointer.</label></p>
</div>
</div>
</div>
<h3>Display</h3>
<div class="content">
<div id="zoom-settings">
<div id="zoom-out"><img src="images/settings/zoom-out.png" alt="-"/></div>
<div id="zoom-state">100%</div>
<div id="zoom-in"><img src="images/settings/zoom-in.png" alt="+"/></div>
</div>
<div><label><input type="checkbox" id="auto-fit" checked="checked"/> Automatically fit to browser window</label></div>
</div>
</div>
<!-- Images which should be preloaded -->
<div id="preload">
<img src="images/action-icons/guac-close.png"/>
@ -61,98 +142,46 @@
<script type="text/javascript" src="scripts/lib/blob/blob.js"></script>
<script type="text/javascript" src="scripts/lib/filesaver/filesaver.js"></script>
<!-- Client core scripts -->
<!-- guacamole-common-js -->
<script type="text/javascript" src="guacamole-common-js/all.min.js"></script>
<!-- guacamole-default-webapp scripts -->
<script type="text/javascript" src="scripts/session.js"></script>
<script type="text/javascript" src="scripts/history.js"></script>
<script type="text/javascript" src="scripts/service.js"></script>
<script type="text/javascript" src="scripts/guac-ui.js"></script>
<script type="text/javascript" src="scripts/client-ui.js"></script>
<!-- Init -->
<script type="text/javascript"> /* <![CDATA[ */
function getQueryParams(qs) {
qs = qs.split("+").join(" ");
var params = {}, tokens,
re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])]
= decodeURIComponent(tokens[2]);
}
return params;
}
window.query = getQueryParams(document.location.search);
// Sanity check Guacamole JavaScript API version
// if (Guacamole.API_VERSION !== "0.9.1")
// GuacUI.Client.showStatus("Clear Your Cache", "An older version of Guacamole has been cached by your browser. Please clear your browser's cache and try again.");
// Start connect after control returns from onload (allow browser
// to consider the page loaded).
//else
window.onload = function() {
window.setTimeout(function() {
// Get parameter information, so we can retrieve connecton data
var params = window.location.search.substr(1);
var data = "";
var exitUrl = "";
var ioa = params.indexOf('&');
// URL is in the format ?XXXXXXXX&URL, where XXX is the id of the credential stored, and URL is the output URL
if( ioa != -1 ) {
data = params.substr(0,ioa);
exiturl = params.substr(ioa+1);
} else {
data = params;
}
var tunnel;
GuacUI.sessionState.setProperty("auto-fit", true);
GuacUI.sessionState.setProperty("exitUrl", exiturl);
GuacUI.sessionState.setProperty("disable-sound", false);
// If WebSocket available, try to use it.
/*if (window.WebSocket)
tunnel = new Guacamole.ChainedTunnel(
new Guacamole.WebSocketTunnel("websocket-tunnel"),
new Guacamole.HTTPTunnel("tunnel")
);
// If no WebSocket, then use HTTP.
else*/
tunnel = new Guacamole.HTTPTunnel("tunnel")
// Instantiate client
var guac = new Guacamole.Client(tunnel);
// Add client to UI
guac.getDisplay().className = "software-cursor";
GuacUI.Client.display.appendChild(guac.getDisplay());
// Tie UI to client
GuacUI.Client.attach(guac);
try {
// Calculate optimal width/height for display
var optimal_width = window.innerWidth;
var optimal_height = window.innerHeight;
// Scale width/height to be at least 600x600
if (optimal_width < 600 || optimal_height < 600) {
var scale = Math.max(600 / optimal_width, 600 / optimal_height);
optimal_width = Math.floor(optimal_width * scale);
optimal_height = Math.floor(optimal_height * scale);
}
// Get entire query string, and pass to connect().
// Normally, only the "id" parameter is required, but
// all parameters should be preserved and passed on for
// the sake of authentication.
var connect_string = "data=" + data + "&width=" + optimal_width + "&height=" + optimal_height;
// Add audio mimetypes to connect_string
GuacUI.Audio.supported.forEach(function(mimetype) {
connect_string += "&audio=" + encodeURIComponent(mimetype);
});
// Add video mimetypes to connect_string
GuacUI.Video.supported.forEach(function(mimetype) {
connect_string += "&video=" + encodeURIComponent(mimetype);
});
guac.connect(connect_string);
}
catch (e) {
GuacUI.Client.showError(e.message);
}
}, 0);
window.setTimeout(GuacUI.Client.connect, 10);
};
/* ]]> */ </script>
</body>

View File

@ -1,19 +1,23 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
* Copyright (C) 2013 Glyptodon LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
@ -76,7 +80,6 @@ GuacAdmin.Field = function() {
};
/**
* Simple HTML input field.
*
@ -108,7 +111,6 @@ GuacAdmin.Field._HTML_INPUT = function(type) {
GuacAdmin.Field._HTML_INPUT.prototype = new GuacAdmin.Field();
/**
* A basic text field.
*
@ -120,6 +122,34 @@ GuacAdmin.Field.TEXT = function() {
GuacAdmin.Field.TEXT.prototype = new GuacAdmin.Field._HTML_INPUT();
/**
* A basic multiline text field.
*
* @augments GuacAdmin.Field
*/
GuacAdmin.Field.MULTILINE = function() {
// Call parent constructor
GuacAdmin.Field.apply(this);
// Create backing element
var element = GuacUI.createElement("textarea");
this.getValue = function() {
return element.value;
};
this.getElement = function() {
return element;
};
this.setValue = function(value) {
element.value = value;
};
};
GuacAdmin.Field.MULTILINE.prototype = new GuacAdmin.Field();
/**
* A basic password field.
@ -132,7 +162,6 @@ GuacAdmin.Field.PASSWORD = function() {
GuacAdmin.Field.PASSWORD.prototype = new GuacAdmin.Field._HTML_INPUT();
/**
* A basic numeric field, leveraging the new HTML5 field types.
*
@ -144,7 +173,6 @@ GuacAdmin.Field.NUMERIC = function() {
GuacAdmin.Field.NUMERIC.prototype = new GuacAdmin.Field._HTML_INPUT();
/**
* Simple checkbox.
*
@ -216,7 +244,6 @@ GuacAdmin.Field.ENUM = function(values) {
GuacAdmin.Field.ENUM.prototype = new GuacAdmin.Field();
/**
* An arbitrary button.
*
@ -363,7 +390,6 @@ GuacAdmin.addUser = function(name, parameters) {
};
/**
* User edit dialog which allows editing of the user's password and connection
* access level.
@ -619,8 +645,8 @@ GuacAdmin.UserEditor = function(name, parameters) {
GuacAdmin.reset();
}
catch (e) {
alert(e.message);
catch (status) {
alert(status.message);
}
};
@ -658,8 +684,8 @@ GuacAdmin.UserEditor = function(name, parameters) {
}
// Alert on failure
catch (e) {
alert(e.message);
catch (status) {
alert(status.message);
}
}
@ -816,8 +842,10 @@ GuacAdmin.ConnectionEditor = function(connection, parameters) {
start.textContent = GuacAdmin.formatDate(record.start);
if (record.duration !== null)
duration.textContent = GuacAdmin.formatSeconds(record.duration);
else
else if (record.active)
duration.textContent = "Active now";
else
duration.textContent = "-";
// Add record to pager
history_pager.addElement(row);
@ -882,6 +910,11 @@ GuacAdmin.ConnectionEditor = function(connection, parameters) {
field = new GuacAdmin.Field.ENUM(parameter.options);
break;
// Multiline text field
case GuacamoleService.Protocol.Parameter.MULTILINE:
field = new GuacAdmin.Field.MULTILINE();
break;
default:
continue;
@ -952,8 +985,8 @@ GuacAdmin.ConnectionEditor = function(connection, parameters) {
GuacAdmin.reset();
}
catch (e) {
alert(e.message);
catch (status) {
alert(status.message);
}
};
@ -991,8 +1024,8 @@ GuacAdmin.ConnectionEditor = function(connection, parameters) {
}
// Alert on failure
catch (e) {
alert(e.message);
catch (status) {
alert(status.message);
}
}
@ -1145,8 +1178,8 @@ GuacAdmin.ConnectionGroupEditor = function(group, parameters) {
GuacAdmin.reset();
}
catch (e) {
alert(e.message);
catch (status) {
alert(status.message);
}
};
@ -1184,8 +1217,8 @@ GuacAdmin.ConnectionGroupEditor = function(group, parameters) {
}
// Alert on failure
catch (e) {
alert(e.message);
catch (status) {
alert(status.message);
}
}
@ -1375,8 +1408,8 @@ GuacAdmin.reset = function() {
}
// Alert on failure
catch (e) {
alert(e.message);
catch (status) {
alert(tatusmessage);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,23 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
* Copyright (C) 2013 Glyptodon LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
@ -22,11 +26,6 @@
*/
var GuacUI = GuacUI || {};
/**
* Current session state, including settings.
*/
GuacUI.sessionState = new GuacamoleSessionState();
/**
* Creates a new element having the given tagname and CSS class.
*/
@ -108,6 +107,55 @@ GuacUI.removeClass = function(element, classname) {
};
/**
* Opens the connection group having the given ID in a new tab/window.
*
* @param {String} id The ID of the connection group to open.
* @param {String} parameters Any parameters that should be added to the URL,
* for sake of authentication.
*/
GuacUI.openConnectionGroup = function(id, parameters) {
GuacUI.openObject("g/" + id, parameters);
};
/**
* Opens the connection having the given ID in a new tab/window.
*
* @param {String} id The ID of the connection to open.
* @param {String} parameters Any parameters that should be added to the URL,
* for sake of authentication.
*/
GuacUI.openConnection = function(id, parameters) {
GuacUI.openObject("c/" + id, parameters);
};
/**
* Opens the object having the given ID in a new tab/window. The ID must
* include the relevant prefix.
*
* @param {String} id The ID of the object to open, including prefix.
* @param {String} parameters Any parameters that should be added to the URL,
* for sake of authentication.
*/
GuacUI.openObject = function(id, parameters) {
// Get URL
var url = "client.xhtml?id=" + encodeURIComponent(id);
// Add parameters, if given
if (parameters)
url += "&" + parameters;
// Attempt to focus existing window
var current = window.open(null, id);
// If window did not already exist, set up as
// Guacamole client
if (!current.GuacUI)
window.open(url, id);
};
/**
* Object describing the UI's level of audio support. If the user has request
* that audio be disabled, this object will pretend that audio is not
@ -133,7 +181,7 @@ GuacUI.Audio = new (function() {
this.supported = [];
// If sound disabled, we're done now.
if (GuacUI.sessionState.getProperty("disable-sound"))
if (GuacamoleSessionStorage.getItem("disable-sound", false))
return;
// Build array of supported audio formats
@ -214,203 +262,6 @@ GuacUI.Video = new (function() {
})();
/**
* Central registry of all components for all states.
*/
GuacUI.StateManager = new (function() {
/**
* The current state.
*/
var current_state = null;
/**
* Array of arrays of components, indexed by the states they are in.
*/
var components = [];
/**
* Registers the given component with this state manager, to be shown
* during the given states.
*
* @param {GuacUI.Component} component The component to register.
* @param {Number} [...] The list of states this component should be
* visible during.
*/
this.registerComponent = function(component) {
// For each state specified, add the given component
for (var i=1; i<arguments.length; i++) {
// Get specified state
var state = arguments[i];
// Get array of components in that state
var component_array = components[state];
if (!component_array)
component_array = components[state] = [];
// Add component
component_array.push(component);
}
};
function allComponents(components, name) {
// Invoke given function on all components in array
for (var i=0; i<components.length; i++)
components[i][name]();
}
/**
* Sets the current visible state.
*/
this.setState = function(state) {
// Hide components in current state
if (current_state && components[current_state])
allComponents(components[current_state], "hide");
// Show all components in new state
current_state = state;
if (components[state])
allComponents(components[state], "show");
};
/**
* Returns the current visible state.
*/
this.getState = function() {
return current_state;
};
})();
/**
* Abstract component which can be registered with GuacUI and shown or hidden
* dynamically based on interface mode.
*
* @constructor
*/
GuacUI.Component = function() {
/**
* Called whenever this component needs to be shown and activated.
* @event
*/
this.onshow = null;
/**
* Called whenever this component needs to be hidden and deactivated.
* @event
*/
this.onhide = null;
};
/**
* A Guacamole UI component which can be repositioned by dragging.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.DraggableComponent = function(element) {
var draggable_component = this;
var position_x = 0;
var position_y = 0;
var start_x = 0;
var start_y = 0;
/*
* Record drag start when finger hits element
*/
if (element)
element.addEventListener("touchstart", function(e) {
if (e.touches.length == 1) {
start_x = e.touches[0].screenX;
start_y = e.touches[0].screenY;
}
e.stopPropagation();
}, true);
/*
* Update position based on last touch
*/
if (element)
element.addEventListener("touchmove", function(e) {
if (e.touches.length == 1) {
var new_x = e.touches[0].screenX;
var new_y = e.touches[0].screenY;
position_x += new_x - start_x;
position_y += new_y - start_y;
start_x = new_x;
start_y = new_y;
// Move magnifier to new position
draggable_component.move(position_x, position_y);
}
e.preventDefault();
e.stopPropagation();
}, true);
if (element)
element.addEventListener("touchend", function(e) {
e.stopPropagation();
}, true);
/**
* Moves this component to the specified location relative to its normal
* position.
*
* @param {Number} x The X coordinate in pixels.
* @param {Number} y The Y coordinate in pixels.
*/
this.move = function(x, y) {
element.style.WebkitTransform =
element.style.MozTransform =
element.style.OTransform =
element.style.msTransform =
element.style.transform = "translate("
+ x + "px, " + y + "px)";
if (draggable_component.onmove)
draggable_component.onmove(x, y);
};
/**
* Trigered whenever this element is moved.
*
* @event
* @param {Number} x The new X coordinate.
* @param {Number} y The new Y coordinate.
*/
this.onmove = null;
};
/**
* A connection UI object which can be easily added to a list of connections
* for sake of display.
@ -642,7 +493,6 @@ GuacUI.Pager = function(container) {
};
/**
* Interface object which displays the progress of a download, ultimately
* becoming a download link once complete.
@ -701,6 +551,22 @@ GuacUI.Download = function(filename) {
progress.textContent = text;
};
/**
* Updates the content of the dialog to reflect an error condition
* represented by the given text.
*
* @param {String} text A human-readable description of the error.
*/
this.showError = function(text) {
element.removeChild(progress);
GuacUI.addClass(element, "error");
var status = GuacUI.createChildElement(element, "div", "status");
status.textContent = text;
};
/**
* Removes the progress indicator and replaces it with a download button.
*/
@ -709,7 +575,7 @@ GuacUI.Download = function(filename) {
element.removeChild(progress);
GuacUI.addClass(element, "complete");
var download = GuacUI.createChildElement(element, "div", "download");
var download = GuacUI.createChildElement(element, "button");
download.textContent = "Download";
download.onclick = function() {
if (guac_download.ondownload)
@ -739,6 +605,108 @@ GuacUI.Download = function(filename) {
};
/**
* Interface object which displays the progress of a upload.
*
* @constructor
* @param {String} filename The name the file will have once complete.
*/
GuacUI.Upload = function(filename) {
/**
* Reference to this GuacUI.Upload.
* @private
*/
var guac_upload = this;
/**
* The outer div representing the notification.
* @private
*/
var element = GuacUI.createElement("div", "upload notification");
/**
* Title bar describing the notification.
* @private
*/
var title = GuacUI.createChildElement(element, "div", "title-bar");
/**
* Close button for removing the notification.
* @private
*/
var close_button = GuacUI.createChildElement(title, "div", "close");
close_button.onclick = function() {
if (guac_upload.onclose)
guac_upload.onclose();
};
GuacUI.createChildElement(title, "div", "title").textContent =
"File Transfer";
GuacUI.createChildElement(element, "div", "caption").textContent =
filename + " ";
/**
* Progress bar and status.
* @private
*/
var progress = GuacUI.createChildElement(element, "div", "progress");
/**
* The actual moving bar within the progress bar.
* @private
*/
var bar = GuacUI.createChildElement(progress, "div", "bar");
/**
* The textual readout of progress.
* @private
*/
var progress_status = GuacUI.createChildElement(progress, "div");
/**
* Updates the content of the progress indicator with the given text.
*
* @param {String} text The text to assign to the progress indicator.
* @param {Number} percent The overall percent complete.
*/
this.updateProgress = function(text, percent) {
progress_status.textContent = text;
bar.style.width = percent + "%";
};
/**
* Updates the content of the dialog to reflect an error condition
* represented by the given text.
*
* @param {String} text A human-readable description of the error.
*/
this.showError = function(text) {
element.removeChild(progress);
GuacUI.addClass(element, "error");
var status = GuacUI.createChildElement(element, "div", "status");
status.textContent = text;
};
/**
* Returns the element representing this notification.
*/
this.getElement = function() {
return element;
};
/**
* Called when the close button of this notification is clicked.
* @event
*/
this.onclose = null;
};
/**
* A grouping component. Child elements can be added via the addElement()
* function. By default, groups display as collapsed.

View File

@ -0,0 +1,212 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Set of thumbnails for each connection, indexed by ID.
*/
GuacamoleHistory = new (function() {
/**
* Reference to this GuacamoleHistory.
*/
var guac_history = this;
/**
* The number of entries to allow before removing old entries based on the
* cutoff.
*/
var IDEAL_LENGTH = 6;
/**
* The maximum age of a history entry before it is removed, in
* milliseconds.
*/
var CUTOFF_AGE = 900000;
var history = {};
function truncate() {
// Build list of entries
var entries = [];
for (var old_id in history)
entries.push(history[old_id]);
// Avoid history growth beyond defined number of entries
if (entries.length > IDEAL_LENGTH) {
// Sort list
entries.sort(GuacamoleHistory.Entry.compare);
// Remove entries until length is ideal or all are recent
var now = new Date().getTime();
while (entries.length > IDEAL_LENGTH
&& now - entries[0].accessed > CUTOFF_AGE) {
// Remove entry
var removed = entries.shift();
delete history[removed.id];
}
}
}
/**
* Returns the URL for the thumbnail of the connection with the given ID,
* or undefined if no thumbnail is associated with that connection.
*/
this.get = function(id) {
return history[id] || new GuacamoleHistory.Entry();
};
/**
* Updates the thumbnail and access time of the history entry for the
* connection with the given ID.
*/
this.update = function(id, thumbnail) {
/* Do nothing if localStorage not present */
if (!localStorage)
return;
// Create updated entry
var entry = new GuacamoleHistory.Entry(
id,
thumbnail,
new Date().getTime()
);
// Store entry in history
history[id] = entry;
truncate();
// Save updated history, ignore inability to use localStorage
try {
localStorage.setItem("GUAC_HISTORY", JSON.stringify(history));
}
catch (ignore) {}
};
/**
* Reloads all history data.
*/
this.reload = function() {
/* Do nothing if localStorage not present */
if (!localStorage)
return;
// Get old and new for comparison, ignore inability to use localStorage
var old_history = history;
try {
var new_history = JSON.parse(localStorage.getItem("GUAC_HISTORY") || "{}");
}
catch (ignore) {
return;
}
// Update history
history = new_history;
// Call onchange handler as necessary
if (guac_history.onchange) {
// Produce union of all known IDs
var known_ids = {};
for (var new_id in new_history) known_ids[new_id] = true;
for (var old_id in old_history) known_ids[old_id] = true;
// For each known ID
for (var id in known_ids) {
// Get entries
var old_entry = old_history[id];
var new_entry = new_history[id];
// Call handler for all changed
if (!old_entry || !new_entry
|| old_entry.accessed != new_entry.accessed)
guac_history.onchange(id, old_entry, new_entry);
}
} // end onchange
};
/**
* Event handler called whenever a history entry is changed.
*
* @event
* @param {String} id The ID of the connection whose history entry is
* changing.
* @param {GuacamoleHistory.Entry} old_entry The old value of the entry, if
* any.
* @param {GuacamoleHistory.Entry} new_entry The new value of the entry, if
* any.
*/
this.onchange = null;
// Reload when modified
window.addEventListener("storage", guac_history.reload, false);
// Initial load
guac_history.reload();
})();
/**
* A single entry in the indexed connection usage history.
*
* @constructor
* @param {String} id The ID of this connection.
* @param {String} thumbnail The URL of the thumbnail to use to represent this
* connection.
* @param {Number} last_access The time this connection was last accessed, in
* seconds.
*/
GuacamoleHistory.Entry = function(id, thumbnail, last_access) {
/**
* The ID of the connection associated with this history entry.
*/
this.id = id;
/**
* The thumbnail associated with the connection associated with this history
* entry.
*/
this.thumbnail = thumbnail;
/**
* The time the connection associated with this entry was last accessed.
*/
this.accessed = last_access;
};
GuacamoleHistory.Entry.compare = function(a, b) {
return a.accessed - b.accessed;
};

View File

@ -1,19 +1,23 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
* Copyright (C) 2013 Glyptodon LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
@ -321,19 +325,22 @@ GuacamoleService.PermissionSet = function() {
GuacamoleService.handleResponse = function(xhr) {
// For HTTP Forbidden, just return permission denied
if (xhr.status == 403)
throw new Error("Permission denied.");
if (xhr.status === 403)
throw new Guacamole.Status(Guacamole.Status.Code.CLIENT_FORBIDDEN, "Permission denied.");
// Otherwise, if unsuccessful, throw error with message derived from
// response
if (xhr.status != 200) {
if (xhr.status !== 200) {
// Retrieve error code
var code = parseInt(xhr.getResponseHeader("Guacamole-Status-Code"));
// Retrieve error message
var message = xhr.getResponseHeader("Guacamole-Error-Message")
|| xhr.statusText;
// Throw error with derived message
throw new Error(message);
throw new Guacamole.Status(code, message);
}
@ -1272,6 +1279,11 @@ GuacamoleService.Protocol.Parameter.BOOLEAN = 3;
*/
GuacamoleService.Protocol.Parameter.ENUM = 4;
/**
* A free-form, multi-line text field.
*/
GuacamoleService.Protocol.Parameter.MULTILINE = 5;
/**
* Collection of service functions which deal with protocols. Each function
* makes an explicit HTTP query to the server, and parses the response.
@ -1364,6 +1376,11 @@ GuacamoleService.Protocols = {
parameter.type = GuacamoleService.Protocol.Parameter.ENUM;
break;
// Multiline text parameter
case "multiline":
parameter.type = GuacamoleService.Protocol.Parameter.MULTILINE;
break;
}
// Parse all options
@ -1396,3 +1413,28 @@ GuacamoleService.Protocols = {
}
};
/**
* Collection of service functions which deal with the clipboard state. Each
* function makes an explicit HTTP query to the server, and parses the response.
*/
GuacamoleService.Clipboard = {
"get" : function(parameters) {
// Construct request URL
var list_url = "clipboard";
if (parameters) list_url += "?" + parameters;
// Get permission list
var xhr = new XMLHttpRequest();
xhr.open("GET", list_url, false);
xhr.send(null);
// Handle response
GuacamoleService.handleResponse(xhr);
return xhr.responseText;
}
};

View File

@ -1,107 +1,160 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
* Copyright (C) 2013 Glyptodon LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Maintains state across multiple Guacamole pages via HTML5 Web Storage.
* @constructor
* Global storage for Guacamole pages.
*/
function GuacamoleSessionState() {
GuacamoleSessionStorage = (opener && opener.GuacamoleSessionStorage) || new (function() {
/**
* Reference to this GuacamoleSessionState.
* The contents of storage, as a JSON string containing name/value pairs as
* properties.
*
* @private
* @type String
*/
var guac_state = this;
var stored_json = "{}";
/**
* The last read state object.
* @private
* Called whenever an item value changes.
*
* @callback onchange
* @param {String} name The name of the item changed.
* @param value The new item value.
*/
var state = localStorage.getItem("GUACAMOLE_STATE") || {};
/**
* Reloads the internal state, sending onchange events for all changed,
* deleted, or new properties.
* All attached listeners.
*
* @type onchange[]
*/
this.reload = function() {
var listeners = [];
/**
* Notifies all listeners that an item has changed.
*
* @param {String} name The name of the item that changed.
* @param value The new item value.
*/
function __notify_changed(name, value) {
for (var i=0; i<listeners.length; i++)
listeners[i](name, value);
}
/**
* Returns the value stored within the item having the given name.
*
* @param {String} name The name of the item to read.
* @param [value] The default value, if any.
* @return The value of the given item.
*/
this.getItem = function(name, value) {
// Attempt to read JSON from localStorage, default to local variable
var json = stored_json;
if (localStorage) {
try {
json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
}
catch (ignore) {}
}
var obj = JSON.parse(json);
if (obj[name] !== undefined)
return obj[name];
return value;
};
/**
* Sets the item having the given name to the given value.
*
* @param {String} name The name of the item to change.
* @param [value] An arbitrary value.
*/
this.setItem = function(name, value) {
// Attempt to read JSON from localStorage, default to local variable
var json = stored_json;
if (localStorage) {
try {
json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
}
catch (ignore) {}
}
// Modify object property
var obj = JSON.parse(json);
var old = obj[name];
obj[name] = value;
// Notify of change
if (old !== value)
__notify_changed(name, value);
// Attempt to set JSON within localStorage, default to local variable
stored_json = JSON.stringify(obj);
if (localStorage) {
try {
localStorage.setItem("GUACAMOLE_STATE", stored_json);
}
catch (ignore) {}
}
};
// Reload when modified
window.addEventListener("storage", function reload() {
// Pull current state
var new_state = JSON.parse(localStorage.getItem("GUACAMOLE_STATE") || "{}");
var new_json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
// Assign new state
var old_state = state;
state = new_state;
var new_state = JSON.parse(new_json);
var old_state = JSON.parse(stored_json);
// Check if any values are different
for (var name in new_state) {
// If value changed, call handler
// If value changed, notify
var old = old_state[name];
if (old != new_state[name]) {
// Call change handler
if (guac_state.onchange)
guac_state.onchange(state, new_state, name);
if (old !== new_state[name])
__notify_changed(name, new_state[name]);
}
}
stored_json = new_json;
};
}, false);
/**
* Sets the given property to the given value.
* Ensures that the given function will be called for each change in
* item value. The function must accept a single argument which will be
* the name of the item changed.
*
* @param {String} name The name of the property to change.
* @param value An arbitrary value.
* @param {onchange} onchange The function to call when an item changes.
*/
this.setProperty = function(name, value) {
state[name] = value;
localStorage.setItem("GUACAMOLE_STATE", JSON.stringify(state));
this.addChangeListener = function(onchange) {
listeners.push(onchange);
};
/**
* Returns the value stored under the property having the given name.
*
* @param {String} name The name of the property to read.
* @return The value of the given property.
*/
this.getProperty = function(name) {
return state[name];
};
/**
* Event which is fired whenever a property value is changed externally.
*
* @event
* @param old_state An object whose properties' values are the old values
* of this GuacamoleSessionState.
* @param new_state An object whose properties' values are the new values
* of this GuacamoleSessionState.
* @param {String} name The name of the property that is being changed.
*/
this.onchange = null;
// Reload when modified
window.addEventListener("storage", guac_state.reload, false);
// Initial load
guac_state.reload();
}
})();

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
button#back {
background-image: url('../images/action-icons/guac-back.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}
button#add-user {
background-image: url('../images/action-icons/guac-user-add.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}
button#add-connection {
background-image: url('../images/action-icons/guac-monitor-add.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}
button#add-connection-group {
background-image: url('../images/action-icons/guac-group-add.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* fadein: Fade from fully transparent to fully opaque.
*/
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
@-moz-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
@-webkit-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
/**
* fadeout: Fade from fully opaque to fully transparent.
*/
@keyframes fadeout {
from { opacity: 1; }
to { opacity: 0; }
}
@-moz-keyframes fadeout {
from { opacity: 1; }
to { opacity: 0; }
}
@-webkit-keyframes fadeout {
from { opacity: 1; }
to { opacity: 0; }
}

View File

@ -1,20 +1,23 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
* Copyright (C) 2013 Glyptodon LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
body {
@ -22,6 +25,7 @@ body {
font-family: FreeSans, Helvetica, Arial, sans-serif;
padding: 0;
margin: 0;
overflow: hidden;
}
img {
@ -29,8 +33,9 @@ img {
}
.software-cursor {
cursor: url('../images/mouse/dot.gif'),url('../images/mouse/blank.cur'),default;
cursor: url('../images/mouse/blank.gif'),url('../images/mouse/blank.cur'),default;
overflow: hidden;
cursor: none;
}
.guac-error .software-cursor {
@ -65,27 +70,6 @@ div.dialogMiddle {
vertical-align: middle;
}
div.dialog {
padding: 1em;
max-width: 75%;
text-align: left;
display: inline-block;
}
div.dialog h1 {
margin: 0;
margin-bottom: 0.25em;
text-align: center;
}
div.dialog div.buttons {
margin: 0;
margin-top: 0.5em;
text-align: center;
}
button {
border-style: solid;
@ -130,6 +114,11 @@ div.dialog p {
margin: 0;
}
div#main {
overflow: auto;
position: absolute;
}
div.displayOuter {
height: 100%;
width: 100%;
@ -256,15 +245,63 @@ div#viewportClone {
visibility: hidden;
}
.status {
text-shadow: 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black;
font-size: xx-large;
color: white;
@keyframes show-dialog {
0% {transform: scale(0.75); }
100% {transform: scale(1); }
}
.guac-error .status {
text-shadow: 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black;
color: #D44;
@-webkit-keyframes show-dialog {
0% {-webkit-transform: scale(0.75); }
100% {-webkit-transform: scale(1); }
}
.dialog {
animation-name: show-dialog;
animation-timing-function: linear;
animation-duration: 0.125s;
-webkit-animation-name: show-dialog;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 0.125s;
max-width: 75%;
max-height: none;
width: 4in;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
padding: 0.5em;
text-align: left;
}
.guac-error .dialog {
background: #FDD;
border: 1px solid #964040;
}
.dialog .title {
font-size: 1.1em;
font-weight: bold;
border-bottom: 1px solid black;
margin-bottom: 0.5em;
}
.dialog .status, .dialog .countdown, .dialog .reconnect {
margin: 0;
padding: 0.5em;
font-size: 0.8em;
}
.dialog .reconnect {
display: none;
}
.guac-error .dialog .reconnect {
display: block;
text-align: center;
}
p.hint {
@ -297,24 +334,29 @@ p.hint {
.notification {
font-size: 0.9em;
font-size: 0.7em;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.25);
background: black;
opacity: 0.9;
border: 1px solid rgba(0, 0, 0, 0.75);
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
background: white;
color: white;
color: black;
padding: 0.5em;
margin: 1em;
overflow: hidden;
box-shadow: 0.25em 0.25em 0.25em rgba(0, 0, 0, 0.75);
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.25);
}
.notification div {
display: inline-block;
text-align: left;
}
.notification .title-bar {
@ -322,7 +364,7 @@ p.hint {
white-space: nowrap;
font-weight: bold;
border-bottom: 1px solid white;
border-bottom: 1px solid black;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
@ -331,10 +373,6 @@ p.hint {
vertical-align: middle;
}
.notification .caption {
color: silver;
}
.notification .close {
background: url('../images/action-icons/guac-close.png');
@ -361,6 +399,7 @@ p.hint {
to {background-position: 64px 0px;}
}
.notification .caption,
.download.notification .caption {
width: 100%;
white-space: nowrap;
@ -368,7 +407,15 @@ p.hint {
text-overflow: ellipsis;
}
.upload.notification .status,
.download.notification .status {
color: red;
font-size: 1em;
padding: 1em;
}
.download.notification .progress,
.upload.notification .progress,
.download.notification .download {
margin-top: 1em;
@ -377,16 +424,47 @@ p.hint {
min-width: 5em;
border: 1px solid gray;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
text-align: center;
float: right;
position: relative;
}
.upload.notification .progress {
float: none;
width: 80%;
margin-left: auto;
margin-right: auto;
}
.download.notification .progress div,
.upload.notification .progress div {
position: relative;
}
.download.notification .progress .bar,
.upload.notification .progress .bar {
background: #A3D655;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0;
box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.5),
inset -1px -1px 0 rgba( 0, 0, 0, 0.1),
1px 1px 0 gray;
}
.upload.notification .progress,
.download.notification .progress {
background: #444 url('../images/progress.png');
background: #C2C2C2 url('../images/progress.png');
background-size: 16px 16px;
-moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px;
@ -418,3 +496,284 @@ p.hint {
height: 0;
overflow: hidden;
}
/* Menu */
/**
* show-menu: Animation for showing the menu
*/
@keyframes show-menu {
from { left: -480px; opacity: 0;}
to { left: 0; opacity: 1;}
}
@-moz-keyframes show-menu {
from { left: -480px; opacity: 0;}
to { left: 0; opacity: 1;}
}
@-webkit-keyframes show-menu {
from { left: -480px; opacity: 0;}
to { left: 0; opacity: 1;}
}
/**
* hide-menu: Animation for hiding the menu
*/
@keyframes hide-menu {
from { left: 0; opacity: 1;}
to { left: -480px; opacity: 0;}
}
@-moz-keyframes hide-menu {
from { left: 0; opacity: 1;}
to { left: -480px; opacity: 0;}
}
@-webkit-keyframes hide-menu {
from { left: 0; opacity: 1;}
to { left: -480px; opacity: 0;}
}
#menu {
overflow: auto;
position: fixed;
top: 0;
bottom: 0;
max-width: 100%;
width: 480px;
background: #EEE;
box-shadow: inset -1px 0 2px white, 1px 0 2px black;
z-index: 10;
}
#menu .content {
padding: 1em;
}
#menu .content > * {
margin: 1em 0;
}
#menu, #menu.closed {
left: -480px;
opacity: 0;
}
#menu.open {
left: 0px;
opacity: 1;
}
#menu.closed {
animation-name: hide-menu;
animation-timing-function: linear;
animation-duration: 0.05s;
-webkit-animation-name: hide-menu;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 0.05s;
}
#menu.open {
animation-name: show-menu;
animation-timing-function: linear;
animation-duration: 0.05s;
-webkit-animation-name: show-menu;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 0.05s;
}
#clipboard-settings textarea {
width: 100%;
border: 1px solid #AAA;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
border-radius: 0.25em;
white-space: pre;
display: block;
}
#mouse-settings .choice {
text-align: center;
}
#mouse-settings .choice .figure {
display: inline-block;
vertical-align: middle;
max-width: 80%;
}
#keyboard-settings .caption {
font-size: 0.9em;
margin-left: 2em;
margin-right: 2em;
}
#mouse-settings .figure .caption {
text-align: center;
font-size: 0.9em;
}
#mouse-settings .figure img {
display: block;
max-width: 100%;
margin: 1em auto;
}
#menu h3 {
background: rgba(0, 0, 0, 0.4);
padding: 0.25em 0.5em;
margin: 0;
font-size: 1em;
color: white;
font-weight: bold;
}
#keyboard-settings .figure {
float: right;
max-width: 30%;
margin: 1em;
}
#keyboard-settings .figure img {
max-width: 100%;
}
#zoom-settings {
text-align: center;
}
#zoom-out, #zoom-in, #zoom-state {
display: inline-block;
vertical-align: middle;
}
#zoom-out, #zoom-in {
max-width: 3em;
border: 1px solid rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.1);
border-radius: 2em;
margin: 0.5em;
cursor: pointer;
}
#zoom-out img, #zoom-in img {
max-width: 100%;
opacity: 0.5;
}
#zoom-out:hover, #zoom-in:hover {
border: 1px solid rgba(0, 0, 0, 1);
background: #CDA;
}
#zoom-out:hover img, #zoom-in:hover img {
opacity: 1;
}
#zoom-state {
font-size: 2em;
}
#text-input {
display: none;
position: absolute;
border-top: 1px solid rgba(0, 0, 0, 0.5);
background: #CDA;
}
#text-input-field,
#text-input-buttons {
display: inline-block;
vertical-align: middle;
}
#text-input-field {
width: 30%;
overflow: hidden;
white-space: nowrap;
}
#text-input-buttons {
width: 70%;
text-align: right;
}
#target {
border: none;
border-radius: 0;
display: inline-block;
vertical-align: middle;
font-size: 12pt;
width: 100%;
height: auto;
resize: none;
outline: none;
margin: 0;
padding: 0.25em;
padding-left: 0;
background: #CDA;
overflow: hidden;
}
#text-input.open {
display: block;
}
#sent-history {
display: inline-block;
vertical-align: middle;
padding: 0.25em;
padding-right: 0;
}
#sent-history .sent-text {
display: inline-block;
vertical-align: baseline;
white-space: pre;
font-size: 12pt;
animation: fadeout 1s linear;
-webkit-animation: fadeout 1s linear;
opacity: 0;
}
#text-input-buttons button {
border: 1px solid rgba(0, 0, 0, 0.5);
background: none;
color: black;
box-shadow: none;
text-shadow: none;
padding: 0.25em;
max-width: 20%;
margin: 0.1em;
min-width: 3em;
}
#text-input-buttons button:active,
#text-input-buttons button.pressed {
border: 1px solid rgba(255, 255, 255, 0.5);
background: rgba(0, 0, 0, 0.75);
color: white;
}
.notification.message {
background: #DFD;
animation: fadein 0.125s linear, fadeout 2s 3s linear;
-webkit-animation: fadein 0.125s linear, fadeout 2s 3s linear;
}
.notification.message .caption {
vertical-align: middle;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
.keyboard-container {
text-align: center;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
margin: 0;
padding: 0;
border-top: 1px solid black;
background: #222;
opacity: 0.85;
z-index: 1;
}
.guac-keyboard {
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
cursor: default;
text-align: left;
vertical-align: middle;
}
.guac-keyboard .guac-keyboard-key-container {
display: inline-block;
}
.guac-keyboard .guac-keyboard-key {
background: #444;
border: 1px outset #888;
-moz-border-radius: 0.1em;
-webkit-border-radius: 0.1em;
-khtml-border-radius: 0.1em;
border-radius: 0.1em;
}
.guac-keyboard .guac-keyboard-cap {
color: white;
font-family: sans-serif;
font-size: 50%;
font-weight: lighter;
text-align: center;
white-space: pre;
}
.guac-keyboard .guac-keyboard-key:hover {
cursor: pointer;
}
.guac-keyboard .guac-keyboard-key.highlight {
background: #666;
border-color: #666;
}
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key.shift,
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
background: #882;
border-color: #DD4;
}
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key.control,
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
background: #882;
border-color: #DD4;
}
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key.alt,
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
background: #882;
border-color: #DD4;
}
.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key.super,
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
background: #882;
border-color: #DD4;
}
.guac-keyboard .guac-keyboard-key.guac-keyboard-pressed {
background: #822;
border-color: #D44;
border-style: inset;
}
.guac-keyboard .guac-keyboard-row {
line-height: 0;
}
.guac-keyboard .guac-keyboard-column {
display: inline-block;
text-align: center;
vertical-align: top;
}
.guac-keyboard .guac-keyboard-gap {
display: inline-block;
}
/* Hide keycaps requiring modifiers which are NOT currently active. */
.guac-keyboard:not(.guac-keyboard-modifier-caps)
.guac-keyboard-cap.guac-keyboard-requires-caps,
.guac-keyboard:not(.guac-keyboard-modifier-numsym)
.guac-keyboard-cap.guac-keyboard-requires-numsym,
.guac-keyboard:not(.guac-keyboard-modifier-shift)
.guac-keyboard-cap.guac-keyboard-requires-shift,
/* Hide keycaps NOT requiring modifiers which ARE currently active, where that
modifier is used to determine which cap is displayed for the current key. */
.guac-keyboard.guac-keyboard-modifier-shift
.guac-keyboard-key.guac-keyboard-uses-shift
.guac-keyboard-cap:not(.guac-keyboard-requires-shift),
.guac-keyboard.guac-keyboard-modifier-numsym
.guac-keyboard-key.guac-keyboard-uses-numsym
.guac-keyboard-cap:not(.guac-keyboard-requires-numsym),
.guac-keyboard.guac-keyboard-modifier-caps
.guac-keyboard-key.guac-keyboard-uses-caps
.guac-keyboard-cap:not(.guac-keyboard-requires-caps) {
display: none;
}

View File

@ -0,0 +1,364 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
* {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
input[type=checkbox], input[type=text], textarea {
-webkit-tap-highlight-color: rgba(128,192,128,0.5);
}
input[type=submit], button {
-webkit-appearance: none;
}
body {
background: #EEE;
font-family: FreeSans, Helvetica, Arial, sans-serif;
padding: 0;
margin: 0;
}
#manage {
display: none;
}
.admin #manage {
display: inline-block;
}
button#manage {
background-image: url('../images/action-icons/guac-config.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}
div#login-ui {
height: 100%;
width: 100%;
position: fixed;
left: 0;
top: 0;
display: table;
}
@keyframes shake-head {
0% { margin-left: 0.25em; margin-right: -0.25em; }
25% { margin-left: -0.25em; margin-right: 0.25em; }
50% { margin-left: 0.25em; margin-right: -0.25em; }
75% { margin-left: -0.25em; margin-right: 0.25em; }
100% { margin-left: 0.00em; margin-right: 0.00em; }
}
@-webkit-keyframes shake-head {
0% { margin-left: 0.25em; margin-right: -0.25em; }
25% { margin-left: -0.25em; margin-right: 0.25em; }
50% { margin-left: 0.25em; margin-right: -0.25em; }
75% { margin-left: -0.25em; margin-right: 0.25em; }
100% { margin-left: 0.00em; margin-right: 0.00em; }
}
p#login-error {
display: none;
}
.error p#login-error {
display: block;
position: fixed;
left: 0;
right: 0;
top: 0;
padding: 1em;
margin: 0.2em;
background: #FDD;
border: 1px solid #964040;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
text-align: center;
color: #964040;
}
.error #login-form {
animation-name: shake-head;
animation-duration: 0.25s;
animation-timing-function: linear;
-webkit-animation-name: shake-head;
-webkit-animation-duration: 0.25s;
-webkit-animation-timing-function: linear;
}
div#login-logo {
position: relative;
bottom: 0;
display: inline-block;
vertical-align: middle;
}
div#login-dialog-middle {
width: 100%;
display: table-cell;
vertical-align: middle;
text-align: center;
}
div#login-dialog {
max-width: 75%;
text-align: left;
display: inline-block;
}
div#login-dialog h1 {
margin-top: 0;
margin-bottom: 0em;
text-align: center;
}
div#login-dialog #buttons {
padding-top: 0.5em;
text-align: right;
}
input[type="submit"]#login, button#login {
background-image: url('../images/guacamole-logo-64.png');
background-repeat: no-repeat;
background-size: 1.5em;
background-position: 0.5em 0.25em;
padding-left: 2.5em;
}
div#login-dialog #login-fields {
vertical-align: middle;
padding: 1em;
border-top: 1px solid #999;
border-bottom: 1px solid #999;
}
div#login-dialog th {
text-shadow: 1px 1px white;
}
div#login-dialog #login-fields input {
border: 1px solid #777;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
width: 100%;
}
div#login-dialog #login-fields img.logo {
position: fixed;
margin: 10px;
left: 0;
bottom: 0;
opacity: 0.1;
z-index: -1;
}
div#version {
text-align: center;
font-style: italic;
font-size: 0.75em;
color: black;
opacity: 0.5;
padding: 0.5em;
}
img {
border: none;
}
img#license {
float: right;
margin: 2px;
}
div#connection-list-ui h1 {
margin: 0;
padding: 0.5em;
font-size: 2em;
vertical-align: middle;
text-align: center;
}
div#connection-list-ui h2 {
padding: 0.5em;
margin: 0;
font-size: 1.5em;
font-weight: lighter;
text-shadow: 1px 1px white;
border-top: 1px solid #AAA;
border-bottom: 1px solid #AAA;
background: #DDD;
}
div#connection-list-ui img {
vertical-align: middle;
}
div#logout-panel {
padding: 0.45em;
text-align: right;
float: right;
}
.history-unavailable div#recent-connections {
display: none;
}
div#recent-connections,
div#clipboardDiv,
div#settings,
div#all-connections {
margin: 1em;
padding: 0;
}
#all-connections .list-buttons {
text-align: center;
padding: 0;
}
div#recent-connections {
text-align: center;
}
#no-recent {
color: black;
text-shadow: 1px 1px white;
opacity: 0.5;
font-size: 2em;
font-weight: bolder;
}
div#recent-connections div.connection {
-moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em;
-khtml-border-radius: 0.5em;
border-radius: 0.5em;
display: inline-block;
padding: 1em;
margin: 1em;
text-align: center;
max-width: 75%;
overflow: hidden;
}
.group,
.connection {
cursor: pointer;
}
.connection:hover {
background: #CDA;
}
.group,
.connection .name {
color: black;
font-weight: normal;
padding: 0.1em;
}
.connection .thumbnail {
margin: 0.5em;
}
.connection .thumbnail img {
border: 1px solid black;
box-shadow: 1px 1px 5px black;
max-width: 75%;
}
div#all-connections .connection {
display: block;
text-align: left;
}
div#recent-connections .connection .thumbnail {
display: block;
}
div#all-connections .connection {
padding: 0.1em;
}
div#recent-connections .protocol {
display: none;
}
.caption * {
vertical-align: middle;
}
.caption .name {
margin-left: 0.25em;
}
#clipboardDiv textarea {
width: 100%;
border: 1px solid #AAA;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
border-radius: 0.25em;
white-space: pre;
}
#settings dt {
border-bottom: 1px dotted #AAA;
padding-bottom: 0.25em;
}
#settings dd {
margin: 1.5em;
margin-left: 2.5em;
font-size: 0.75em;
}

View File

@ -0,0 +1,626 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
@import url('animation.css');
* {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
input[type=checkbox], input[type=number], input[type=text], input[type=radio], label, textarea {
-webkit-tap-highlight-color: rgba(128,192,128,0.5);
}
input[type=submit], button {
-webkit-appearance: none;
}
div.location, input[type=text], input[type=number], input[type=password], textarea {
border: 1px solid #777;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
width: 100%;
max-width: 16em;
padding: 0.25em;
font-size: 10pt;
background: white;
cursor: text;
}
textarea {
max-width: none;
width: 30em;
height: 10em;
white-space: nowrap;
overflow: auto;
}
input[type="submit"], button {
background-color: #3C3C3C;
border: 1px solid rgba(0, 0, 0, 0.4);
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
-khtml-border-radius: 0.25em;
border-radius: 0.25em;
color: white;
text-shadow: -1px -1px rgba(0, 0, 0, 0.3);
font-size: 1em;
box-shadow: inset -1px -1px 0.1em rgba(0, 0, 0, 0.25),
inset 1px 1px 0.1em rgba(255, 255, 255, 0.25),
-1px -1px 0.1em rgba(0, 0, 0, 0.25),
1px 1px 0.1em rgba(255, 255, 255, 0.25);
padding: 0.35em;
padding-right: 1em;
padding-left: 1em;
min-width: 5em;
}
input[type="submit"]:hover, button:hover {
background-color: #5A5A5A;
}
input[type="submit"]:active, button:active {
background-color: #2C2C2C;
box-shadow:
inset 1px 1px 0.25em rgba(0, 0, 0, 0.25),
-1px -1px 0.25em rgba(0, 0, 0, 0.25),
1px 1px 0.25em rgba(255, 255, 255, 0.25);
}
button.danger {
background: #A43;
}
button.danger:hover {
background: #C54;
}
button.danger:active {
background: #932;
}
body {
background: #EEE;
font-family: FreeSans, Helvetica, Arial, sans-serif;
padding: 0;
margin: 0;
}
img {
border: none;
vertical-align: middle;
}
h1 {
margin: 0;
padding: 0.5em;
font-size: 2em;
vertical-align: middle;
text-align: center;
}
h2 {
border-top: 1px solid #AAA;
border-bottom: 1px solid #AAA;
background: rgba(0, 0, 0, 0.07);
padding: 0.5em;
margin: 0;
font-size: 1.5em;
font-weight: lighter;
text-shadow: 1px 1px white;
}
div.section {
margin: 0;
padding: 1em;
}
/*
* Dialogs
*/
.dialog-container {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.5);
padding: 1em;
}
.dialog {
max-width: 100%;
width: 8in;
margin-left: auto;
margin-right: auto;
max-height: 100%;
overflow: auto;
border: 1px solid rgba(0, 0, 0, 0.5);
background: #E7E7E7;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.6);
}
.dialog > * {
margin: 1em;
}
.dialog .header {
margin: 0;
}
.dialog td {
position: relative;
}
.dialog .overlay {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 1;
}
.dialog .dropdown {
position: absolute;
z-index: 2;
margin-top: -1px;
width: 3in;
max-height: 2in;
overflow: auto;
border: 1px solid rgba(0, 0, 0, 0.5);
background: white;
font-size: 10pt;
}
.dialog .footer {
text-align: center;
}
/*
* List elements
*/
.list-item {
display: block;
text-align: left;
cursor: pointer;
position: relative;
}
.icon {
width: 24px;
height: 24px;
background-size: 16px 16px;
-moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px;
-khtml-background-size: 16px 16px;
background-repeat: no-repeat;
background-position: center center;
opacity: 0.5;
display: inline-block;
vertical-align: middle;
}
.list-item * {
vertical-align: middle;
}
.list-item .caption {
padding: 0.1em;
}
.list-item .name {
color: black;
font-weight: normal;
padding: 0.1em;
margin-left: 0.25em;
}
.list-item .usage {
float: right;
font-style: italic;
color: gray;
}
.list-item.in-use {
opacity: 0.5;
}
.choice .list-item .usage {
display: none;
}
.choice .list-item.in-use {
opacity: 1;
}
/*
* List element styling
*/
.list-item.selected {
background: #DEB;
}
.list-item.selected > .icon {
opacity: 1.0;
}
.list-item:not(.selected) .caption:hover {
background: #CDA;
}
.choice .list-item {
display: inline-block;
}
.choice input[type='checkbox'] {
vertical-align: top;
height: 24px;
padding: 0;
margin: 0;
}
.disabled .list-item:not(.selected) {
opacity: 0.25;
}
.disabled .list-item:not(.selected):hover {
background: inherit;
}
/*
* List element fields (editing)
*/
/*
.form {
position: absolute;
display: inline-block;
vertical-align: middle;
z-index: 1;
border: 1px solid rgba(0, 0, 0, 0.5);
background: #E7E7E7;
padding: 0;
margin: 0.25em;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.6);
}
*/
.form .fields th,
.form .permissions th {
font-weight: normal;
vertical-align: middle;
text-align: left;
}
.form h2 {
border-top: none;
}
.form h3 {
font-size: 1em;
margin-bottom: 0.25em;
}
.form {
cursor: auto;
animation-name: fadein;
-webkit-animation-name: fadein;
animation-duration: 0.125s;
-webkit-animation-duration: 0.125s;
}
.object-buttons {
text-align: right;
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding-top: 0.5em;
margin: 0.5em;
}
/*
* List element icons
*/
.icon.user {
background-image: url('../images/user-icons/guac-user.png');
}
.icon.user.add {
background-image: url('../images/action-icons/guac-user-add.png');
}
.icon.connection {
background-image: url('../images/protocol-icons/guac-plug.png');
}
.icon.connection.add {
background-image: url('../images/action-icons/guac-monitor-add.png');
}
.protocol {
display: inline-block;
}
.protocol .icon {
width: 24px;
height: 24px;
background-image: url('../images/protocol-icons/guac-plug.png');
background-size: 16px 16px;
-moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px;
-khtml-background-size: 16px 16px;
background-repeat: no-repeat;
background-position: center center;
opacity: 0.5;
}
.protocol .icon.ssh,
.protocol .icon.telnet {
background-image: url('../images/protocol-icons/guac-text.png');
}
.protocol .icon.vnc,
.protocol .icon.rdp {
background-image: url('../images/protocol-icons/guac-monitor.png');
}
.connection .thumbnail {
display: none;
}
/*
* Groups
*/
.group > .children {
margin-left: 13px;
padding-left: 6px;
display: none;
}
.group.expanded > .children {
display: block;
border-left: 1px dotted rgba(0, 0, 0, 0.25);
}
.group > .caption .icon.type {
display: none;
}
.group.balancer > .caption .icon.type {
display: inline-block;
background-image: url('../images/protocol-icons/guac-monitor.png');
}
.group > .caption .icon.group {
opacity: 0.75;
background-image: url('../images/group-icons/guac-closed.png');
}
.group.expanded > .caption .icon.group {
background-image: url('../images/group-icons/guac-open.png');
}
.group.empty > .caption .icon.group {
opacity: 0.25;
background-image: url('../images/group-icons/guac-open.png');
}
.group.empty.balancer > .caption .icon.group {
display: none;
}
/*
* Settings formatting
*/
.form dt,
.settings dt {
border-bottom: 1px dotted #AAA;
padding-bottom: 0.25em;
}
.form dd,
.settings dd {
margin: 1.5em;
margin-left: 2.5em;
font-size: 0.75em;
}
#connections input.name,
#users input.name {
max-width: 80%;
width: 20em;
}
#connection-list,
#user-list {
border: 1px solid rgba(0, 0, 0, 0.25);
min-height: 20em;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
}
#connections #add-connection,
#connections #add-connection-group,
#users #add-user {
font-size: 0.8em;
}
#connection-add-form,
#user-add-form {
margin-bottom: 0.5em;
}
body:not(.manage-connections) .require-manage-connections,
body:not(.manage-users) .require-manage-users {
display: none;
}
body:not(.add-connections) #add-connection,
body:not(.add-connection-groups) #add-connection-group,
body:not(.add-users) #user-add-form {
display: none;
display: none;
}
div#logout-panel {
padding: 0.45em;
text-align: right;
float: right;
}
.history th,
.history td {
padding-left: 1em;
padding-right: 1em;
}
.first-page,
.prev-page,
.set-page,
.next-page,
.last-page {
cursor: pointer;
vertical-align: middle;
}
.first-page.disabled,
.prev-page.disabled,
.set-page.disabled,
.next-page.disabled,
.last-page.disabled {
cursor: auto;
opacity: 0.25;
}
.set-page,
.more-pages {
display: inline-block;
padding: 0.25em;
text-align: center;
min-width: 1.25em;
}
.set-page {
text-decoration: underline;
}
.set-page.current {
cursor: auto;
text-decoration: none;
font-weight: bold;
background: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.1);
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
}
.icon.first-page {
background-image: url('../images/action-icons/guac-first-page.png');
}
.icon.prev-page {
background-image: url('../images/action-icons/guac-prev-page.png');
}
.icon.next-page {
background-image: url('../images/action-icons/guac-next-page.png');
}
.icon.last-page {
background-image: url('../images/action-icons/guac-last-page.png');
}
.buttons,
.list-pager-buttons {
text-align: center;
margin: 1em;
}
button#logout {
background-image: url('../images/action-icons/guac-logout.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}