mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 05:31:22 +00:00
Merge pull request #17 from glyptodon/cleanup-angular
GUAC-932: Initial cleanup of Angular code
This commit is contained in:
@@ -142,8 +142,6 @@
|
||||
<jsSourceFile>lib/messageformat/messageformat.js</jsSourceFile>
|
||||
<jsSourceFile>license.txt</jsSourceFile>
|
||||
<jsSourceFile>guacamole-common-js/all.js</jsSourceFile>
|
||||
<jsSourceFile>scripts/session.js</jsSourceFile>
|
||||
<jsSourceFile>scripts/history.js</jsSourceFile>
|
||||
</jsSourceFiles>
|
||||
|
||||
<jsSourceIncludes>
|
||||
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* The module for authentication and management of tokens.
|
||||
*/
|
||||
angular.module('auth', ['util']);
|
||||
angular.module('auth', ['ngCookies']);
|
||||
|
@@ -23,12 +23,13 @@
|
||||
/**
|
||||
* A service for authenticating a user against the REST API.
|
||||
*/
|
||||
angular.module('auth').factory('authenticationService', ['$http', '$injector',
|
||||
function authenticationService($http, $injector) {
|
||||
angular.module('auth').factory('authenticationService', ['$http', '$cookieStore',
|
||||
function authenticationService($http, $cookieStore) {
|
||||
|
||||
var localStorageUtility = $injector.get("localStorageUtility");
|
||||
var service = {};
|
||||
|
||||
|
||||
var AUTH_COOKIE_ID = "GUAC_AUTH";
|
||||
|
||||
/**
|
||||
* Makes a request to authenticate a user using the token REST API endpoint,
|
||||
* returning a promise that can be used for processing the results of the call.
|
||||
@@ -49,8 +50,10 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector',
|
||||
password: password
|
||||
})
|
||||
}).success(function success(data, status, headers, config) {
|
||||
localStorageUtility.set('authToken', data.authToken);
|
||||
localStorageUtility.set('userID', data.userID);
|
||||
$cookieStore.put(AUTH_COOKIE_ID, {
|
||||
authToken : data.authToken,
|
||||
userID : data.userID
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -68,22 +71,43 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector',
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the user ID of the current user.
|
||||
* Returns the user ID of the current user. If the current user is not
|
||||
* logged in, this ID may not be valid.
|
||||
*
|
||||
* @returns {String} The user ID of the current user.
|
||||
* @returns {String}
|
||||
* The user ID of the current user, or null if no authentication data
|
||||
* is present.
|
||||
*/
|
||||
service.getCurrentUserID = function getCurrentUserID() {
|
||||
return localStorageUtility.get('userID');
|
||||
|
||||
// Return user ID, if available
|
||||
var authData = $cookieStore.get(AUTH_COOKIE_ID);
|
||||
if (authData)
|
||||
return authData.userID;
|
||||
|
||||
// No auth data present
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the auth token associated with the current user. If the current
|
||||
* user is not logged in, this token may not be valid.
|
||||
*
|
||||
* @returns {String} The auth token associated with the current user.
|
||||
* @returns {String}
|
||||
* The auth token associated with the current user, or null if no
|
||||
* authentication data is present.
|
||||
*/
|
||||
service.getCurrentToken = function getCurrentToken() {
|
||||
return localStorageUtility.get('authToken');
|
||||
|
||||
// Return auth token, if available
|
||||
var authData = $cookieStore.get(AUTH_COOKIE_ID);
|
||||
if (authData)
|
||||
return authData.authToken;
|
||||
|
||||
// No auth data present
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
return service;
|
||||
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* The module for code used to connect to a connection or balancing group.
|
||||
*/
|
||||
angular.module('client', []);
|
||||
angular.module('client', ['auth', 'history']);
|
||||
|
@@ -135,7 +135,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
|
||||
// Get DAO for reading connections and groups
|
||||
var connectionGroupDAO = $injector.get('connectionGroupDAO');
|
||||
var connectionDAO = $injector.get('connectionDAO');
|
||||
var ClientProperties = $injector.get('clientProperties');
|
||||
var ClientProperties = $injector.get('ClientProperties');
|
||||
|
||||
// Client settings and state
|
||||
$scope.clientProperties = new ClientProperties();
|
||||
@@ -265,9 +265,6 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
|
||||
// Show status dialog when client errors occur
|
||||
$scope.$on('guacClientError', function clientErrorListener(event, client, status) {
|
||||
|
||||
// Disconnect
|
||||
$scope.id = null;
|
||||
|
||||
// Determine translation name of error
|
||||
var errorName = (status in CLIENT_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
|
||||
|
||||
@@ -293,6 +290,10 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
|
||||
|
||||
// Show new status only if disconnected
|
||||
if (status === "closed") {
|
||||
|
||||
// Disconnect
|
||||
$scope.id = null;
|
||||
|
||||
$scope.showStatus({
|
||||
title: "client.status.closedStatusTitle",
|
||||
text: "client.status.tunnelStates." + status
|
||||
@@ -304,9 +305,6 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
|
||||
// Show status dialog when tunnel errors occur
|
||||
$scope.$on('guacTunnelError', function tunnelErrorListener(event, tunnel, status) {
|
||||
|
||||
// Disconnect
|
||||
$scope.id = null;
|
||||
|
||||
// Determine translation name of error
|
||||
var errorName = (status in TUNNEL_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
|
||||
|
||||
|
@@ -30,12 +30,29 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
// Parameters for controlling client state
|
||||
clientProperties : '=',
|
||||
|
||||
/**
|
||||
* Parameters for controlling client state.
|
||||
*
|
||||
* @type ClientProperties|Object
|
||||
*/
|
||||
clientProperties : '=',
|
||||
|
||||
// Parameters for initially connecting
|
||||
id : '=',
|
||||
connectionParameters : '='
|
||||
/**
|
||||
* The ID of the Guacamole connection to connect to.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
id : '=',
|
||||
|
||||
/**
|
||||
* Arbitrary URL-encoded parameters to append to the connection
|
||||
* string when connecting.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
connectionParameters : '='
|
||||
|
||||
},
|
||||
templateUrl: 'app/client/templates/guacClient.html',
|
||||
controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) {
|
||||
@@ -122,12 +139,13 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
||||
*/
|
||||
var touchPad = new Guacamole.Mouse.Touchpad(displayContainer);
|
||||
|
||||
var $window = $injector.get('$window'),
|
||||
guacAudio = $injector.get('guacAudio'),
|
||||
guacVideo = $injector.get('guacVideo'),
|
||||
guacTunnelFactory = $injector.get('guacTunnelFactory'),
|
||||
guacClientFactory = $injector.get('guacClientFactory'),
|
||||
localStorageUtility = $injector.get('localStorageUtility');
|
||||
var $window = $injector.get('$window'),
|
||||
guacAudio = $injector.get('guacAudio'),
|
||||
guacVideo = $injector.get('guacVideo'),
|
||||
guacHistory = $injector.get('guacHistory'),
|
||||
guacTunnelFactory = $injector.get('guacTunnelFactory'),
|
||||
guacClientFactory = $injector.get('guacClientFactory'),
|
||||
authenticationService = $injector.get('authenticationService');
|
||||
|
||||
/**
|
||||
* Updates the scale of the attached Guacamole.Client based on current window
|
||||
@@ -175,7 +193,7 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
||||
// Build base connect string
|
||||
var connectString =
|
||||
"id=" + encodeURIComponent($scope.id)
|
||||
+ "&authToken=" + encodeURIComponent(localStorageUtility.get('authToken'))
|
||||
+ "&authToken=" + encodeURIComponent(authenticationService.getCurrentToken())
|
||||
+ "&width=" + Math.floor(optimal_width)
|
||||
+ "&height=" + Math.floor(optimal_height)
|
||||
+ "&dpi=" + Math.floor(optimal_dpi)
|
||||
@@ -298,12 +316,37 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
||||
*/
|
||||
|
||||
// Connect to given ID whenever ID changes
|
||||
$scope.$watch('id', function(id) {
|
||||
$scope.$watch('id', function(id, previousID) {
|
||||
|
||||
// If a client is already attached, ensure it is disconnected
|
||||
if (client)
|
||||
client.disconnect();
|
||||
|
||||
// Update stored thumbnail of previous connection
|
||||
if (previousID && display && display.getWidth() > 0 && display.getHeight() > 0) {
|
||||
|
||||
// Get screenshot
|
||||
var canvas = display.flatten();
|
||||
|
||||
// Calculate scale of thumbnail (max 320x240, max zoom 100%)
|
||||
var scale = Math.min(320 / canvas.width, 240 / canvas.height, 1);
|
||||
|
||||
// Create thumbnail canvas
|
||||
var thumbnail = document.createElement("canvas");
|
||||
thumbnail.width = canvas.width*scale;
|
||||
thumbnail.height = canvas.height*scale;
|
||||
|
||||
// Scale screenshot to thumbnail
|
||||
var context = thumbnail.getContext("2d");
|
||||
context.drawImage(canvas,
|
||||
0, 0, canvas.width, canvas.height,
|
||||
0, 0, thumbnail.width, thumbnail.height
|
||||
);
|
||||
|
||||
guacHistory.updateThumbnail(previousID, thumbnail.toDataURL("image/png"));
|
||||
|
||||
}
|
||||
|
||||
// Only proceed if a new client is attached
|
||||
if (!id)
|
||||
return;
|
||||
|
@@ -23,14 +23,20 @@
|
||||
/**
|
||||
* A service for generating new guacClient properties objects.
|
||||
*/
|
||||
angular.module('client').factory('clientProperties', [function clientProperties() {
|
||||
angular.module('client').factory('ClientProperties', [function defineClientProperties() {
|
||||
|
||||
/**
|
||||
* Object used for interacting with a guacClient directive.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ClientProperties|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ClientProperties.
|
||||
*/
|
||||
return function() {
|
||||
var ClientProperties = function ClientProperties(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* Whether the display should be scaled automatically to fit within the
|
||||
@@ -38,7 +44,7 @@ angular.module('client').factory('clientProperties', [function clientProperties(
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.autoFit = true;
|
||||
this.autoFit = template.autoFit || true;
|
||||
|
||||
/**
|
||||
* The current scale. If autoFit is true, the effect of setting this
|
||||
@@ -46,28 +52,28 @@ angular.module('client').factory('clientProperties', [function clientProperties(
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.scale = 1;
|
||||
this.scale = template.scale || 1;
|
||||
|
||||
/**
|
||||
* The minimum scale value.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.minScale = 1;
|
||||
this.minScale = template.minScale || 1;
|
||||
|
||||
/**
|
||||
* The maximum scale value.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.maxScale = 3;
|
||||
this.maxScale = template.maxScale || 3;
|
||||
|
||||
/**
|
||||
* Whether or not the client should listen to keyboard events.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.keyboardEnabled = true;
|
||||
this.keyboardEnabled = template.keyboardEnabled || true;
|
||||
|
||||
/**
|
||||
* Whether translation of touch to mouse events should emulate an
|
||||
@@ -75,8 +81,10 @@ angular.module('client').factory('clientProperties', [function clientProperties(
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.emulateAbsoluteMouse = true;
|
||||
this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || true;
|
||||
|
||||
};
|
||||
|
||||
return ClientProperties;
|
||||
|
||||
}]);
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* The module for code relating to connections.
|
||||
*/
|
||||
angular.module('connection', ['util']);
|
||||
angular.module('connection', ['auth']);
|
||||
|
@@ -23,8 +23,8 @@
|
||||
/**
|
||||
* The DAO for connection operations agains the REST API.
|
||||
*/
|
||||
angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUtility',
|
||||
function connectionDAO($http, localStorageUtility) {
|
||||
angular.module('connection').factory('connectionDAO', ['$http', 'authenticationService',
|
||||
function connectionDAO($http, authenticationService) {
|
||||
|
||||
var service = {};
|
||||
|
||||
@@ -36,7 +36,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
|
||||
* @returns {promise} A promise for the HTTP call.
|
||||
*/
|
||||
service.getConnection = function getConnection(id) {
|
||||
return $http.get("api/connection/" + id + "?token=" + localStorageUtility.get('authToken'));
|
||||
return $http.get("api/connection/" + id + "?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,7 +55,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
|
||||
if(parentID !== undefined)
|
||||
parentIDParam = "&parentID=" + parentID;
|
||||
|
||||
return $http.get("api/connection?token=" + localStorageUtility.get('authToken') + parentIDParam);
|
||||
return $http.get("api/connection?token=" + authenticationService.getCurrentToken() + parentIDParam);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -74,7 +74,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
|
||||
|
||||
// This is a new connection
|
||||
if(!connectionToSave.identifier) {
|
||||
return $http.post("api/connection/?token=" + localStorageUtility.get('authToken'), connectionToSave).success(
|
||||
return $http.post("api/connection/?token=" + authenticationService.getCurrentToken(), connectionToSave).success(
|
||||
function setConnectionID(connectionID){
|
||||
// Set the identifier on the new connection
|
||||
connection.identifier = connectionID;
|
||||
@@ -83,7 +83,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
|
||||
} else {
|
||||
return $http.post(
|
||||
"api/connection/" + connectionToSave.identifier +
|
||||
"?token=" + localStorageUtility.get('authToken'),
|
||||
"?token=" + authenticationService.getCurrentToken(),
|
||||
connectionToSave);
|
||||
}
|
||||
};
|
||||
@@ -100,7 +100,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
|
||||
|
||||
return $http.put(
|
||||
"api/connection/" + connection.identifier +
|
||||
"?token=" + localStorageUtility.get('authToken') +
|
||||
"?token=" + authenticationService.getCurrentToken() +
|
||||
"&parentID=" + connection.parentIdentifier,
|
||||
connection);
|
||||
|
||||
@@ -117,7 +117,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
|
||||
service.deleteConnection = function deleteConnection(connection) {
|
||||
return $http['delete'](
|
||||
"api/connection/" + connection.identifier +
|
||||
"?token=" + localStorageUtility.get('authToken'));
|
||||
"?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
return service;
|
||||
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* The module for code relating to connection groups.
|
||||
*/
|
||||
angular.module('connectionGroup', ['util', 'connection']);
|
||||
angular.module('connectionGroup', ['auth', 'util', 'connection']);
|
||||
|
@@ -23,8 +23,8 @@
|
||||
/**
|
||||
* The DAO for connection group operations agains the REST API.
|
||||
*/
|
||||
angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'localStorageUtility',
|
||||
function connectionGrouDAO($http, localStorageUtility) {
|
||||
angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'authenticationService',
|
||||
function connectionGrouDAO($http, authenticationService) {
|
||||
|
||||
/**
|
||||
* The ID of the root connection group.
|
||||
@@ -49,7 +49,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
|
||||
if(parentID !== undefined)
|
||||
parentIDParam = "&parentID=" + parentID;
|
||||
|
||||
return $http.get("api/connectionGroup?token=" + localStorageUtility.get('authToken') + parentIDParam);
|
||||
return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -67,7 +67,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
|
||||
// Use the root connection group ID if no ID is passed in
|
||||
connectionGroupID = connectionGroupID || ROOT_CONNECTION_GROUP_ID;
|
||||
|
||||
return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + localStorageUtility.get('authToken'));
|
||||
return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -81,7 +81,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
|
||||
service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) {
|
||||
// This is a new connection group
|
||||
if(!connectionGroup.identifier) {
|
||||
return $http.post("api/connectionGroup/?token=" + localStorageUtility.get('authToken'), connectionGroup).success(
|
||||
return $http.post("api/connectionGroup/?token=" + authenticationService.getCurrentToken(), connectionGroup).success(
|
||||
function setConnectionGroupID(connectionGroupID){
|
||||
// Set the identifier on the new connection
|
||||
connectionGroup.identifier = connectionGroupID;
|
||||
@@ -90,7 +90,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
|
||||
} else {
|
||||
return $http.post(
|
||||
"api/connectionGroup/" + connectionGroup.identifier +
|
||||
"?token=" + localStorageUtility.get('authToken'),
|
||||
"?token=" + authenticationService.getCurrentToken(),
|
||||
connectionGroup);
|
||||
}
|
||||
};
|
||||
@@ -107,7 +107,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
|
||||
|
||||
return $http.put(
|
||||
"api/connectionGroup/" + connectionGroup.identifier +
|
||||
"?token=" + localStorageUtility.get('authToken') +
|
||||
"?token=" + authenticationService.getCurrentToken() +
|
||||
"&parentID=" + connectionGroup.parentIdentifier,
|
||||
connectionGroup);
|
||||
};
|
||||
@@ -123,7 +123,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
|
||||
service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) {
|
||||
return $http['delete'](
|
||||
"api/connectionGroup/" + connectionGroup.identifier +
|
||||
"?token=" + localStorageUtility.get('authToken'));
|
||||
"?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
return service;
|
||||
|
26
guacamole/src/main/webapp/app/history/historyModule.js
Normal file
26
guacamole/src/main/webapp/app/history/historyModule.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module for code relating to connection history.
|
||||
*/
|
||||
angular.module('history', []);
|
103
guacamole/src/main/webapp/app/history/services/guacHistory.js
Normal file
103
guacamole/src/main/webapp/app/history/services/guacHistory.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A service for reading and manipulating the Guacamole connection history.
|
||||
*/
|
||||
angular.module('history').factory('guacHistory', ['HistoryEntry', function guacHistory(HistoryEntry) {
|
||||
|
||||
var service = {};
|
||||
|
||||
// The parameter name for getting the history from local storage
|
||||
var GUAC_HISTORY_STORAGE_KEY = "GUAC_HISTORY";
|
||||
|
||||
/**
|
||||
* The number of entries to allow before removing old entries based on the
|
||||
* cutoff.
|
||||
*/
|
||||
var IDEAL_LENGTH = 6;
|
||||
|
||||
/**
|
||||
* The top few recent connections, sorted in order of most recent access.
|
||||
*
|
||||
* @type HistoryEntry[]
|
||||
*/
|
||||
service.recentConnections = [];
|
||||
|
||||
/**
|
||||
* Updates the thumbnail and access time of the history entry for the
|
||||
* connection with the given ID.
|
||||
*
|
||||
* @param {String} id
|
||||
* The ID of the connection whose history entry should be updated.
|
||||
*
|
||||
* @param {String} thumbnail
|
||||
* The URL of the thumbnail image to associate with the history entry.
|
||||
*/
|
||||
service.updateThumbnail = function(id, thumbnail) {
|
||||
|
||||
var i;
|
||||
|
||||
// Remove any existing entry for this connection
|
||||
for (i=0; i < service.recentConnections.length; i++) {
|
||||
if (service.recentConnections[i].id === id) {
|
||||
service.recentConnections.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Store new entry in history
|
||||
service.recentConnections.unshift(new HistoryEntry(
|
||||
id,
|
||||
thumbnail,
|
||||
new Date().getTime()
|
||||
));
|
||||
|
||||
// Truncate history to ideal length
|
||||
if (service.recentConnections.length > IDEAL_LENGTH)
|
||||
service.recentConnections.length = IDEAL_LENGTH;
|
||||
|
||||
// Save updated history, ignore inability to use localStorage
|
||||
try {
|
||||
if (localStorage)
|
||||
localStorage.setItem(GUAC_HISTORY_STORAGE_KEY, JSON.stringify(service.recentConnections));
|
||||
}
|
||||
catch (ignore) {}
|
||||
|
||||
};
|
||||
|
||||
// Get stored connection history, ignore inability to use localStorage
|
||||
try {
|
||||
|
||||
if (localStorage) {
|
||||
var storedHistory = JSON.parse(localStorage.getItem(GUAC_HISTORY_STORAGE_KEY) || "[]");
|
||||
if (storedHistory instanceof Array)
|
||||
service.recentConnections = storedHistory;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (ignore) {}
|
||||
|
||||
return service;
|
||||
|
||||
}]);
|
54
guacamole/src/main/webapp/app/history/types/HistoryEntry.js
Normal file
54
guacamole/src/main/webapp/app/history/types/HistoryEntry.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the HistoryEntry class used by the guacHistory service.
|
||||
*/
|
||||
angular.module('history').factory('HistoryEntry', [function defineHistoryEntry() {
|
||||
|
||||
/**
|
||||
* A single entry in the connection history.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} id The ID of the connection.
|
||||
*
|
||||
* @param {String} thumbnail
|
||||
* The URL of the thumbnail to use to represent the connection.
|
||||
*/
|
||||
var HistoryEntry = function HistoryEntry(id, thumbnail) {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
};
|
||||
|
||||
return HistoryEntry;
|
||||
|
||||
}]);
|
@@ -31,7 +31,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
|
||||
|
||||
// Get the dependencies commonJS style
|
||||
var connectionGroupService = $injector.get("connectionGroupService");
|
||||
var localStorageUtility = $injector.get("localStorageUtility");
|
||||
var guacHistory = $injector.get("guacHistory");
|
||||
|
||||
// All the connections and connection groups in root
|
||||
$scope.connectionsAndGroups = [];
|
||||
@@ -48,14 +48,14 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
|
||||
connectionGroupService.getAllGroupsAndConnections($scope.connectionsAndGroups)
|
||||
.then(function findRecentConnections() {
|
||||
|
||||
// Try to parse out the recent connections from local storage
|
||||
var recentConnections;
|
||||
try {
|
||||
recentConnections = JSON.parse(localStorageUtility.get(GUAC_HISTORY_STORAGE_KEY));
|
||||
} catch(e) {
|
||||
|
||||
// The recent history is corrupted - clear it
|
||||
localStorageUtility.clear(GUAC_HISTORY_STORAGE_KEY);
|
||||
// TODONT: Munch the guacHistory recentConnections list into a legacy-style object
|
||||
var recentConnections = {};
|
||||
for (var i=0; i < guacHistory.recentConnections.length; i++) {
|
||||
var entry = guacHistory.recentConnections[i];
|
||||
recentConnections[encodeURIComponent(entry.id)] = {
|
||||
id : entry.id,
|
||||
thumbnail : entry.thumbnail
|
||||
};
|
||||
}
|
||||
|
||||
// Figure out which recent connection entries are valid
|
||||
|
@@ -20,4 +20,4 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
angular.module('home', ['connection', 'connectionGroup', 'user', 'permission']);
|
||||
angular.module('home', ['connection', 'connectionGroup', 'history', 'user', 'permission']);
|
||||
|
@@ -61,7 +61,7 @@
|
||||
<div ng-repeat="recentConnection in recentConnections" class="connection">
|
||||
<a href="#/client/{{recentConnection.type}}/{{recentConnection.id}}/{{recentConnection.name}}">
|
||||
<div class="thumbnail">
|
||||
<img alt="{{recentConnection.name}}" src="{{recentConnection.thumbnail}}"/>
|
||||
<img alt="{{recentConnection.name}}" ng-src="{{recentConnection.thumbnail}}"/>
|
||||
</div>
|
||||
<div class="caption">
|
||||
<span class="name">{{recentConnection.name}}</span>
|
||||
|
@@ -50,6 +50,21 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The current status notification, or false if no status is currently
|
||||
* shown.
|
||||
*
|
||||
* @type Notification|Boolean
|
||||
*/
|
||||
$scope.status = false;
|
||||
|
||||
/**
|
||||
* All currently-visible notifications.
|
||||
*
|
||||
* @type Notification[]
|
||||
*/
|
||||
$scope.notifications = [];
|
||||
|
||||
// Put some useful variables in the top level scope
|
||||
$scope.page = {
|
||||
title: '',
|
||||
@@ -59,7 +74,6 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
||||
$scope.currentUserIsAdmin = false;
|
||||
$scope.currentUserHasUpdate = false;
|
||||
$scope.currentUserPermissions = null;
|
||||
$scope.notifications = [];
|
||||
var notificationUniqueID = 0;
|
||||
|
||||
// A promise to be fulfilled when all basic user permissions are loaded.
|
||||
@@ -77,41 +91,8 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
||||
* notification is currently shown, no further statuses will be shown
|
||||
* until the current status is hidden.
|
||||
*
|
||||
* @param {Object} status The status notification to show.
|
||||
* @param {String} [status.title] The title of the notification.
|
||||
* @param {String} [status.text] The body text of the notification.
|
||||
* @param {String} [status.className] The CSS class name to apply.
|
||||
*
|
||||
* @param {String} [status.countdown.text]
|
||||
* In the case that a countdown applies to the notification, the text to
|
||||
* display while the countdown is active.
|
||||
*
|
||||
* @param {Function} [status.countdown.callback]
|
||||
* The callback to call when the countdown expires.
|
||||
*
|
||||
* @param {String} [status.countdown.remaining]
|
||||
* The number of seconds remaining before the countdown callback is
|
||||
* called.
|
||||
*
|
||||
* @param {String} [status.progress.text]
|
||||
* If this notification has associated progress, the text to display
|
||||
* while the operation is occurring.
|
||||
*
|
||||
* @param {String} [status.progress.value]
|
||||
* The current state of operation progress, as an arbitrary number
|
||||
* which increases as the operation continues.
|
||||
*
|
||||
* @param {String} [status.progress.unit]
|
||||
* The unit of the arbitrary status.progress.value, if that value has
|
||||
* an associated unit.
|
||||
*
|
||||
* @param {String} [status.progress.ratio]
|
||||
* If known, the current status of the operation as a value between
|
||||
* 0 and 1 inclusive, where 0 is not yet started, and 1 is complete.
|
||||
*
|
||||
* @param {Object[]} [status.actions]
|
||||
* Array of action objects which contain an action name and callback to
|
||||
* be executed when that action is invoked.
|
||||
* @param {Notification|Boolean|Object} status
|
||||
* The status notification to show.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
@@ -138,43 +119,10 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
||||
/**
|
||||
* Adds a notification to the the list of notifications shown.
|
||||
*
|
||||
* @param {Object} notification The notification to add.
|
||||
* @param {String} [notification.title] The title of the notification.
|
||||
* @param {String} [notification.text] The body text of the notification.
|
||||
* @param {String} [notification.className] The CSS class name to apply.
|
||||
* @param {Notification|Object} notification The notification to add.
|
||||
*
|
||||
* @param {String} [notification.countdown.text]
|
||||
* In the case that a countdown applies to the notification, the text to
|
||||
* display while the countdown is active.
|
||||
*
|
||||
* @param {Function} [notification.countdown.callback]
|
||||
* The callback to call when the countdown expires.
|
||||
*
|
||||
* @param {String} [notification.countdown.remaining]
|
||||
* The number of seconds remaining before the countdown callback is
|
||||
* called.
|
||||
*
|
||||
* @param {String} [notification.progress.text]
|
||||
* If this notification has associated progress, the text to display
|
||||
* while the operation is occurring.
|
||||
*
|
||||
* @param {String} [notification.progress.value]
|
||||
* The current state of operation progress, as an arbitrary number
|
||||
* which increases as the operation continues.
|
||||
*
|
||||
* @param {String} [notification.progress.unit]
|
||||
* The unit of the arbitrary notification.progress.value, if that value
|
||||
* has an associated unit.
|
||||
*
|
||||
* @param {String} [notification.progress.ratio]
|
||||
* If known, the current status of the operation as a value between
|
||||
* 0 and 1 inclusive, where 0 is not yet started, and 1 is complete.
|
||||
*
|
||||
* @param {Object[]} [notification.actions]
|
||||
* Array of action objects which contain an action name and callback to
|
||||
* be executed when that action is invoked.
|
||||
*
|
||||
* @returns {Number} A unique ID for the notification that's just been added.
|
||||
* @returns {Number}
|
||||
* A unique ID for the notification that's just been added.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
|
@@ -31,110 +31,11 @@ angular.module('notification').directive('guacNotification', [function guacNotif
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The CSS class to apply to the notification.
|
||||
* The notification to display.
|
||||
*
|
||||
* @type String
|
||||
* @type Notification|Object
|
||||
*/
|
||||
className : '=',
|
||||
|
||||
/**
|
||||
* The title of the notification.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
title : '=',
|
||||
|
||||
/**
|
||||
* The body text of the notification.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
text : '=',
|
||||
|
||||
/**
|
||||
* The text to use for displaying the countdown. For the sake of
|
||||
* i18n, the variable REMAINING will be applied within the
|
||||
* translation string for formatting plurals, etc.
|
||||
*
|
||||
* @type String
|
||||
* @example
|
||||
* "Only {REMAINING} {REMAINING, plural, one{second} other{seconds}} remain."
|
||||
*/
|
||||
countdownText : '=',
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before automatically calling the
|
||||
* default callback.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
countdown : '=',
|
||||
|
||||
/**
|
||||
* The function to call when timeRemaining expires. If timeRemaining
|
||||
* is not set, this does not apply.
|
||||
*
|
||||
* @type Function
|
||||
*/
|
||||
defaultCallback : '=',
|
||||
|
||||
/**
|
||||
* The text to use for displaying the progress. For the sake of
|
||||
* i18n, the variable PROGRESS will be applied within the
|
||||
* translation string for formatting plurals, etc., while the
|
||||
* variable UNIT will be applied, if needed, for whatever units
|
||||
* are applicable to the progress display.
|
||||
*
|
||||
* @type String
|
||||
* @example
|
||||
* "{PROGRESS} {UNIT, select, b{B} kb{KB}} uploaded."
|
||||
*/
|
||||
progressText : '=',
|
||||
|
||||
/**
|
||||
* The unit which applies to the progress indicator, if any. This
|
||||
* will be substituted in the progressText string with the UNIT
|
||||
* variable.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
progressUnit : '=',
|
||||
|
||||
/**
|
||||
* Arbitrary value denoting how much progress has been made
|
||||
* in some ongoing task that this notification represents.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
progress : '=',
|
||||
|
||||
/**
|
||||
* Value between 0 and 1 denoting what proportion of the operation
|
||||
* has completed. If known, this value should be 0 if the operation
|
||||
* has not started, and 1 if the operation is complete.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
progressRatio : '=',
|
||||
|
||||
/**
|
||||
* Array of name/callback pairs for each action the user is allowed
|
||||
* to take once the notification is shown.
|
||||
*
|
||||
* @type Array
|
||||
* @example
|
||||
* [
|
||||
* {
|
||||
* name : "Action 1 name",
|
||||
* callback : actionCallback1
|
||||
* },
|
||||
* {
|
||||
* name : "Action 2 text",
|
||||
* callback : actionCallback2
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
actions : '='
|
||||
notification : '='
|
||||
|
||||
},
|
||||
|
||||
@@ -142,21 +43,22 @@ angular.module('notification').directive('guacNotification', [function guacNotif
|
||||
controller: ['$scope', '$interval', function guacNotificationController($scope, $interval) {
|
||||
|
||||
// Update progress bar if end known
|
||||
$scope.$watch("progressRatio", function updateProgress(ratio) {
|
||||
$scope.$watch("notification.progress.ratio", function updateProgress(ratio) {
|
||||
$scope.progressPercent = ratio * 100;
|
||||
});
|
||||
|
||||
// Set countdown interval when associated property is set
|
||||
$scope.$watch("countdown", function resetTimeRemaining(countdown) {
|
||||
$scope.$watch("notification", function resetTimeRemaining(notification) {
|
||||
|
||||
$scope.timeRemaining = countdown;
|
||||
var countdown = notification.countdown;
|
||||
|
||||
// Clean up any existing interval
|
||||
if ($scope.interval)
|
||||
$interval.cancel($scope.interval);
|
||||
|
||||
// Update and handle countdown, if provided
|
||||
if ($scope.timeRemaining) {
|
||||
if (countdown) {
|
||||
|
||||
$scope.timeRemaining = countdown.remaining;
|
||||
|
||||
$scope.interval = $interval(function updateTimeRemaining() {
|
||||
|
||||
@@ -164,8 +66,8 @@ angular.module('notification').directive('guacNotification', [function guacNotif
|
||||
$scope.timeRemaining--;
|
||||
|
||||
// Call countdown callback when time remaining expires
|
||||
if ($scope.timeRemaining === 0 && $scope.defaultCallback)
|
||||
$scope.defaultCallback();
|
||||
if ($scope.timeRemaining === 0)
|
||||
countdown.performAction();
|
||||
|
||||
}, 1000, $scope.timeRemaining);
|
||||
|
||||
@@ -184,4 +86,4 @@ angular.module('notification').directive('guacNotification', [function guacNotif
|
||||
}]
|
||||
|
||||
};
|
||||
}]);
|
||||
}]);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="notification" ng-class="className">
|
||||
<div class="notification" ng-class="notification.className">
|
||||
<!--
|
||||
Copyright (C) 2014 Glyptodon LLC
|
||||
|
||||
@@ -22,26 +22,26 @@
|
||||
-->
|
||||
|
||||
<!-- Notification title -->
|
||||
<div ng-show="title" class="title-bar">
|
||||
<div class="title">{{title | translate}}</div>
|
||||
<div ng-show="notification.title" class="title-bar">
|
||||
<div class="title">{{notification.title | translate}}</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<!-- Notification text -->
|
||||
<p ng-show="text" class="text">{{text | translate}}</p>
|
||||
<p ng-show="notification.text" class="text">{{notification.text | translate}}</p>
|
||||
|
||||
<!-- Current progress -->
|
||||
<div ng-show="progressText" class="progress"><div ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}" class="bar"></div><div class="text">{{progressText | translate:"{ PROGRESS: progress, UNIT: progressUnit }"}}</div></div>
|
||||
<div ng-show="notification.progress" class="progress"><div ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}" class="bar"></div><div ng-show="notification.progress.text" class="text">{{notification.progress.text | translate:"{ PROGRESS: notification.progress.value, UNIT: notification.progress.unit}"}}</div></div>
|
||||
|
||||
<!-- Default action countdown text -->
|
||||
<p ng-show="countdownText" class="countdown-text">{{countdownText | translate:"{ REMAINING: timeRemaining }"}}</p>
|
||||
<p ng-show="notification.countdown.text" class="countdown-text">{{notification.countdown.text | translate:"{ REMAINING: timeRemaining}"}}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div ng-show="actions && actions.length" class="buttons">
|
||||
<button ng-repeat="action in actions" ng-click="action.callback()">{{action.name | translate}}</button>
|
||||
<div ng-show="notification.actions.length" class="buttons">
|
||||
<button ng-repeat="action in notification.actions" ng-click="action.callback()">{{action.name | translate}}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the Notification class definition.
|
||||
*/
|
||||
angular.module('notification').factory('Notification', [function defineNotification() {
|
||||
|
||||
/**
|
||||
* Creates a new Notification, initializing the properties of that
|
||||
* Notification with the corresponding properties of the given template.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Notification|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* Notification.
|
||||
*/
|
||||
var Notification = function Notification(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The CSS class to associate with the notification, if any.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.className = template.className;
|
||||
|
||||
/**
|
||||
* The title of the notification.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.title = template.title;
|
||||
|
||||
/**
|
||||
* The body text of the notification.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.text = template.text;
|
||||
|
||||
/**
|
||||
* An array of all actions available to the user in response to this
|
||||
* notification.
|
||||
*
|
||||
* @type NotificationAction[]
|
||||
*/
|
||||
this.actions = template.actions || [];
|
||||
|
||||
/**
|
||||
* The current progress state of the ongoing action associated with this
|
||||
* notification.
|
||||
*
|
||||
* @type NotificationProgress
|
||||
*/
|
||||
this.progress = template.progress;
|
||||
|
||||
/**
|
||||
* The countdown and corresponding default action which applies to
|
||||
* this notification, if any.
|
||||
*
|
||||
* @type NotificationCountdown
|
||||
*/
|
||||
this.countdown = template.countdown;
|
||||
|
||||
};
|
||||
|
||||
return Notification;
|
||||
|
||||
}]);
|
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the NotificationAction class definition.
|
||||
*/
|
||||
angular.module('notification').factory('NotificationAction', [function defineNotificationAction() {
|
||||
|
||||
/**
|
||||
* Creates a new NotificationAction, which pairs an arbitrary callback with
|
||||
* an action name. The name of this action will ultimately be presented to
|
||||
* the user when the user is prompted to choose among available actions.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} name The name of this action.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* The callback to call when the user elects to perform this action.
|
||||
*/
|
||||
var NotificationAction = function NotificationAction(name, callback) {
|
||||
|
||||
/**
|
||||
* Reference to this NotificationAction.
|
||||
*
|
||||
* @type NotificationAction
|
||||
*/
|
||||
var action = this;
|
||||
|
||||
/**
|
||||
* The name of this action.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* The callback to call when this action is performed.
|
||||
*
|
||||
* @type Function
|
||||
*/
|
||||
this.callback = callback;
|
||||
|
||||
/**
|
||||
* Calls the callback associated with this NotificationAction, if any.
|
||||
* If no callback is associated with this NotificationAction, this
|
||||
* function has no effect.
|
||||
*/
|
||||
this.performAction = function performAction() {
|
||||
if (action.callback)
|
||||
action.callback();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return NotificationAction;
|
||||
|
||||
}]);
|
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the NotificationCountdown class definition.
|
||||
*/
|
||||
angular.module('notification').factory('NotificationCountdown', [function defineNotificationCountdown() {
|
||||
|
||||
/**
|
||||
* Creates a new NotificationCountdown which describes an action that
|
||||
* should be performed after a specific number of seconds has elapsed.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} text The body text of the notification countdown.
|
||||
*
|
||||
* @param {Number} remaining
|
||||
* The number of seconds remaining in the countdown.
|
||||
*
|
||||
* @param {Function} [callback]
|
||||
* The callback to call when the countdown elapses.
|
||||
*/
|
||||
var NotificationCountdown = function NotificationCountdown(text, remaining, callback) {
|
||||
|
||||
/**
|
||||
* Reference to this NotificationCountdown.
|
||||
*
|
||||
* @type NotificationCountdown
|
||||
*/
|
||||
var countdown = this;
|
||||
|
||||
/**
|
||||
* The body text of the notification countdown. For the sake of i18n,
|
||||
* the variable REMAINING should be applied within the translation
|
||||
* string for formatting plurals, etc.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.text = text;
|
||||
|
||||
/**
|
||||
* The number of seconds remaining in the countdown. After this number
|
||||
* of seconds elapses, the callback associated with this
|
||||
* NotificationCountdown will be called.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.remaining = remaining;
|
||||
|
||||
/**
|
||||
* The callback to call when this countdown expires.
|
||||
*
|
||||
* @type Function
|
||||
*/
|
||||
this.callback = callback;
|
||||
|
||||
/**
|
||||
* Calls the callback associated with this NotificationCountdown, if any.
|
||||
* If no callback is associated with this NotificationCountdown, this
|
||||
* function has no effect.
|
||||
*/
|
||||
this.performAction = function performAction() {
|
||||
if (countdown.callback)
|
||||
countdown.callback();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return NotificationCountdown;
|
||||
|
||||
}]);
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the NotificationProgress class definition.
|
||||
*/
|
||||
angular.module('notification').factory('NotificationProgress', [function defineNotificationProgress() {
|
||||
|
||||
/**
|
||||
* Creates a new NotificationProgress which describes the current status
|
||||
* of an operation, and how much of that operation remains to be performed.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} text The text describing the operation progress.
|
||||
*
|
||||
* @param {Number} value
|
||||
* The current state of operation progress, as an arbitrary number
|
||||
* which increases as the operation continues.
|
||||
*
|
||||
* @param {String} [unit]
|
||||
* The unit of the arbitrary value, if that value has an associated
|
||||
* unit.
|
||||
*
|
||||
* @param {Number} [ratio]
|
||||
* If known, the current status of the operation as a value between 0
|
||||
* and 1 inclusive, where 0 is not yet started, and 1 is complete.
|
||||
*/
|
||||
var NotificationProgress = function NotificationProgress(text, value, unit, ratio) {
|
||||
|
||||
/**
|
||||
* The text describing the operation progress. For the sake of i18n,
|
||||
* the variable PROGRESS should be applied within the translation
|
||||
* string for formatting plurals, etc., while UNIT should be used
|
||||
* for the progress unit, if any.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.text = text;
|
||||
|
||||
/**
|
||||
* The current state of operation progress, as an arbitrary number which
|
||||
* increases as the operation continues.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/**
|
||||
* The unit of the arbitrary value, if that value has an associated
|
||||
* unit.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.unit = unit;
|
||||
|
||||
/**
|
||||
* If known, the current status of the operation as a value between 0
|
||||
* and 1 inclusive, where 0 is not yet started, and 1 is complete.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.ratio = ratio;
|
||||
|
||||
};
|
||||
|
||||
return NotificationProgress;
|
||||
|
||||
}]);
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* A module for code relating to permissions.
|
||||
*/
|
||||
angular.module('permission', []);
|
||||
angular.module('permission', ['auth']);
|
||||
|
@@ -23,8 +23,8 @@
|
||||
/**
|
||||
* The DAO for permission operations agains the REST API.
|
||||
*/
|
||||
angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUtility',
|
||||
function permissionDAO($http, localStorageUtility) {
|
||||
angular.module('permission').factory('permissionDAO', ['$http', 'authenticationService',
|
||||
function permissionDAO($http, authenticationService) {
|
||||
|
||||
var service = {};
|
||||
|
||||
@@ -37,7 +37,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
|
||||
* @returns {promise} A promise for the HTTP call.
|
||||
*/
|
||||
service.getPermissions = function getPermissions(userID) {
|
||||
return $http.get("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken'));
|
||||
return $http.get("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
|
||||
* @returns {promise} A promise for the HTTP call.
|
||||
*/
|
||||
service.addPermission = function addPermission(userID, permission) {
|
||||
return $http.post("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken'), permission);
|
||||
return $http.post("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission);
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
|
||||
* @returns {promise} A promise for the HTTP call.
|
||||
*/
|
||||
service.removePermission = function removePermission(userID, permission) {
|
||||
return $http.post("api/permission/remove/" + userID + "/?token=" + localStorageUtility.get('authToken'), permission);
|
||||
return $http.post("api/permission/remove/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission);
|
||||
};
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
|
||||
// Make the HTTP call
|
||||
return $http({
|
||||
method : 'PATCH',
|
||||
url : "api/permission/?token=" + localStorageUtility.get('authToken'),
|
||||
url : "api/permission/?token=" + authenticationService.getCurrentToken(),
|
||||
data : permissionPatch
|
||||
});
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@
|
||||
/**
|
||||
* The DAO for connection operations agains the REST API.
|
||||
*/
|
||||
angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
|
||||
function userDAO($http, localStorageUtility) {
|
||||
angular.module('user').factory('userDAO', ['$http', 'authenticationService',
|
||||
function userDAO($http, authenticationService) {
|
||||
|
||||
var service = {};
|
||||
|
||||
@@ -35,7 +35,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
|
||||
* @returns {promise} A promise for the HTTP call.
|
||||
*/
|
||||
service.getUsers = function getUsers() {
|
||||
return $http.get("api/user?token=" + localStorageUtility.get('authToken'));
|
||||
return $http.get("api/user?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
|
||||
* @returns {promise} A promise for the HTTP call.
|
||||
*/
|
||||
service.getUser = function getUser(userID) {
|
||||
return $http.get("api/user/" + userID + "/?token=" + localStorageUtility.get('authToken'));
|
||||
return $http.get("api/user/" + userID + "/?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,7 +61,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
|
||||
service.deleteUser = function deleteUser(user) {
|
||||
return $http['delete'](
|
||||
"api/user/" + user.username +
|
||||
"?token=" + localStorageUtility.get('authToken'));
|
||||
"?token=" + authenticationService.getCurrentToken());
|
||||
};
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
|
||||
service.createUser = function createUser(user) {
|
||||
return $http.post(
|
||||
"api/user/"
|
||||
+ "?token=" + localStorageUtility.get('authToken'),
|
||||
+ "?token=" + authenticationService.getCurrentToken(),
|
||||
user
|
||||
);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
|
||||
service.saveUser = function saveUser(user) {
|
||||
return $http.post(
|
||||
"api/user/" + user.username +
|
||||
"?token=" + localStorageUtility.get('authToken'),
|
||||
"?token=" + authenticationService.getCurrentToken(),
|
||||
user);
|
||||
};
|
||||
|
||||
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* A module for code relating to users.
|
||||
*/
|
||||
angular.module('user', []);
|
||||
angular.module('user', ['auth']);
|
||||
|
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A service for handling storage and retrieval of values on localStorage.
|
||||
* If local storage is not available, cookies will be used as a fallback.
|
||||
*/
|
||||
angular.module('util').factory('localStorageUtility', ['$cookieStore',
|
||||
function localStorageUtility($cookieStore) {
|
||||
|
||||
var service = {};
|
||||
|
||||
// The prefix to use when storing cookies
|
||||
var COOKIE_PREFIX = "guacamole.ui.localstorage.";
|
||||
|
||||
// Check if we can actually use localStorage
|
||||
var localStorageEnabled;
|
||||
try {
|
||||
window.localStorage.setItem("test", "test");
|
||||
window.localStorage.removeItem("test");
|
||||
localStorageEnabled = true;
|
||||
} catch(e) {
|
||||
localStorageEnabled = false;
|
||||
}
|
||||
|
||||
var getFunc, setFunc;
|
||||
|
||||
if(localStorageEnabled) {
|
||||
|
||||
// Just a passthrough to localStorage
|
||||
getFunc = function getFromLocalStorage(key) {
|
||||
return window.localStorage.getItem(key);
|
||||
};
|
||||
|
||||
setFunc = function setOnLocalStorage(key, value) {
|
||||
return window.localStorage.setItem(key, value);
|
||||
};
|
||||
}
|
||||
else {
|
||||
|
||||
// Store the values as cookies
|
||||
getFunc = function getValueFromCookie(key) {
|
||||
return $cookieStore.get(COOKIE_PREFIX + key);
|
||||
};
|
||||
|
||||
setFunc = function setValueOnCookie(key, value) {
|
||||
return $cookieStore.put(COOKIE_PREFIX + key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the persistent local store.
|
||||
*
|
||||
* @param {string} key The key to use as an index into the map.
|
||||
*
|
||||
* @returns {string} The value, if found.
|
||||
*/
|
||||
service.get = getFunc;
|
||||
|
||||
/**
|
||||
* Sets a value on the persistent local store.
|
||||
*
|
||||
* @param {string} key The key to use as an index into the map.
|
||||
* @param {string} value The value to store in the map.
|
||||
*/
|
||||
service.set = setFunc;
|
||||
|
||||
/**
|
||||
* Clear a value from the persistent local store.
|
||||
*
|
||||
* @param {string} key The key to clear from the map.
|
||||
*/
|
||||
service.clear = function clear(key) {
|
||||
return service.set(key, undefined);
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
@@ -23,4 +23,4 @@
|
||||
/**
|
||||
* A module for miscellaneous services and utilities that don't belong elsewhere.
|
||||
*/
|
||||
angular.module('util', ['ngCookies']);
|
||||
angular.module('util', []);
|
||||
|
@@ -37,20 +37,7 @@ THE SOFTWARE.
|
||||
<!-- Global status/error dialog -->
|
||||
<div ng-class="{shown: status}" class="status-outer">
|
||||
<div class="status-middle">
|
||||
|
||||
<guac-notification
|
||||
class-name="status.className"
|
||||
title="status.title"
|
||||
text="status.text"
|
||||
progress-text="status.progress.text"
|
||||
progress-unit="status.progress.unit"
|
||||
progress-ratio="status.progress.ratio"
|
||||
progress="status.progress.value"
|
||||
actions="status.actions"
|
||||
countdown-text="status.countdown.text"
|
||||
countdown="status.countdown.remaining"
|
||||
default-callback="status.countdown.callback"/>
|
||||
|
||||
<guac-notification notification="status"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,20 +47,7 @@ THE SOFTWARE.
|
||||
<!-- Notification area -->
|
||||
<div id="notificationArea">
|
||||
<div ng-repeat="wrapper in notifications">
|
||||
|
||||
<guac-notification
|
||||
class-name="wrapper.notification.className"
|
||||
title="wrapper.notification.title"
|
||||
text="wrapper.notification.text"
|
||||
progress-text="wrapper.notification.progress.text"
|
||||
progress-unit="wrapper.notification.progress.unit"
|
||||
progress-ratio="wrapper.notification.progress.ratio"
|
||||
progress="wrapper.notification.progress.value"
|
||||
actions="wrapper.notification.actions"
|
||||
countdown-text="wrapper.notification.countdown.text"
|
||||
countdown="wrapper.notification.countdown.remaining"
|
||||
default-callback="wrapper.notification.countdown.callback"/>
|
||||
|
||||
<guac-notification notification="wrapper.notification"/>
|
||||
<div>
|
||||
</div>
|
||||
|
||||
|
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
};
|
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Global storage for Guacamole pages.
|
||||
*/
|
||||
GuacamoleSessionStorage = (function() {
|
||||
|
||||
// Retrieve storage from owner of window, if possible
|
||||
var opener_storage = null;
|
||||
try {
|
||||
opener_storage = opener && opener.GuacamoleSessionStorage;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
return opener_storage;
|
||||
|
||||
})() || new (function() {
|
||||
|
||||
/**
|
||||
* The contents of storage, as a JSON string containing name/value pairs as
|
||||
* properties.
|
||||
*
|
||||
* @private
|
||||
* @type String
|
||||
*/
|
||||
var stored_json = "{}";
|
||||
|
||||
/**
|
||||
* Called whenever an item value changes.
|
||||
*
|
||||
* @callback onchange
|
||||
* @param {String} name The name of the item changed.
|
||||
* @param value The new item value.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All attached listeners.
|
||||
*
|
||||
* @type onchange[]
|
||||
*/
|
||||
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_json = localStorage.getItem("GUACAMOLE_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, notify
|
||||
var old = old_state[name];
|
||||
if (old !== new_state[name])
|
||||
__notify_changed(name, new_state[name]);
|
||||
|
||||
}
|
||||
|
||||
stored_json = new_json;
|
||||
|
||||
}, false);
|
||||
|
||||
/**
|
||||
* 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 {onchange} onchange The function to call when an item changes.
|
||||
*/
|
||||
this.addChangeListener = function(onchange) {
|
||||
listeners.push(onchange);
|
||||
};
|
||||
|
||||
})();
|
Reference in New Issue
Block a user