+ Click or tap on a user below to manage that user. Depending
+ on your access level, users can be added and deleted, and their
+ passwords can be changed.
+
+
+
+
+
+
+
+
+
Connections
+
+
+
+ Click or tap on a connection below to manage that connection.
+ Depending on your access level, connections can be added and
+ deleted, and their properties (protocol, hostname, port, etc.)
+ can be changed.
+
+
+
+
+
+
+
+
Another connection
+
+
+
+
Protocol:
+
+
+
+
+
+
Hostname:
+
+
+
+
Password:
+
+
+
+
Port:
+
+
+
+
Read-only:
+
+
+
+
Swap red/blue:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Guacamole ${project.version}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/guacamole/src/main/webapp/images/action-icons/guac-config.png b/guacamole/src/main/webapp/images/action-icons/guac-config.png
new file mode 100644
index 000000000..eb91fc0a0
Binary files /dev/null and b/guacamole/src/main/webapp/images/action-icons/guac-config.png differ
diff --git a/guacamole/src/main/webapp/images/action-icons/guac-delete.png b/guacamole/src/main/webapp/images/action-icons/guac-delete.png
new file mode 100644
index 000000000..925e95831
Binary files /dev/null and b/guacamole/src/main/webapp/images/action-icons/guac-delete.png differ
diff --git a/guacamole/src/main/webapp/images/action-icons/guac-monitor-add.png b/guacamole/src/main/webapp/images/action-icons/guac-monitor-add.png
new file mode 100644
index 000000000..0c9ea962c
Binary files /dev/null and b/guacamole/src/main/webapp/images/action-icons/guac-monitor-add.png differ
diff --git a/guacamole/src/main/webapp/images/action-icons/guac-user-add.png b/guacamole/src/main/webapp/images/action-icons/guac-user-add.png
new file mode 100644
index 000000000..8b2d80ece
Binary files /dev/null and b/guacamole/src/main/webapp/images/action-icons/guac-user-add.png differ
diff --git a/guacamole/src/main/webapp/images/user-icons/guac-user.png b/guacamole/src/main/webapp/images/user-icons/guac-user.png
new file mode 100644
index 000000000..527004040
Binary files /dev/null and b/guacamole/src/main/webapp/images/user-icons/guac-user.png differ
diff --git a/guacamole/src/main/webapp/scripts/guac-ui.js b/guacamole/src/main/webapp/scripts/guac-ui.js
index f543a86ee..a84e8a956 100644
--- a/guacamole/src/main/webapp/scripts/guac-ui.js
+++ b/guacamole/src/main/webapp/scripts/guac-ui.js
@@ -392,4 +392,245 @@ GuacUI.DraggableComponent = function(element) {
*/
this.onmove = null;
-};
\ No newline at end of file
+};
+
+/**
+ * A connection UI object which can be easily added to a list of connections
+ * for sake of display.
+ */
+GuacUI.Connection = function(connection) {
+
+ /**
+ * The actual connection associated with this connection UI element.
+ */
+ this.connection = connection;
+
+ function createElement(tagname, classname) {
+ var new_element = document.createElement(tagname);
+ new_element.className = classname;
+ return new_element;
+ }
+
+ // Create connection display elements
+ var element = createElement("div", "connection");
+ var caption = createElement("div", "caption");
+ var protocol = createElement("div", "protocol");
+ var name = createElement("span", "name");
+ var protocol_icon = createElement("div", "icon " + connection.protocol);
+ var thumbnail = createElement("div", "thumbnail");
+ var thumb_img;
+
+ // Get URL
+ var url = "client.xhtml?id=" + encodeURIComponent(connection.id);
+
+ // Create link to client
+ element.onclick = function() {
+
+ // Attempt to focus existing window
+ var current = window.open(null, connection.id);
+
+ // If window did not already exist, set up as
+ // Guacamole client
+ if (!current.GuacUI)
+ window.open(url, connection.id);
+
+ };
+
+ // Add icon
+ protocol.appendChild(protocol_icon);
+
+ // Set name
+ name.textContent = connection.id;
+
+ // Assemble caption
+ caption.appendChild(protocol);
+ caption.appendChild(name);
+
+ // Assemble connection icon
+ element.appendChild(thumbnail);
+ element.appendChild(caption);
+
+ // Add screenshot if available
+ var thumbnail_url = GuacamoleHistory.get(connection.id).thumbnail;
+ if (thumbnail_url) {
+
+ // Create thumbnail element
+ thumb_img = document.createElement("img");
+ thumb_img.src = thumbnail_url;
+ thumbnail.appendChild(thumb_img);
+
+ }
+
+ /**
+ * Returns the DOM element representing this connection.
+ */
+ this.getElement = function() {
+ return element;
+ };
+
+ /**
+ * Returns whether this connection has an associated thumbnail.
+ */
+ this.hasThumbnail = function() {
+ return thumb_img && true;
+ };
+
+ /**
+ * Sets the thumbnail URL of this existing connection. Note that this will
+ * only work if the connection already had a thumbnail associated with it.
+ */
+ this.setThumbnail = function(url) {
+
+ // If no image element, create it
+ if (!thumb_img) {
+ thumb_img = document.createElement("img");
+ thumb_img.src = url;
+ thumbnail.appendChild(thumb_img);
+ }
+
+ // Otherwise, set source of existing
+ else
+ thumb_img.src = url;
+
+ };
+
+};
+
+/**
+ * An arbitrary table-based form.
+ */
+GuacUI.Form = function() {
+
+ var element = GuacUI.createElement("table");
+
+ /**
+ * Returns the DOM element representing this form.
+ */
+ this.getElement = function() {
+ return element;
+ };
+
+ this.addField = function(title, type, value) {
+
+ // Add elements
+ var row = GuacUI.createChildElement(element, "tr");
+ var header = GuacUI.createChildElement(row, "th");
+ var cell = GuacUI.createChildElement(row, "td");
+ var input = GuacUI.createChildElement(cell, "input");
+
+ // Set title
+ header.textContent = title;
+
+ // Set type and value
+ input.setAttribute("type", type);
+ if (value) input.setAttribute("value", value);
+
+ };
+
+};
+
+/**
+ * A user UI object.
+ */
+GuacUI.User = function(username) {
+
+ /**
+ * This user's username.
+ */
+ this.username = username;
+
+ var user_type = "normal";
+
+ // Create connection display elements
+ var element = GuacUI.createElement("div", "user");
+ var caption = GuacUI.createChildElement(element ,"div", "caption");
+ var type = GuacUI.createChildElement(caption, "div", "type");
+ var name = GuacUI.createChildElement(caption, "span", "name");
+ GuacUI.createChildElement(type, "div", "icon " + user_type);
+
+ // Get URL
+ var url = "user.xhtml?name=" + encodeURIComponent(username);
+
+ // Create link to client
+ element.onclick = function() {
+
+ // Attempt to focus existing window
+ var current = window.open(null, username);
+
+ // If window did not already exist, set up as
+ // Guacamole client
+ if (!current.GuacUI)
+ window.open(url, username);
+
+ };
+
+ // Set name
+ name.textContent = username;
+
+ /**
+ * Returns the DOM element representing this connection.
+ */
+ this.getElement = function() {
+ return element;
+ };
+
+};
+
+
+/**
+ * An editable user UI object.
+ */
+GuacUI.EditableUser = function(username) {
+
+ /**
+ * This user's username.
+ */
+ this.username = username;
+
+ /**
+ * Current status of edit mode.
+ */
+ this.edit = false;
+
+ // Create contained user
+ var user = new GuacUI.User(username);
+ var element = user.getElement();
+ GuacUI.addClass(element, "editable");
+
+ // Fields
+ var fields = GuacUI.createChildElement(element, "div", "fields");
+ var form = new GuacUI.Form();
+
+ // Add form
+ fields.appendChild(form.getElement());
+
+ // Add fields
+ form.addField("Password:", "password", "123412341234");
+ form.addField("Re-enter Password:", "password", "123412341234");
+
+ /**
+ * Returns the DOM element representing this connection.
+ */
+ this.getElement = function() {
+ return element;
+ };
+
+ /**
+ * Sets/unsets edit mode. When edit mode is on, the user's properties
+ * will be visible and editable.
+ */
+ this.setEditMode = function(enabled) {
+
+ // Set edit mode
+ this.edit = enabled;
+
+ // Alter class accordingly
+ if (enabled)
+ GuacUI.addClass(element, "edit");
+ else
+ GuacUI.removeClass(element, "edit");
+
+ };
+
+};
+
diff --git a/guacamole/src/main/webapp/scripts/root-ui.js b/guacamole/src/main/webapp/scripts/root-ui.js
index 5beb8cd90..51ba19e27 100644
--- a/guacamole/src/main/webapp/scripts/root-ui.js
+++ b/guacamole/src/main/webapp/scripts/root-ui.js
@@ -91,108 +91,6 @@ GuacamoleRootUI.login = function(username, password) {
};
-/**
- * A connection UI object which can be easily added to a list of connections
- * for sake of display.
- */
-GuacamoleRootUI.Connection = function(connection) {
-
- /**
- * The actual connection associated with this connection UI element.
- */
- this.connection = connection;
-
- function createElement(tagname, classname) {
- var new_element = document.createElement(tagname);
- new_element.className = classname;
- return new_element;
- }
-
- // Create connection display elements
- var element = createElement("div", "connection");
- var caption = createElement("div", "caption");
- var protocol = createElement("div", "protocol");
- var name = createElement("span", "name");
- var protocol_icon = createElement("div", "icon " + connection.protocol);
- var thumbnail = createElement("div", "thumbnail");
- var thumb_img;
-
- // Get URL
- var url = "client.xhtml?id=" + encodeURIComponent(connection.id);
-
- // Create link to client
- element.onclick = function() {
-
- // Attempt to focus existing window
- var current = window.open(null, connection.id);
-
- // If window did not already exist, set up as
- // Guacamole client
- if (!current.GuacUI)
- window.open(url, connection.id);
-
- };
-
- // Add icon
- protocol.appendChild(protocol_icon);
-
- // Set name
- name.textContent = connection.id;
-
- // Assemble caption
- caption.appendChild(protocol);
- caption.appendChild(name);
-
- // Assemble connection icon
- element.appendChild(thumbnail);
- element.appendChild(caption);
-
- // Add screenshot if available
- var thumbnail_url = GuacamoleHistory.get(connection.id).thumbnail;
- if (thumbnail_url) {
-
- // Create thumbnail element
- thumb_img = document.createElement("img");
- thumb_img.src = thumbnail_url;
- thumbnail.appendChild(thumb_img);
-
- }
-
- /**
- * Returns the DOM element representing this connection.
- */
- this.getElement = function() {
- return element;
- };
-
- /**
- * Returns whether this connection has an associated thumbnail.
- */
- this.hasThumbnail = function() {
- return thumb_img && true;
- };
-
- /**
- * Sets the thumbnail URL of this existing connection. Note that this will
- * only work if the connection already had a thumbnail associated with it.
- */
- this.setThumbnail = function(url) {
-
- // If no image element, create it
- if (!thumb_img) {
- thumb_img = document.createElement("img");
- thumb_img.src = url;
- thumbnail.appendChild(thumb_img);
- }
-
- // Otherwise, set source of existing
- else
- thumb_img.src = url;
-
- };
-
-};
-
/**
* Set of all thumbnailed connections, indexed by ID.
*/
diff --git a/guacamole/src/main/webapp/styles/animation.css b/guacamole/src/main/webapp/styles/animation.css
new file mode 100644
index 000000000..80bb237a4
--- /dev/null
+++ b/guacamole/src/main/webapp/styles/animation.css
@@ -0,0 +1,35 @@
+
+/*
+ * Guacamole - Clientless Remote Desktop
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * 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; }
+}
+
diff --git a/guacamole/src/main/webapp/styles/ui.css b/guacamole/src/main/webapp/styles/ui.css
new file mode 100644
index 000000000..f7e99f23c
--- /dev/null
+++ b/guacamole/src/main/webapp/styles/ui.css
@@ -0,0 +1,328 @@
+
+/*
+ * Guacamole - Clientless Remote Desktop
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+@import url('animation.css');
+
+* {
+ -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;
+}
+
+input[type=text], input[type=password] {
+ 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%;
+}
+
+button {
+
+ background: #8A6;
+ border: 1px solid rgba(0, 0, 0, 0.4);
+ -moz-border-radius: 0.6em;
+ -webkit-border-radius: 0.6em;
+ -khtml-border-radius: 0.6em;
+ border-radius: 0.6em;
+
+ color: white;
+ text-shadow: -1px -1px rgba(0, 0, 0, 0.3);
+ font-weight: bold;
+ font-size: 1.125em;
+
+ box-shadow: inset -1px -1px 0.25em rgba(0, 0, 0, 0.25),
+ inset 1px 1px 0.25em rgba(255, 255, 255, 0.25),
+ -1px -1px 0.25em rgba(0, 0, 0, 0.25),
+ 1px 1px 0.25em rgba(255, 255, 255, 0.25);
+
+ padding: 0.35em;
+ padding-right: 1em;
+ padding-left: 1em;
+ min-width: 5em;
+
+}
+
+button:hover {
+ background: #9C7;
+}
+
+button:active {
+
+ padding-left: 1.1em;
+ padding-right: 0.9em;
+ padding-top: 0.45em;
+ padding-bottom: 0.25em;
+
+ 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;
+}
+
+body {
+ background: #EEE;
+ font-family: FreeSans, Helvetica, Arial, sans-serif;
+ padding: 0;
+ margin: 0;
+}
+
+img {
+ border: none;
+ vertical-align: middle;
+}
+
+div#version-dialog {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ text-align: right;
+
+ font-style: italic;
+ font-size: 0.75em;
+ color: black;
+ opacity: 0.5;
+
+ padding: 0.5em;
+}
+
+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;
+
+}
+
+h1 {
+
+ margin: 0;
+ padding: 0.5em;
+
+ font-size: 2em;
+ vertical-align: middle;
+ text-align: center;
+
+}
+
+div.section {
+ margin: 0;
+ padding: 1em;
+}
+
+/*
+ * List elements
+ */
+
+.user,
+.connection {
+
+ display: block;
+ text-align: left;
+ padding: 0.1em;
+ cursor: pointer;
+
+ -moz-border-radius: 0.2em;
+ -webkit-border-radius: 0.2em;
+ -khtml-border-radius: 0.2em;
+ border-radius: 0.2em;
+ border: 1px solid rgba(0, 0, 0, 0);
+
+ position: relative;
+
+}
+
+.connection .thumbnail {
+ margin: 0.5em;
+ display: none;
+}
+
+.connection .thumbnail img {
+ border: 1px solid black;
+ box-shadow: 1px 1px 5px black;
+ max-width: 75%;
+}
+
+.user .type,
+.connection .protocol {
+ display: inline-block;
+}
+
+.user .icon,
+.connection .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;
+}
+
+.user .caption,
+.connection .caption {
+ display: inline-block;
+ vertical-align: top;
+}
+
+.user .caption *,
+.connection .caption * {
+ vertical-align: middle;
+}
+
+.user .name,
+.connection .name {
+ color: black;
+ font-weight: normal;
+ padding: 0.1em;
+ margin-left: 0.25em;
+}
+
+/*
+ * List element edit / non-edit styling
+ */
+
+.user.edit,
+.connection.edit {
+ background: #DEB;
+ border: 1px solid rgba(0, 0, 0, 0.25);
+}
+
+.user.edit .icon,
+.connection.edit .icon {
+ opacity: 1.0;
+}
+
+.user:not(.edit):hover,
+.connection:not(.edit):hover {
+ background: #CDA;
+}
+
+/*
+ * List element fields (editing)
+ */
+
+.user .fields,
+.connection .fields {
+
+ position: absolute;
+ display: inline-block;
+ vertical-align: top;
+ z-index: 1;
+
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ background: #DDD;
+ padding: 0.25em;
+ 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);
+
+}
+
+.user .fields th,
+.connection .fields th {
+ font-weight: normal;
+ font-size: 0.8em;
+}
+
+.user:not(.edit) .fields,
+.connection:not(.edit) .fields {
+ display: none;
+}
+
+.user.edit .fields,
+.connection.edit .fields {
+ animation-name: fadein;
+ -webkit-animation-name: fadein;
+ animation-duration: 0.125s;
+ -webkit-animation-duration: 0.125s;
+}
+
+/*
+ * List element icons
+ */
+
+.user .icon {
+ background-image: url('../images/user-icons/guac-user.png');
+}
+
+.user .icon.add {
+ background-image: url('../images/action-icons/guac-user-add.png');
+}
+
+.protocol .icon {
+ background-image: url('../images/protocol-icons/guac-plug.png');
+}
+
+.protocol .icon.ssh {
+ background-image: url('../images/protocol-icons/guac-text.png');
+}
+
+.protocol .icon.add{
+ background-image: url('../images/action-icons/guac-monitor-add.png');
+}
+
+.protocol .icon.vnc,
+.protocol .icon.rdp {
+ background-image: url('../images/protocol-icons/guac-monitor.png');
+}
+
+/*
+ * Settings formatting
+ */
+
+.settings dt {
+ border-bottom: 1px dotted #AAA;
+ padding-bottom: 0.25em;
+}
+
+.settings dd {
+ margin: 1.5em;
+ margin-left: 2.5em;
+ font-size: 0.75em;
+}