diff --git a/guacamole/src/main/webapp/index.xhtml b/guacamole/src/main/webapp/index.xhtml
index 5fdd2bfa9..69f68a8c6 100644
--- a/guacamole/src/main/webapp/index.xhtml
+++ b/guacamole/src/main/webapp/index.xhtml
@@ -77,8 +77,8 @@
No recent connections.
-
+
All Connections
+
Clipboard
diff --git a/guacamole/src/main/webapp/scripts/history.js b/guacamole/src/main/webapp/scripts/history.js
index ba6eb0d5f..d605bc984 100644
--- a/guacamole/src/main/webapp/scripts/history.js
+++ b/guacamole/src/main/webapp/scripts/history.js
@@ -3,8 +3,53 @@
*/
GuacamoleHistory = new (function() {
- var history =
- JSON.parse(localStorage.getItem("GUAC_HISTORY") || "{}");
+ /**
+ * 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() {
+
+ // Avoid history growth beyond defined number of entries
+ if (history.length > IDEAL_LENGTH) {
+
+ // Build list of entries
+ var entries = [];
+ for (var old_entry in history)
+ entries.push(old_entry);
+
+ // Sort list
+ entries.sort(GuacamoleHistory.Entries.compare);
+
+ // Remove entries until length is ideal or all are recent
+ var now = new Date().getTime();
+ while (entries.length > IDEAL_LENGTH
+ && entries[0].accessed - now > 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,
@@ -29,12 +74,67 @@ GuacamoleHistory = new (function() {
// Store entry in history
history[id] = entry;
+ truncate();
// Save updated history
localStorage.setItem("GUAC_HISTORY", JSON.stringify(history));
};
+ /**
+ * Reloads all history data.
+ */
+ this.reload = function() {
+
+ // Get old and new for comparison
+ var old_history = history;
+ var new_history = JSON.parse(localStorage.getItem("GUAC_HISTORY") || "{}");
+
+ // 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;
+
+ // Initial load
+ guac_history.reload();
+
})();
/**
@@ -66,4 +166,7 @@ GuacamoleHistory.Entry = function(id, thumbnail, last_access) {
this.accessed = last_access;
};
-
\ No newline at end of file
+
+GuacamoleHistory.Entry.compare = function(a, b) {
+ return a.accessed - b.accessed;
+};
diff --git a/guacamole/src/main/webapp/scripts/root-ui.js b/guacamole/src/main/webapp/scripts/root-ui.js
index 20945767e..60e4c2fdc 100644
--- a/guacamole/src/main/webapp/scripts/root-ui.js
+++ b/guacamole/src/main/webapp/scripts/root-ui.js
@@ -26,7 +26,7 @@ var GuacamoleRootUI = {
"sections": {
"login_form" : document.getElementById("login-form"),
"recent_connections" : document.getElementById("recent-connections"),
- "other_connections" : document.getElementById("other-connections")
+ "all_connections" : document.getElementById("all-connections")
},
"messages": {
@@ -148,6 +148,11 @@ GuacamoleRootUI.getConfigurations = function(parameters) {
*/
GuacamoleRootUI.Connection = function(config) {
+ /**
+ * The configuration associated with this connection.
+ */
+ this.configuration = config;
+
function element(tagname, classname) {
var new_element = document.createElement(tagname);
new_element.className = classname;
@@ -161,6 +166,7 @@ GuacamoleRootUI.Connection = function(config) {
var name = element("span", "name");
var protocol_icon = element("div", "icon " + config.protocol);
var thumbnail = element("div", "thumbnail");
+ var thumb_img;
// Get URL
var url = "client.xhtml?id=" + encodeURIComponent(config.id);
@@ -197,9 +203,9 @@ GuacamoleRootUI.Connection = function(config) {
if (thumbnail_url) {
// Create thumbnail element
- var img = document.createElement("img");
- img.src = thumbnail_url;
- thumbnail.appendChild(img);
+ thumb_img = document.createElement("img");
+ thumb_img.src = thumbnail_url;
+ thumbnail.appendChild(thumb_img);
}
@@ -214,11 +220,59 @@ GuacamoleRootUI.Connection = function(config) {
* Returns whether this connection has an associated thumbnail.
*/
this.hasThumbnail = function() {
- return thumbnail_url && true;
+ 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.
+ */
+GuacamoleRootUI.thumbnailConnections = {};
+
+/**
+ * Set of all configurations, indexed by ID.
+ */
+GuacamoleRootUI.configurations = {};
+
+/**
+ * Adds the given connection to the recent connections list.
+ */
+GuacamoleRootUI.addRecentConnection = function(connection) {
+
+ // Add connection object to list of thumbnailed connections
+ GuacamoleRootUI.thumbnailConnections[connection.configuration.id] =
+ connection;
+
+ // Add connection to recent list
+ GuacamoleRootUI.sections.recent_connections.appendChild(
+ connection.getElement());
+
+ // Hide "No recent connections" message
+ GuacamoleRootUI.messages.no_recent_connections.style.display = "none";
+
+};
+
+
/**
* Resets the interface such that the login UI is displayed if
* the user is not authenticated (or authentication fails) and
@@ -249,26 +303,19 @@ GuacamoleRootUI.reset = function() {
// Add connection icons
for (var i=0; i