GUAC-919: Copy Angular changes from old GUAC-546 branch.

This commit is contained in:
James Muehlner
2014-11-03 12:51:17 -08:00
committed by Michael Jumper
parent ac2617b92a
commit 5c43ae4ff9
84 changed files with 16551 additions and 7476 deletions

View File

@@ -63,6 +63,10 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId> <artifactId>maven-war-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<phase>process-resources</phase>
<configuration> <configuration>
<!-- Filter webapp dir --> <!-- Filter webapp dir -->
@@ -83,6 +87,78 @@
</overlays> </overlays>
</configuration> </configuration>
<goals>
<goal>exploded</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- JS/CSS Minification Plugin -->
<plugin>
<groupId>com.samaxes.maven</groupId>
<artifactId>minify-maven-plugin</artifactId>
<version>1.6.1</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<charset>UTF-8</charset>
<webappSourceDir>${project.build.directory}/${project.build.finalName}</webappSourceDir>
<cssSourceDir>/</cssSourceDir>
<cssTargetDir>/</cssTargetDir>
<cssFinalFile>guacamole.css</cssFinalFile>
<cssSourceFiles>
<cssSourceFile>license.txt</cssSourceFile>
</cssSourceFiles>
<cssSourceIncludes>
<cssSourceInclude>app/**/*.css</cssSourceInclude>
<cssSourceInclude>styles/**/*.css</cssSourceInclude>
</cssSourceIncludes>
<jsSourceDir>/</jsSourceDir>
<jsTargetDir>/</jsTargetDir>
<jsFinalFile>guacamole.js</jsFinalFile>
<jsSourceFiles>
<jsSourceFile>lib/jquery.js</jsSourceFile>
<jsSourceFile>lib/lodash.js</jsSourceFile>
<jsSourceFile>lib/angular.js</jsSourceFile>
<jsSourceFile>lib/angular-module-shim.js</jsSourceFile>
<jsSourceFile>lib/plugins/angular-cookies.min.js</jsSourceFile>
<jsSourceFile>lib/plugins/angular-route.js</jsSourceFile>
<jsSourceFile>lib/plugins/angular-translate.js</jsSourceFile>
<jsSourceFile>lib/plugins/angular-translate-loader-static-files.js</jsSourceFile>
<jsSourceFile>lib/plugins/modal.js</jsSourceFile>
<jsSourceFile>lib/blob/blob.js</jsSourceFile>
<jsSourceFile>lib/filesaver/filesaver.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>
<jsSourceInclude>app/**/*.js</jsSourceInclude>
</jsSourceIncludes>
<!-- Do not minify and include tests -->
<jsSourceExcludes>
<jsSourceExclude>**/*.test.js</jsSourceExclude>
</jsSourceExcludes>
<jsEngine>CLOSURE</jsEngine>
<skipMinify>true</skipMinify>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -28,7 +28,7 @@
<!-- Basic config --> <!-- Basic config -->
<welcome-file-list> <welcome-file-list>
<welcome-file>index.xhtml</welcome-file> <welcome-file>index.html</welcome-file>
</welcome-file-list> </welcome-file-list>
<!-- Guice --> <!-- Guice -->

View File

@@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<!--
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
<link rel="apple-touch-icon" type="image/png" href="images/guacamole-logo-144.png"/>
<link rel="stylesheet" type="text/css" href="styles/ui.css"/>
<link rel="stylesheet" type="text/css" href="styles/admin.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi"/>
<title>Guacamole ${project.version}</title>
</head>
<body>
<div id="logout-panel">
<button id="back">Back</button>
<button id="logout">Logout</button>
</div>
<h2>Administration</h2>
<div class="settings section">
<h3 class="require-manage-users">Users</h3>
<div class="require-manage-users" id="users">
<p>
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.
</p>
<div id="user-add-form"><input type="text" class="name" id="username" placeholder="Username"/><button id="add-user">Add User</button></div>
<div id="user-list">
</div>
<div id="user-list-buttons">
</div>
</div>
<h3 class="require-manage-connections">Connections</h3>
<div class="require-manage-connections" id="connections">
<p>
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.
</p>
<div id="connection-add-form"><button id="add-connection">New Connection</button><button id="add-connection-group">New Group</button></div>
<div id="connection-list">
</div>
<div id="connection-list-buttons">
</div>
</div>
</div>
<!-- guacamole-common-js -->
<script type="text/javascript" src="guacamole-common-js/all.min.js"></script>
<script type="text/javascript" src="scripts/session.js"></script>
<script type="text/javascript" src="scripts/guac-ui.js"></script>
<script type="text/javascript" src="scripts/service.js"></script>
<script type="text/javascript" src="scripts/history.js"></script>
<script type="text/javascript" src="scripts/admin-ui.js"></script>
</body>
</html>

View 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 used to connect to a connection or balancing group.
*/
angular.module('client', []);

View File

@@ -0,0 +1,132 @@
/*
* 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.
*/
/*
* In order to open the guacamole menu, we need to hit ctrl-alt-shift. There are
* several possible keysysms for each key.
*/
var SHIFT_KEYS = {0xFFE1 : true, 0xFFE2: true},
ALT_KEYS = {0xFFE9 : true, 0xFFEA : true, 0xFE03: true},
CTRL_KEYS = {0xFFE3 : true, 0xFFE4: true},
MENU_KEYS = angular.extend({}, SHIFT_KEYS, ALT_KEYS, CTRL_KEYS);
/**
* The controller for the page used to connect to a connection or balancing group.
*/
angular.module('home').controller('clientController', ['$scope', '$routeParams', 'localStorageUtility', '$injector',
function clientController($scope, $routeParams, localStorageUtility, $injector) {
// Get DAO for reading connections and groups
var connectionGroupDAO = $injector.get('connectionGroupDAO');
var connectionDAO = $injector.get('connectionDAO');
// Client settings and state
$scope.clientParameters = {scale: 1};
// Hide menu by default
$scope.menuShown = false;
$scope.menuHasBeenShown = false;
/*
* Parse the type, name, and id out of the url paramteres,
* as well as any extra parameters if set.
*/
$scope.type = $routeParams.type;
$scope.id = $routeParams.id;
$scope.connectionParameters = $routeParams.params || '';
// Keep title in sync with connection state
$scope.$watch('connectionName', function updateTitle() {
$scope.page.title = $scope.connectionName;
});
// Pull connection name from server
switch ($scope.type) {
// Connection
case 'c':
connectionDAO.getConnection($scope.id).success(function (connection) {
$scope.connectionName = connection.name;
});
break;
// Connection group
case 'g':
connectionGroupDAO.getConnectionGroup($scope.id).success(function (group) {
$scope.connectionName = group.name;
});
break;
}
var keysCurrentlyPressed = {};
/*
* Check to see if all currently pressed keys are in the set of menu keys.
*/
function checkMenuModeActive() {
for(var keysym in keysCurrentlyPressed) {
if(!MENU_KEYS[keysym]) {
return false;
}
}
return true;
}
$scope.$on('guacKeydown', function keydownListener(event, keysym, keyboard) {
keysCurrentlyPressed[keysym] = true;
});
// Listen for broadcasted keyup events and fire the appropriate listeners
$scope.$on('guacKeyup', function keyupListener(event, keysym, keyboard) {
/*
* If only menu keys are pressed, and we have one keysym from each group,
* and one of the keys is being released, show the menu.
*/
if(checkMenuModeActive()) {
var currentKeysPressedKeys = Object.keys(keysCurrentlyPressed);
// Check that there is a key pressed for each of the required key classes
if(!_.isEmpty(_.pick(SHIFT_KEYS, currentKeysPressedKeys)) &&
!_.isEmpty(_.pick(ALT_KEYS, currentKeysPressedKeys)) &&
!_.isEmpty(_.pick(CTRL_KEYS, currentKeysPressedKeys))
) {
// Toggle the menu
$scope.safeApply(function() {
$scope.menuShown = !$scope.menuShown;
// The menu has been shown at least once before
$scope.menuHasBeenShown = true;
});
// Reset the keys pressed
keysCurrentlyPressed = {};
}
}
delete keysCurrentlyPressed[keysym];
});
}]);

View File

@@ -0,0 +1,670 @@
/*
* 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 directive for the guacamole client.
*/
angular.module('client').directive('guacClient', [function guacClient() {
return {
// Element only
restrict: 'E',
replace: true,
scope: {
// Parameters for controlling client state
clientParameters : '=',
// Parameters for initially connecting
id : '=',
type : '=',
connectionName : '=',
connectionParameters : '='
},
templateUrl: 'app/client/templates/guacClient.html',
controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) {
var $window = $injector.get('$window'),
guacAudio = $injector.get('guacAudio'),
guacVideo = $injector.get('guacVideo'),
localStorageUtility = $injector.get('localStorageUtility');
var authToken = localStorageUtility.get('authToken'),
uniqueId = encodeURIComponent($scope.type + '/' + $scope.id);
// Get elements for DOM manipulation
$scope.main = $element[0];
// Settings and constants
$.extend(true, $scope, {
/**
* All error codes for which automatic reconnection is appropriate when a
* tunnel error occurs.
*/
"tunnel_auto_reconnect": {
0x0200: true,
0x0202: true,
0x0203: true,
0x0308: true
},
/**
* All error codes for which automatic reconnection is appropriate when a
* client error occurs.
*/
"client_auto_reconnect": {
0x0200: true,
0x0202: true,
0x0203: true,
0x0301: true,
0x0308: true
},
/* Constants */
"KEYBOARD_AUTO_RESIZE_INTERVAL" : 30, /* milliseconds */
"RECONNECT_PERIOD" : 15, /* seconds */
"TEXT_INPUT_PADDING" : 128, /* characters */
"TEXT_INPUT_PADDING_CODEPOINT" : 0x200B,
/* Settings for zoom */
"min_zoom" : 1,
"max_zoom" : 3,
/* Current connection parameters */
/* The user defined named for this connection */
"connectionName" : "Guacamole",
/* The attached client instance */
"attachedClient" : null,
/* Mouse emulation */
"emulate_absolute" : true,
"touch" : null,
"touch_screen" : null,
"touch_pad" : null,
/* Clipboard */
"remote_clipboard" : "",
"clipboard_integration_enabled" : undefined
});
/**
* Updates the scale of the attached Guacamole.Client based on current window
* size and "auto-fit" setting.
*/
$scope.updateDisplayScale = function() {
var guac = $scope.attachedClient;
if (!guac)
return;
// Determine whether display is currently fit to the screen
var auto_fit = (guac.getDisplay().getScale() === $scope.min_zoom);
// Calculate scale to fit screen
$scope.min_zoom = Math.min(
$scope.main.offsetWidth / Math.max(guac.getDisplay().getWidth(), 1),
$scope.main.offsetHeight / Math.max(guac.getDisplay().getHeight(), 1)
);
// Calculate appropriate maximum zoom level
$scope.max_zoom = Math.max($scope.min_zoom, 3);
// Clamp zoom level, maintain auto-fit
if (guac.getDisplay().getScale() < $scope.min_zoom || auto_fit)
$scope.setScale($scope.min_zoom);
else if (guac.getDisplay().getScale() > $scope.max_zoom)
$scope.setScale($scope.max_zoom);
};
/**
* Attaches a Guacamole.Client to the client UI, such that Guacamole events
* affect the UI, and local events affect the Guacamole.Client. If a client
* is already attached, it is replaced.
*
* @param {Guacamole.Client} guac The Guacamole.Client to attach to the UI.
*/
$scope.attach = function(guac) {
// If a client is already attached, ensure it is disconnected
if ($scope.attachedClient)
$scope.attachedClient.disconnect();
// Store attached client
$scope.attachedClient = guac;
// Get display element
var guac_display = guac.getDisplay().getElement();
/*
* Update the scale of the display when the client display size changes.
*/
guac.getDisplay().onresize = function() {
$scope.updateDisplayScale();
};
/*
* Update UI when the state of the Guacamole.Client changes.
*/
guac.onstatechange = function(clientState) {
switch (clientState) {
// Idle
case 0:
$scope.$emit('guacClientStatusChange', guac, "idle");
break;
// Connecting
case 1:
$scope.$emit('guacClientStatusChange', guac, "connecting");
break;
// Connected + waiting
case 2:
$scope.$emit('guacClientStatusChange', guac, "waiting");
break;
// Connected
case 3:
$scope.$emit('guacClientStatusChange', guac, null);
// Update server clipboard with current data
var clipboard = localStorageUtility.get("clipboard");
if (clipboard)
guac.setClipboard(clipboard);
break;
// Disconnecting / disconnected are handled by tunnel instead
case 4:
case 5:
break;
// Unknown status code
default:
$scope.$emit('guacClientError', guac, "unknown");
}
};
// Listen for clipboard events not sent by the client
$scope.$on('guacClipboard', function onClipboardChange(event, data) {
// Update server clipboard with current data
$scope.guac.setClipboard(data);
});
/*
* Emit a name change event
*/
guac.onname = function(name) {
$scope.connectionDisplayName = name;
$scope.$emit('name', guac, name);
};
/*
* Disconnect and emits an error when the client receives an error
*/
guac.onerror = function(status) {
// Disconnect, if connected
guac.disconnect();
$scope.$emit('guacClientError', guac, status.code, {operations: {reconnect: function reconnect () {
$scope.connect();
}}});
};
// Server copy handler
guac.onclipboard = function(stream, mimetype) {
// Only text/plain is supported for now
if (mimetype !== "text/plain") {
stream.sendAck("Only text/plain supported", Guacamole.Status.Code.UNSUPPORTED);
return;
}
var reader = new Guacamole.StringReader(stream);
var data = "";
// Append any received data to buffer
reader.ontext = function clipboard_text_received(text) {
data += text;
stream.sendAck("Received", Guacamole.Status.Code.SUCCESS);
};
// Emit event when done
reader.onend = function clipboard_text_end() {
$scope.$emit('guacClientClipboard', guac, data);
};
};
/*
* Prompt to download file when file received.
*/
guac.onfile = function onfile(stream, mimetype, filename) {
// Begin file download
var guacFileStartEvent = $scope.$emit('guacFileStart', guac, stream.index, mimetype, filename);
if (!guacFileStartEvent.defaultPrevented) {
var blob_reader = new Guacamole.BlobReader(stream, mimetype);
// Update progress as data is received
blob_reader.onprogress = function onprogress() {
$scope.$emit('guacFileProgress', guac, stream.index, mimetype, filename);
stream.sendAck("Received", Guacamole.Status.Code.SUCCESS);
};
// When complete, prompt for download
blob_reader.onend = function onend() {
$scope.$emit('guacFileEnd', guac, stream.index, mimetype, filename);
};
stream.sendAck("Ready", Guacamole.Status.Code.SUCCESS);
}
// Respond with UNSUPPORTED if download (default action) canceled within event handler
else
stream.sendAck("Download canceled", Guacamole.Status.Code.UNSUPPORTED);
};
/*
* Do nothing when the display element is clicked on.
*/
guac_display.onclick = function(e) {
e.preventDefault();
return false;
};
/*
* Handle mouse and touch events relative to the display element.
*/
// Touchscreen
var touch_screen = new Guacamole.Mouse.Touchscreen(guac_display);
$scope.touch_screen = touch_screen;
// Touchpad
var touch_pad = new Guacamole.Mouse.Touchpad(guac_display);
$scope.touch_pad = touch_pad;
// Mouse
var mouse = new Guacamole.Mouse(guac_display);
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = function(mouseState) {
// Scale event by current scale
var scaledState = new Guacamole.Mouse.State(
mouseState.x / guac.getDisplay().getScale(),
mouseState.y / guac.getDisplay().getScale(),
mouseState.left,
mouseState.middle,
mouseState.right,
mouseState.up,
mouseState.down);
// Send mouse event
guac.sendMouseState(scaledState);
};
// Hide any existing status notifications
$scope.$emit('guacClientStatusChange', guac, null);
var $display = $element.find('.display');
// Remove old client from UI, if any
$display.html("");
// Add client to UI
guac.getDisplay().getElement().className = "software-cursor";
$display.append(guac.getDisplay().getElement());
};
// Watch for changes to mouse emulation mode
$scope.$watch('parameters.emulateAbsolute', function(emulateAbsolute) {
$scope.setMouseEmulationAbsolute(emulateAbsolute);
});
/**
* Sets the mouse emulation mode to absolute or relative.
*
* @param {Boolean} absolute Whether mouse emulation should use absolute
* (touchscreen) mode.
*/
$scope.setMouseEmulationAbsolute = function(absolute) {
function __handle_mouse_state(mouseState) {
// Get client - do nothing if not attached
var guac = $scope.attachedClient;
if (!guac) return;
// Determine mouse position within view
var guac_display = guac.getDisplay().getElement();
var mouse_view_x = mouseState.x + guac_display.offsetLeft - $scope.main.scrollLeft;
var mouse_view_y = mouseState.y + guac_display.offsetTop - $scope.main.scrollTop;
// Determine viewport dimensioins
var view_width = $scope.main.offsetWidth;
var view_height = $scope.main.offsetHeight;
// Determine scroll amounts based on mouse position relative to document
var scroll_amount_x;
if (mouse_view_x > view_width)
scroll_amount_x = mouse_view_x - view_width;
else if (mouse_view_x < 0)
scroll_amount_x = mouse_view_x;
else
scroll_amount_x = 0;
var scroll_amount_y;
if (mouse_view_y > view_height)
scroll_amount_y = mouse_view_y - view_height;
else if (mouse_view_y < 0)
scroll_amount_y = mouse_view_y;
else
scroll_amount_y = 0;
// Scroll (if necessary) to keep mouse on screen.
$scope.main.scrollLeft += scroll_amount_x;
$scope.main.scrollTop += scroll_amount_y;
// Scale event by current scale
var scaledState = new Guacamole.Mouse.State(
mouseState.x / guac.getDisplay().getScale(),
mouseState.y / guac.getDisplay().getScale(),
mouseState.left,
mouseState.middle,
mouseState.right,
mouseState.up,
mouseState.down);
// Send mouse event
guac.sendMouseState(scaledState);
};
var new_mode, old_mode;
$scope.emulate_absolute = absolute;
// Switch to touchscreen if absolute
if (absolute) {
new_mode = $scope.touch_screen;
old_mode = $scope.touch;
}
// Switch to touchpad if not absolute (relative)
else {
new_mode = $scope.touch_pad;
old_mode = $scope.touch;
}
// Perform switch
if (new_mode) {
if (old_mode) {
old_mode.onmousedown = old_mode.onmouseup = old_mode.onmousemove = null;
new_mode.currentState.x = old_mode.currentState.x;
new_mode.currentState.y = old_mode.currentState.y;
}
new_mode.onmousedown = new_mode.onmouseup = new_mode.onmousemove = __handle_mouse_state;
$scope.touch = new_mode;
}
};
/**
* Connects to the current Guacamole connection, attaching a new Guacamole
* client to the user interface. If a Guacamole client is already attached,
* it is replaced.
*/
$scope.connect = function connect() {
// If WebSocket available, try to use it.
if ($window.WebSocket)
$scope.tunnel = new Guacamole.ChainedTunnel(
new Guacamole.WebSocketTunnel("websocket-tunnel"),
new Guacamole.HTTPTunnel("tunnel")
);
// If no WebSocket, then use HTTP.
else
$scope.tunnel = new Guacamole.HTTPTunnel("tunnel");
// Instantiate client
$scope.guac = new Guacamole.Client($scope.tunnel);
// Tie UI to client
$scope.attach($scope.guac);
// Calculate optimal width/height for display
var pixel_density = $window.devicePixelRatio || 1;
var optimal_dpi = pixel_density * 96;
var optimal_width = $window.innerWidth * pixel_density;
var optimal_height = $window.innerHeight * pixel_density;
// Scale width/height to be at least 600x600
if (optimal_width < 600 || optimal_height < 600) {
var scale = Math.max(600 / optimal_width, 600 / optimal_height);
optimal_width = optimal_width * scale;
optimal_height = optimal_height * scale;
}
// Get entire query string, and pass to connect().
// Normally, only the "id" parameter is required, but
// all parameters should be preserved and passed on for
// the sake of authentication.
var connectString =
"id=" + uniqueId + ($scope.connectionParameters ? '&' + $scope.connectionParameters : '')
+ "&authToken="+ authToken
+ "&width=" + Math.floor(optimal_width)
+ "&height=" + Math.floor(optimal_height)
+ "&dpi=" + Math.floor(optimal_dpi);
// Add audio mimetypes to connect_string
guacAudio.supported.forEach(function(mimetype) {
connectString += "&audio=" + encodeURIComponent(mimetype);
});
// Add video mimetypes to connect_string
guacVideo.supported.forEach(function(mimetype) {
connectString += "&video=" + encodeURIComponent(mimetype);
});
// Show connection errors from tunnel
$scope.tunnel.onerror = function onerror(status) {
//FIXME: Needs to auto reconnect - should that be here, or in the error handler further up?
$scope.$emit('guacTunnelError', $scope.guac, status.code);
};
// Notify of disconnections (if not already notified of something else)
$scope.tunnel.onstatechange = function onstatechange(state) {
if (state === Guacamole.Tunnel.State.CLOSED) {
$scope.$emit('guacTunnelError', $scope.guac, "disconnected", state);
}
};
// Connect
$scope.guac.connect(connectString);
};
/**
* Sets the current display scale to the given value, where 1 is 100% (1:1
* pixel ratio). Out-of-range values will be clamped in-range.
*
* @param {Number} scale The new scale to apply
*/
$scope.setScale = function setScale(scale) {
scale = Math.max(scale, $scope.min_zoom);
scale = Math.min(scale, $scope.max_zoom);
if ($scope.attachedClient)
$scope.attachedClient.getDisplay().scale(scale);
return scale;
};
// Adjust scale if modified externally
$scope.$watch('clientParameters.scale', function changeScale(scale) {
$scope.setScale(scale);
checkScale();
});
// Verify that the scale is within acceptable bounds, and adjust if needed
function checkScale() {
// If at minimum zoom level, auto fit is ON
if ($scope.scale === $scope.min_zoom) {
$scope.main.style.overflow = "hidden";
$scope.autoFitEnabled = true;
}
// If at minimum zoom level, auto fit is OFF
else {
$scope.main.style.overflow = "auto";
$scope.autoFitEnabled = false;
}
}
var show_keyboard_gesture_possible = true;
// Handle Keyboard events
function __send_key(pressed, keysym) {
$scope.attachedClient.sendKeyEvent(pressed, keysym);
return false;
}
$scope.keydown = function keydown (keysym, keyboard) {
// Only handle key events if client is attached
var guac = $scope.attachedClient;
if (!guac) return true;
// Handle Ctrl-shortcuts specifically
if (keyboard.modifiers.ctrl && !keyboard.modifiers.alt && !keyboard.modifiers.shift) {
// Allow event through if Ctrl+C or Ctrl+X
if (keyboard.pressed[0x63] || keyboard.pressed[0x78]) {
__send_key(1, keysym);
return true;
}
// If Ctrl+V, wait until after paste event (next event loop)
if (keyboard.pressed[0x76]) {
window.setTimeout(function after_paste() {
__send_key(1, keysym);
}, 10);
return true;
}
}
// If key is NOT one of the expected keys, gesture not possible
if (keysym !== 0xFFE3 && keysym !== 0xFFE9 && keysym !== 0xFFE1)
show_keyboard_gesture_possible = false;
// Send key event
return __send_key(1, keysym);
};
$scope.keyup = function keyup(keysym, keyboard) {
// Only handle key events if client is attached
var guac = $scope.attachedClient;
if (!guac) return true;
// If lifting up on shift, toggle menu visibility if rest of gesture
// conditions satisfied
if (show_keyboard_gesture_possible && keysym === 0xFFE1
&& keyboard.pressed[0xFFE3] && keyboard.pressed[0xFFE9]) {
__send_key(0, 0xFFE1);
__send_key(0, 0xFFE9);
__send_key(0, 0xFFE3);
// Emit an event to show the menu
$scope.$emit('guacClientMenu', true);
}
// Detect if no keys are pressed
var reset_gesture = true;
for (var pressed in keyboard.pressed) {
reset_gesture = false;
break;
}
// Reset gesture state if possible
if (reset_gesture)
show_keyboard_gesture_possible = true;
// Send key event
return __send_key(0, keysym);
};
// Listen for broadcasted keydown events and fire the appropriate listeners
$scope.$on('guacKeydown', function keydownListener(event, keysym, keyboard) {
var preventDefault = $scope.keydown(keysym, keyboard);
if(preventDefault) {
event.preventDefault();
}
});
// Listen for broadcasted keyup events and fire the appropriate listeners
$scope.$on('guacKeyup', function keyupListener(event, keysym, keyboard) {
var preventDefault = $scope.keyup(keysym, keyboard);
if(preventDefault) {
event.preventDefault();
}
});
// Connect!
$scope.connect();
}]
};
}]);

View File

@@ -0,0 +1,79 @@
/*
* 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 checking browser audio support.
*/
angular.module('client').factory('guacAudio', [function guacAudio() {
/**
* Object describing the UI's level of audio support.
*/
return new (function() {
var codecs = [
'audio/ogg; codecs="vorbis"',
'audio/mp4; codecs="mp4a.40.5"',
'audio/mpeg; codecs="mp3"',
'audio/webm; codecs="vorbis"',
'audio/wav; codecs=1'
];
var probably_supported = [];
var maybe_supported = [];
/**
* Array of all supported audio mimetypes, ordered by liklihood of
* working.
*/
this.supported = [];
// Build array of supported audio formats
codecs.forEach(function(mimetype) {
var audio = new Audio();
var support_level = audio.canPlayType(mimetype);
// Trim semicolon and trailer
var semicolon = mimetype.indexOf(";");
if (semicolon != -1)
mimetype = mimetype.substring(0, semicolon);
// Partition by probably/maybe
if (support_level == "probably")
probably_supported.push(mimetype);
else if (support_level == "maybe")
maybe_supported.push(mimetype);
});
// Add probably supported types first
Array.prototype.push.apply(
this.supported, probably_supported);
// Prioritize "maybe" supported types second
Array.prototype.push.apply(
this.supported, maybe_supported);
})();
}]);

View File

@@ -0,0 +1,77 @@
/*
* 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 checking browser video support.
*/
angular.module('client').factory('guacVideo', [function guacVideo() {
/**
* Object describing the UI's level of video support.
*/
return new (function() {
var codecs = [
'video/ogg; codecs="theora, vorbis"',
'video/mp4; codecs="avc1.4D401E, mp4a.40.5"',
'video/webm; codecs="vp8.0, vorbis"'
];
var probably_supported = [];
var maybe_supported = [];
/**
* Array of all supported video mimetypes, ordered by liklihood of
* working.
*/
this.supported = [];
// Build array of supported audio formats
codecs.forEach(function(mimetype) {
var video = document.createElement("video");
var support_level = video.canPlayType(mimetype);
// Trim semicolon and trailer
var semicolon = mimetype.indexOf(";");
if (semicolon != -1)
mimetype = mimetype.substring(0, semicolon);
// Partition by probably/maybe
if (support_level == "probably")
probably_supported.push(mimetype);
else if (support_level == "maybe")
maybe_supported.push(mimetype);
});
// Add probably supported types first
Array.prototype.push.apply(
this.supported, probably_supported);
// Prioritize "maybe" supported types second
Array.prototype.push.apply(
this.supported, maybe_supported);
})();
}]);

View File

@@ -0,0 +1,482 @@
/*
* 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.
*/
body {
background: black;
font-family: FreeSans, Helvetica, Arial, sans-serif;
padding: 0;
margin: 0;
}
img {
border: none;
}
.software-cursor {
cursor: url('images/mouse/blank.gif'),url('images/mouse/blank.cur'),default;
overflow: hidden;
cursor: none;
}
.guac-error .software-cursor {
cursor: default;
}
* {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.event-target {
position: fixed;
opacity: 0;
}
/* Dialogs */
div.dialogOuter {
display: table;
height: 100%;
width: 100%;
position: fixed;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.75);
}
div.dialogMiddle {
width: 100%;
text-align: center;
display: table-cell;
vertical-align: middle;
}
button {
border-style: solid;
border-width: 1px;
padding: 0.25em;
padding-right: 1em;
padding-left: 1em;
}
button:active {
padding-top: 0.35em;
padding-left: 1.1em;
padding-bottom: 0.15em;
padding-right: 0.9em;
}
button#reconnect {
display: none;
}
.guac-error button#reconnect {
display: inline;
background: #200;
border-color: #822;
color: #944;
}
.guac-error button#reconnect:hover {
background: #822;
border-color: #B33;
color: black;
}
div.dialog p {
margin: 0;
}
div.displayOuter {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
display: table;
}
div.displayMiddle {
width: 100%;
display: table-cell;
vertical-align: middle;
text-align: center;
}
div.display * {
position: relative;
}
div.display > * {
margin-left: auto;
margin-right: auto;
}
div.magnifier-background {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
}
div.magnifier {
position: absolute;
left: 0;
top: 0;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.75);
width: 50%;
height: 50%;
overflow: hidden;
}
.pan-overlay,
.type-overlay {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.pan-overlay .indicator {
position: fixed;
background-size: 32px 32px;
-moz-background-size: 32px 32px;
-webkit-background-size: 32px 32px;
-khtml-background-size: 32px 32px;
background-position: center;
background-repeat: no-repeat;
opacity: 0.8;
}
.pan-overlay .indicator.up {
top: 0;
left: 0;
right: 0;
height: 32px;
background-image: url('images/arrows/arrows-u.png');
}
.pan-overlay .indicator.down {
bottom: 0;
left: 0;
right: 0;
height: 32px;
background-image: url('images/arrows/arrows-d.png');
}
.pan-overlay .indicator.left {
top: 0;
bottom: 0;
left: 0;
width: 32px;
background-image: url('images/arrows/arrows-l.png');
}
.pan-overlay .indicator.right {
top: 0;
bottom: 0;
right: 0;
width: 32px;
background-image: url('images/arrows/arrows-r.png');
}
/* Viewport Clone */
div#viewportClone {
display: table;
height: 100%;
width: 100%;
position: fixed;
left: 0;
top: 0;
visibility: hidden;
}
@keyframes show-dialog {
0% {transform: scale(0.75); }
100% {transform: scale(1); }
}
@-webkit-keyframes show-dialog {
0% {-webkit-transform: scale(0.75); }
100% {-webkit-transform: scale(1); }
}
.dialog {
animation-name: show-dialog;
animation-timing-function: linear;
animation-duration: 0.125s;
-webkit-animation-name: show-dialog;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 0.125s;
max-width: 75%;
max-height: none;
width: 4in;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
padding: 0.5em;
text-align: left;
}
.guac-error .dialog {
background: #FDD;
border: 1px solid #964040;
}
.dialog .title {
font-size: 1.1em;
font-weight: bold;
border-bottom: 1px solid black;
margin-bottom: 0.5em;
}
.dialog .status {
padding: 0.5em;
font-size: 0.8em;
}
p.hint {
border: 0.25em solid rgba(255, 255, 255, 0.25);
background: black;
opacity: 0.75;
color: white;
max-width: 10em;
padding: 1em;
margin: 1em;
position: absolute;
left: 0;
top: 0;
box-shadow: 0.25em 0.25em 0.25em rgba(0, 0, 0, 0.75);
}
#notificationArea {
position: fixed;
right: 0.5em;
bottom: 0.5em;
max-width: 25%;
min-width: 10em;
}
.notification {
font-size: 0.7em;
text-align: center;
border: 1px solid rgba(0, 0, 0, 0.75);
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
background: white;
color: black;
padding: 0.5em;
margin: 1em;
overflow: hidden;
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.25);
}
.notification div {
display: inline-block;
text-align: left;
}
.notification .title-bar {
display: block;
white-space: nowrap;
font-weight: bold;
border-bottom: 1px solid black;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
.notification .title-bar * {
vertical-align: middle;
}
.notification .close {
background: url('images/action-icons/guac-close.png');
background-size: 10px 10px;
-moz-background-size: 10px 10px;
-webkit-background-size: 10px 10px;
-khtml-background-size: 10px 10px;
width: 10px;
height: 10px;
float: right;
cursor: pointer;
}
@keyframes progress {
from {background-position: 0px 0px;}
to {background-position: 64px 0px;}
}
@-webkit-keyframes progress {
from {background-position: 0px 0px;}
to {background-position: 64px 0px;}
}
.notification .caption,
.download.notification .caption {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.upload.notification .status,
.download.notification .status {
color: red;
font-size: 1em;
padding: 1em;
}
.download.notification .progress,
.upload.notification .progress,
.download.notification .download {
margin-top: 1em;
margin-left: 0.75em;
padding: 0.25em;
min-width: 5em;
border: 1px solid gray;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
-khtml-border-radius: 0.2em;
border-radius: 0.2em;
text-align: center;
float: right;
position: relative;
}
.upload.notification .progress {
float: none;
width: 80%;
margin-left: auto;
margin-right: auto;
}
.download.notification .progress div,
.upload.notification .progress div {
position: relative;
}
.download.notification .progress .bar,
.upload.notification .progress .bar {
background: #A3D655;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0;
box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.5),
inset -1px -1px 0 rgba( 0, 0, 0, 0.1),
1px 1px 0 gray;
}
.upload.notification .progress,
.download.notification .progress {
background: #C2C2C2 url('images/progress.png');
background-size: 16px 16px;
-moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px;
-khtml-background-size: 16px 16px;
animation-name: progress;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
-webkit-animation-name: progress;
-webkit-animation-duration: 2s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
}
.download.notification .download {
background: rgb(16, 87, 153);
cursor: pointer;
}
#preload {
visibility: hidden;
position: absolute;
left: 0;
right: 0;
width: 0;
height: 0;
overflow: hidden;
}

View File

@@ -0,0 +1,120 @@
<!--
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.
-->
<div id="clientContainer">
<!-- Client -->
<guac-client
client-parameters="clientParameters"
type="type"
id="id"
connection-name="connectionName"
connection-parameters="connectionParameters"
></guac-client>
<!-- Text input target -->
<div id="text-input"><div id="text-input-field"><div id="sent-history"></div><textarea rows="1" id="target"></textarea></div><div id="text-input-buttons"><button class="key" data-keysym="0xFFE3" data-sticky="true">{{'client.ctrl' | translate}}</button><button class="key" data-keysym="0xFFE9" data-sticky="true">{{'client.alt' | translate}}</button><button class="key" data-keysym="0xFF1B">{{'client.esc' | translate}}</button><button class="key" data-keysym="0xFF09">{{'client.tab' | translate}}</button></div></div>
<!-- Dimensional clone of viewport -->
<div id="viewportClone"/>
<!-- Notification area -->
<div id="notificationArea"/>
<!-- Menu -->
<div ng-class="{closed: menuHasBeenShown && !menuShown, open: menuShown}" id="menu">
<h2 id="menu-title">Guacamole ${project.version}</h2>
<h3>{{'client.clipboard' | translate}}</h3>
<div class="content" id="clipboard-settings">
<p class="description"></p>
<textarea rows="10" cols="40" id="clipboard">{{'client.copiedText' | translate}}</textarea>
</div>
<h3></h3>
<div class="content" id="keyboard-settings">
<!-- No IME -->
<div class="choice">
<label><input name="input-method" type="radio" value="ime-none" checked="checked" id="ime-none"/> {{'client.none' | translate}}</label>
<p class="caption"><label for="ime-none">{{'client.noneDesc' | translate}}</label></p>
</div>
<!-- Text input -->
<div class="choice">
<div class="figure"><label for="ime-text"><img src="images/settings/tablet-keys.png" alt=""/></label></div>
<label><input name="input-method" type="radio" value="ime-text" id="ime-text"/> {{'client.textInput' | translate}}</label>
<p class="caption"><label for="ime-text">{{'client.textInputDesc' | translate}} </label></p>
</div>
<!-- Guac OSK -->
<div class="choice">
<label><input name="input-method" type="radio" value="ime-osk" id="ime-osk"/> {{'client.osk' | translate}}</label>
<p class="caption"><label for="ime-osk">{{'client.oskDesc' | translate}}</label></p>
</div>
</div>
<h3>{{'client.mouseMode' | translate}}</h3>
<div class="content" id="mouse-settings">
<p class="description">{{'client.mouseModeDesc' | translate}}</p>
<!-- Touchscreen -->
<div class="choice">
<input name="mouse-mode" type="radio" value="absolute" checked="checked" id="absolute"/>
<div class="figure">
<label for="absolute"><img src="images/settings/touchscreen.png" alt="{{'client.touchscreen' | translate}}"/></label>
<p class="caption"><label for="absolute">{{'client.touchscreenDesc' | translate}}</label></p>
</div>
</div>
<!-- Touchpad -->
<div class="choice">
<input name="mouse-mode" type="radio" value="relative" id="relative"/>
<div class="figure">
<label for="relative"><img src="images/settings/touchpad.png" alt="{{'client.touchpad' | translate}}"/></label>
<p class="caption"><label for="relative">{{'client.touchpadDesc' | translate}}</label></p>
</div>
</div>
</div>
<h3>{{'client.display' | translate}}</h3>
<div class="content">
<div id="zoom-settings">
<div ng-click="zoomOut()" id="zoom-out"><img src="images/settings/zoom-out.png" alt="-"/></div>
<div id="zoom-state">{{formattedScale()}}</div>
<div ng-click="zoomIn()" id="zoom-in"><img src="images/settings/zoom-in.png" alt="+"/></div>
</div>
<div><label><input ng-model="autoFitEnabled" ng-change="autoFit(autoFitEnabled)" type="checkbox" id="auto-fit" checked="checked"/> {{'client.autoFit' | translate}}</label></div>
</div>
</div>
<!-- Images which should be preloaded -->
<div id="preload">
<img src="images/action-icons/guac-close.png"/>
<img src="images/progress.png"/>
</div>
<ng-include src="app/client/template/clientError.html"/>
</div>

View File

@@ -0,0 +1,35 @@
<!--
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.
-->
<div ng-show="errorPresent" class="dialogOuter guac-error">
<div class="dialogMiddle">
<div class="dialog">
<p class="title">{{'client.error.connectionErrorTitle' | translate}}</p>
<p class="status">{{errorStatus}}</p>
<div class="reconnect">
<button ng-click="reconnect()">{{'client.error.reconnect' | translate}}</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div class="main">
<!--
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.
-->
<!-- Display -->
<div class="displayOuter">
<div class="displayMiddle">
<div class="display">
</div>
</div>
</div>
</div>

View 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 connections.
*/
angular.module('connection', ['util']);

View File

@@ -0,0 +1,124 @@
/*
* 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 DAO for connection operations agains the REST API.
*/
angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUtility',
function connectionDAO($http, localStorageUtility) {
var service = {};
/**
* Makes a request to the REST API to get a single connection, returning a
* promise that can be used for processing the results of the call.
*
* @param {string} id The ID of the connection.
* @returns {promise} A promise for the HTTP call.
*/
service.getConnection = function getConnection(id) {
return $http.get("api/connection/" + id + "?token=" + localStorageUtility.get('authToken'));
};
/**
* Makes a request to the REST API to get the list of connections,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} parentID The parent ID for the connection.
* If not passed in, it will query a list of the
* connections in the root group.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getConnections = function getConnections(parentID) {
var parentIDParam = "";
if(parentID !== undefined)
parentIDParam = "&parentID=" + parentID;
return $http.get("api/connection?token=" + localStorageUtility.get('authToken') + parentIDParam);
};
/**
* Makes a request to the REST API to save a connection,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} connection The connection to update
*
* @returns {promise} A promise for the HTTP call.
*/
service.saveConnection = function saveConnection(connection) {
// Do not try to save the connection history records
var connectionToSave = angular.copy(connection);
delete connectionToSave.history;
// This is a new connection
if(!connectionToSave.identifier) {
return $http.post("api/connection/?token=" + localStorageUtility.get('authToken'), connectionToSave).success(
function setConnectionID(connectionID){
// Set the identifier on the new connection
connection.identifier = connectionID;
return connectionID;
});
} else {
return $http.post(
"api/connection/" + connectionToSave.identifier +
"?token=" + localStorageUtility.get('authToken'),
connectionToSave);
}
};
/**
* Makes a request to the REST API to move a connection to a different group,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} connection The connection to move.
*
* @returns {promise} A promise for the HTTP call.
*/
service.moveConnection = function moveConnection(connection) {
return $http.put(
"api/connection/" + connection.identifier +
"?token=" + localStorageUtility.get('authToken') +
"&parentID=" + connection.parentIdentifier,
connection);
};
/**
* Makes a request to the REST API to delete a connection,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} connection The connection to delete
*
* @returns {promise} A promise for the HTTP call.
*/
service.deleteConnection = function deleteConnection(connection) {
return $http['delete'](
"api/connection/" + connection.identifier +
"?token=" + localStorageUtility.get('authToken'));
};
return service;
}]);

View 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 groups.
*/
angular.module('connectionGroup', ['util', 'connection']);

View File

@@ -0,0 +1,130 @@
/*
* 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 DAO for connection group operations agains the REST API.
*/
angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'localStorageUtility',
function connectionGrouDAO($http, localStorageUtility) {
/**
* The ID of the root connection group.
*/
var ROOT_CONNECTION_GROUP_ID = "ROOT";
var service = {};
/**
* Makes a request to the REST API to get the list of connection groups,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} parentID The parent ID for the connection group.
* If not passed in, it will query a list of the
* connection groups in the root group.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getConnectionGroups = function getConnectionGroups(parentID) {
var parentIDParam = "";
if(parentID !== undefined)
parentIDParam = "&parentID=" + parentID;
return $http.get("api/connectionGroup?token=" + localStorageUtility.get('authToken') + parentIDParam);
};
/**
* Makes a request to the REST API to get an individual connection group,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} connectionGroupID The ID for the connection group.
* If not passed in, it will query the
* root connection group.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getConnectionGroup = function getConnectionGroup(connectionGroupID) {
// 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'));
};
/**
* Makes a request to the REST API to save a connection group,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} connectionGroup The connection group to update
*
* @returns {promise} A promise for the HTTP call.
*/
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(
function setConnectionGroupID(connectionGroupID){
// Set the identifier on the new connection
connectionGroup.identifier = connectionGroupID;
return connectionGroupID;
});
} else {
return $http.post(
"api/connectionGroup/" + connectionGroup.identifier +
"?token=" + localStorageUtility.get('authToken'),
connectionGroup);
}
};
/**
* Makes a request to the REST API to move a connection group to a different group,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} connectionGroup The connection group to move.
*
* @returns {promise} A promise for the HTTP call.
*/
service.moveConnectionGroup = function moveConnectionGroup(connectionGroup) {
return $http.put(
"api/connectionGroup/" + connectionGroup.identifier +
"?token=" + localStorageUtility.get('authToken') +
"&parentID=" + connectionGroup.parentIdentifier,
connectionGroup);
};
/**
* Makes a request to the REST API to delete a connection group,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} connectionGroup The connection group to delete
*
* @returns {promise} A promise for the HTTP call.
*/
service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) {
return $http['delete'](
"api/connectionGroup/" + connectionGroup.identifier +
"?token=" + localStorageUtility.get('authToken'));
};
return service;
}]);

View File

@@ -0,0 +1,231 @@
/*
* 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 performing useful connection group related functionaltiy.
*/
angular.module('connectionGroup').factory('connectionGroupService', ['$injector', function connectionGroupService($injector) {
var connectionGroupDAO = $injector.get('connectionGroupDAO');
var connectionDAO = $injector.get('connectionDAO');
var permissionCheckService = $injector.get('permissionCheckService');
var $q = $injector.get('$q');
var displayObjectPreparationService = $injector.get('displayObjectPreparationService');
var service = {};
// Add all groups from this group to the parent group child list
function addToParent(connectionGroup, parentGroup, context, includeConnections) {
// Include connections by default
if(typeof includeConnections === 'undefined')
includeConnections = true;
parentGroup.children.push(connectionGroup);
// Prepare this group for display
displayObjectPreparationService.prepareConnectionGroup(connectionGroup);
if(includeConnections) {
// Get all connections in the group and add them under this connection group
context.openRequest();
connectionDAO.getConnections(connectionGroup.identifier).success(function fetchConnections(connections) {
for(var i = 0; i < connections.length; i++) {
connections[i].isConnection = true;
connectionGroup.children.push(connections[i]);
}
context.closeRequest();
});
}
// Get all connection groups in the group and repeat
context.openRequest();
connectionGroupDAO.getConnectionGroups(connectionGroup.identifier).success(function fetchConnectionGroups(connectionGroups) {
for(var i = 0; i < connectionGroups.length; i++) {
addToParent(connectionGroups[i], connectionGroup, context, includeConnections);
}
context.closeRequest();
});
}
/**
* Queries all connections and connection groups under the connection group
* with the provided parent ID, and returns them in a heirarchical structure
* with convinient display properties set on the objects.
*
* @param {array} items The root list of connections and groups. Should be an
* initally empty array that will get filled in as the
* connections and groups are loaded.
*
* @param {string} parentID The parent ID for the connection group.
* If not passed in, it will begin with
* the root connection group.
*
* @param {boolean} includeConnections Whether or not to include connections
* in the structure. Defaults to true.
*
* @param {boolean} includeRoot Whether or not to include the root connection group
* in the structure. Defaults to false.
*
* @return {promise} A promise that will be fulfilled when all connections
* and groups have been loaded.
*/
service.getAllGroupsAndConnections = function getAllGroupsAndConnections(items, parentID, includeConnections, includeRoot) {
// Include connections by default
if(typeof includeConnections === 'undefined')
includeConnections = true;
var context = {
// The number of requets to the server currently open
openRequests : 0,
// Create the promise
finishedFetching : $q.defer(),
// Notify the caller that the promise has been completed
complete : function complete() {
this.finishedFetching.resolve(items);
},
/**
* Indicate that a request has been started.
*/
openRequest : function openRequest() {
this.openRequests++;
},
/**
* Indicate that a request has been completed. If this was the last
* open request, fulfill the promise.
*/
closeRequest : function closeRequest() {
if(--this.openRequests === 0)
this.complete();
}
};
// Include the root only if it was asked for
if(includeRoot) {
context.openRequest();
connectionGroupDAO.getConnectionGroup(parentID).success(function setRootGroup (rootGroup) {
items.push(rootGroup);
rootGroup.children = [];
getChildrenOfRootGroup(rootGroup.children);
context.closeRequest();
});
} else {
getChildrenOfRootGroup(items);
}
// Get the children of the root group
function getChildrenOfRootGroup(children) {
context.openRequest();
connectionGroupDAO.getConnectionGroups(parentID).success(function fetchRootConnectionGroups(connectionGroups) {
for(var i = 0; i < connectionGroups.length; i++) {
addToParent(connectionGroups[i], {children: children}, context, includeConnections);
}
if(includeConnections) {
// Get all connections in the root group and add them under this connection group
context.openRequest();
connectionDAO.getConnections().success(function fetchRootConnections(connections) {
for(var i = 0; i < connections.length; i++) {
// Prepare this connection for display
displayObjectPreparationService.prepareConnection(connections[i]);
children.push(connections[i]);
}
context.closeRequest();
});
}
context.closeRequest();
});
}
// Return the promise
return context.finishedFetching.promise;
};
/**
* Filters the list of connections and groups using the provided permissions.
*
* @param {array} items The heirarchical list of groups and connections.
*
* @param {object} permissionList The list of permissions to use
* when filtering.
*
* @param {object} permissionCriteria A map of object type to permission type(s)
* required for that object type.
*
* @return {array} The filtered list.
*/
service.filterConnectionsAndGroupByPermission = function filterConnectionsAndGroupByPermission(items, permissionList, permissionCriteria) {
var requiredConnectionPermission = permissionCriteria.CONNECTION;
var requiredConnectionGroupPermission = permissionCriteria.CONNECTION_GROUP;
for(var i = 0; i < items.length; i++) {
var item = items[i];
if(item.isConnection && requiredConnectionPermission) {
/*
* If item is a connection and a permission is required for this
* item, check now to see if the permission exists. If not,
* remove the item.
*/
if(!permissionCheckService.checkPermission(permissionList,
"CONNECTION", item.identifier, requiredConnectionPermission)) {
items.splice(i, 1);
continue;
}
}
else {
/*
* If item is a group and a permission is required for this
* item, check now to see if the permission exists. If not,
* remove the item.
*/
if(requiredConnectionGroupPermission) {
if(!permissionCheckService.checkPermission(permissionList,
"CONNECTION_GROUP", item.identifier, requiredConnectionGroupPermission)) {
items.splice(i, 1);
continue;
}
}
// Filter the children of this connection group as well
if(item.children && item.children.length)
service.filterConnectionsAndGroupByPermission(items.children);
}
}
return items;
};
return service;
}]);

View File

@@ -0,0 +1,90 @@
/*
* 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 controller for the home page.
*/
angular.module('home').controller('homeController', ['$scope', '$injector',
function homeController($scope, $injector) {
// The parameter name for getting the history from local storage
var GUAC_HISTORY_STORAGE_KEY = "GUAC_HISTORY";
// Get the dependencies commonJS style
var connectionGroupService = $injector.get("connectionGroupService");
var localStorageUtility = $injector.get("localStorageUtility");
// All the connections and connection groups in root
$scope.connectionsAndGroups = [];
// All valid recent connections
$scope.recentConnections = [];
/* Fetch all connections and groups, then find which recent connections
* still refer to valid connections and groups.
*/
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);
}
// Figure out which recent connection entries are valid
$scope.connectionsAndGroups.forEach(function findValidEntries (connectionOrGroup) {
var type = connectionOrGroup.isConnection ? "c" : "cg";
// Find the unique ID to index into the recent connections
var uniqueId = encodeURIComponent(
type + "/" + connectionOrGroup.identifier
);
/*
* If it's a valid recent connection, add it to the list,
* along with enough information to make a connection url.
*/
var recentConnection = recentConnections[uniqueId];
if(recentConnection) {
recentConnection.type = type;
recentConnection.id = connectionOrGroup.identifier;
$scope.recentConnections.push(recentConnection);
}
});
});
/**
* Toggle the open/closed status of the connectionGroup.
*
* @param {object} connectionGroup The connection group to toggle.
*/
$scope.toggleExpanded = function toggleExpanded(connectionGroup) {
connectionGroup.expanded = !connectionGroup.expanded;
};
}]);

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
angular.module('home', ['connection', 'connectionGroup', 'user', 'permission']);

View File

@@ -0,0 +1,37 @@
/*
* 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.
*/
.connection a, .group a {
text-decoration:none;
color: black;
}
.connection a:hover, .group a:hover {
text-decoration:none;
color: black;
}
.connection a:visited, .group a:visited {
text-decoration:none;
color: black;
}

View File

@@ -0,0 +1,78 @@
<!--
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.
-->
<script type="text/ng-template" id="nestedGroup.html">
<div class="connection" ng-show="item.isConnection">
<a ng-href="#/client/c/{{item.identifier}}">
<div class="caption">
<div class="protocol">
<div class="icon type" ng-class="item.protocol"></div>
</div>
<span class="name">{{item.name}}</span>
</div>
</a>
</div>
<div" class="group" ng-show="!item.isConnection">
<div class="caption">
<div class="icon group type" ng-click="toggleExpanded(item)" ng-class="{expanded: item.expanded, empty: !item.children.length, balancer: item.balancer && !item.children.length}"></div>
<span class="name">
<a ng-show="item.balancer" ng-href="#/client/cg/{{item.identifier}}">{{item.name}}</a>
<span ng-show="!item.balancer">{{item.name}}</span>
</span>
</div>
<div class="children" ng-show="item.expanded">
<div class="list-item" ng-repeat="item in item.children | orderBy : 'name'" ng-include="'nestedGroup.html'">
</div>
</div>
</script>
<div class="connection-list-ui">
<div class="logout-panel">
<a class="manage button" ng-show="currentUserHasUpdate" href="#/manage">{{'home.manage' | translate}}</a>
<a class="logout button" href="#/login">{{'home.logout' | translate}}</a>
</div>
<!-- The recent connections for this user -->
<h2>{{'home.recentConnections' | translate}}</h2>
<div class="recent-connections" ng-hide="recentConnections.length">
<p class="no-recent">{{'home.noRecentConnections' | translate}}</p>
</div>
<div class="recent-connections" ng-show="recentConnections.length">
<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}}"/>
</div>
<div class="caption">
<span class="name">{{recentConnection.name}}</span>
</div>
</a>
</div>
</div>
<!-- All connections for this user -->
<h2>{{'home.allConnections' | translate}}</h2>
<div class="all-connections">
<div class="list-item" ng-repeat="item in connectionsAndGroups | orderBy : 'name'" ng-include="'nestedGroup.html'"></div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
/*
* 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 config block for setting up the HTTP PATCH method.
*/
angular.module('index').config(['$httpProvider',
function indexHttpPatchConfig($httpProvider) {
$httpProvider.defaults.headers.patch = {
'Content-Type': 'application/json'
}
}]);

View File

@@ -0,0 +1,31 @@
/*
* 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 config block for setting up the authentication interceptor.
*/
angular.module('index').config(['$httpProvider',
function indexInterceptorConfig($httpProvider) {
$httpProvider.interceptors.push('authenticationInterceptor');
}]);

View File

@@ -0,0 +1,57 @@
/*
* 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 config block for setting up all the url routing.
*/
angular.module('index').config(['$routeProvider', '$locationProvider',
function indexRouteConfig($routeProvider, $locationProvider) {
// Disable HTML5 mode (use # for routing)
$locationProvider.html5Mode(false);
$routeProvider.
when('/', {
title: 'index.title',
templateUrl: 'app/home/templates/home.html',
controller: 'homeController'
}).
when('/manage/', {
title: 'index.title',
templateUrl: 'app/manage/templates/manage.html',
controller: 'manageController'
}).
when('/login/', {
title: 'index.title',
templateUrl: 'app/login/templates/login.html',
controller: 'loginController'
}).
when('/client/:type/:id/:params?', {
templateUrl: 'app/client/templates/client.html',
controller: 'clientController'
}).
otherwise({
redirectTo: '/'
});
}]);

View File

@@ -0,0 +1,33 @@
/*
* 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 configuration block for setting up everything having to do with i18n.
*/
angular.module('index').config(['$translateProvider', function($translateProvider) {
$translateProvider.preferredLanguage('en_US');
$translateProvider.useStaticFilesLoader({
prefix: 'translations/',
suffix: '.json'
});
}]);

View File

@@ -0,0 +1,116 @@
/*
* 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 controller for the root of the application.
*/
angular.module('index').controller('indexController', ['$scope', '$injector',
function indexController($scope, $injector) {
// Get the dependencies commonJS style
var permissionDAO = $injector.get("permissionDAO"),
permissionCheckService = $injector.get("permissionCheckService"),
localStorageUtility = $injector.get("localStorageUtility"),
$q = $injector.get("$q"),
$document = $injector.get("$document"),
$window = $injector.get("$window"),
$location = $injector.get("$location");
/*
* Safe $apply implementation from Alex Vanston:
* https://coderwall.com/p/ngisma
*/
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase === '$apply' || phase === '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
// Put some useful variables in the top level scope
$scope.page = { title: '' };
$scope.currentUserID = null;
$scope.currentUserIsAdmin = false;
$scope.currentUserHasUpdate = false;
$scope.currentUserPermissions = null;
// A promise to be fulfilled when all basic user permissions are loaded.
var permissionsLoaded= $q.defer();
$scope.basicPermissionsLoaded = permissionsLoaded.promise;
$scope.currentUserID = localStorageUtility.get('userID');
// If the user is unknown, force a login
if(!$scope.currentUserID)
$location.path('/login');
// Allow the permissions to be reloaded elsewhere if needed
$scope.loadBasicPermissions = function loadBasicPermissions() {
permissionDAO.getPermissions($scope.currentUserID).success(function fetchCurrentUserPermissions(permissions) {
$scope.currentUserPermissions = permissions;
// Will be true if the user is an admin
$scope.currentUserIsAdmin = permissionCheckService.checkPermission($scope.currentUserPermissions, "SYSTEM", undefined, "ADMINISTER");
// Will be true if the user is an admin or has update access to any object
$scope.currentUserHasUpdate = $scope.currentUserIsAdmin ||
permissionCheckService.checkPermission($scope.currentUserPermissions, undefined, undefined, "UPDATE");
permissionsLoaded.resolve();
});
};
// Try to load them now
$scope.loadBasicPermissions();
// Create event listeners at the global level
var keyboard = new Guacamole.Keyboard($document[0]);
// Broadcast keydown events down the scope heirarchy
keyboard.onkeydown = function onkeydown(keysym) {
var guacKeydownEvent = $scope.$broadcast('guacKeydown', keysym, keyboard);
return !guacKeydownEvent.defaultPrevented;
};
// Broadcast keyup events down the scope heirarchy
keyboard.onkeyup = function onkeyup(keysym) {
$scope.$broadcast('guacKeyup', keysym, keyboard);
};
// Release all keys when window loses focus
$window.onblur = function () {
keyboard.reset();
};
// Update title upon navigation
$scope.$on('$routeChangeSuccess', function(event, current, previous) {
var title = current.$$route.title;
if (title)
$scope.page.title = title;
});
}]);

View 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 the root of the application.
*/
angular.module('index', ['ngRoute', 'pascalprecht.translate', 'home', 'manage', 'login', 'client']);

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
angular.module('index').factory('authenticationInterceptor', ['$location', '$q',
function authenticationInterceptor($location, $q) {
return {
'response': function(response) {
return response || $q.when(response);
},
'responseError': function(rejection) {
// Do not redirect failed login requests to the login page.
if ((rejection.status === 401 || rejection.status === 403)
&& rejection.config.url.search('api/login') === -1) {
$location.path('/login');
}
return $q.reject(rejection);
}
};
}]);

View File

@@ -0,0 +1,50 @@
/*
* 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.
*/
angular.module('login').controller('loginController', ['$scope', '$injector',
function loginController($scope, $injector) {
// Get the dependencies commonJS style
var authenticationService = $injector.get("authenticationService");
var localStorageUtility = $injector.get("localStorageUtility");
var $location = $injector.get("$location");
// Clear the auth token and userID to log out the user
localStorageUtility.clear("authToken");
localStorageUtility.clear("userID");
$scope.loginError = false;
$scope.login = function login() {
authenticationService.login($scope.username, $scope.password)
.success(function success(data, status, headers, config) {
localStorageUtility.set('authToken', data.authToken);
localStorageUtility.set('userID', data.userID);
// Set up the basic permissions for the user
$scope.loadBasicPermissions();
$location.path('/');
}).error(function error(data, status, headers, config) {
$scope.loginError = true;
});
};
}]);

View 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 the login functionality.
*/
angular.module('login', []);

View File

@@ -0,0 +1,43 @@
/*
* 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 authenticating a user against the REST API.
*/
angular.module('index').factory('authenticationService', ['$http',
function authenticationService($http) {
var service = {};
/**
* Makes a request to authenticate a user using the login REST API endpoint,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} username The username to log in with.
* @param {string} password The password to log in with.
* @returns {promise} A promise for the HTTP call.
*/
service.login = function login(username, password) {
return $http.post("api/login?username=" + username +"&password=" + password);
};
return service;
}]);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2014 Glyptodon LLC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -39,26 +39,7 @@ body {
margin: 0; margin: 0;
} }
#manage { div.login-ui {
display: none;
}
.admin #manage {
display: inline-block;
}
button#manage {
background-image: url('../images/action-icons/guac-config.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}
div#login-ui {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: fixed; position: fixed;
@@ -83,11 +64,11 @@ div#login-ui {
100% { margin-left: 0.00em; margin-right: 0.00em; } 100% { margin-left: 0.00em; margin-right: 0.00em; }
} }
p#login-error { p.login-error {
display: none; display: none;
} }
.error p#login-error { .error p.login-error {
display: block; display: block;
position: fixed; position: fixed;
@@ -107,7 +88,7 @@ p#login-error {
color: #964040; color: #964040;
} }
.error #login-form { .error .login-form {
animation-name: shake-head; animation-name: shake-head;
animation-duration: 0.25s; animation-duration: 0.25s;
animation-timing-function: linear; animation-timing-function: linear;
@@ -116,21 +97,21 @@ p#login-error {
-webkit-animation-timing-function: linear; -webkit-animation-timing-function: linear;
} }
div#login-logo { div.login-logo {
position: relative; position: relative;
bottom: 0; bottom: 0;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
div#login-dialog-middle { div.login-dialog-middle {
width: 100%; width: 100%;
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
} }
div#login-dialog { div.login-dialog {
max-width: 75%; max-width: 75%;
text-align: left; text-align: left;
@@ -138,20 +119,20 @@ div#login-dialog {
display: inline-block; display: inline-block;
} }
div#login-dialog h1 { div.login-dialog h1 {
margin-top: 0; margin-top: 0;
margin-bottom: 0em; margin-bottom: 0em;
text-align: center; text-align: center;
} }
div#login-dialog #buttons { div.login-dialog .buttons {
padding-top: 0.5em; padding-top: 0.5em;
text-align: right; text-align: right;
} }
input[type="submit"]#login, button#login { input[type="submit"].login, button.login {
background-image: url('../images/guacamole-logo-64.png'); background-image: url('images/guacamole-logo-64.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1.5em; background-size: 1.5em;
background-position: 0.5em 0.25em; background-position: 0.5em 0.25em;
@@ -160,7 +141,7 @@ input[type="submit"]#login, button#login {
} }
div#login-dialog #login-fields { div.login-dialog .login-fields {
vertical-align: middle; vertical-align: middle;
@@ -170,11 +151,11 @@ div#login-dialog #login-fields {
} }
div#login-dialog th { div.login-dialog th {
text-shadow: 1px 1px white; text-shadow: 1px 1px white;
} }
div#login-dialog #login-fields input { div.login-dialog .login-fields input {
border: 1px solid #777; border: 1px solid #777;
-moz-border-radius: 0.2em; -moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em; -webkit-border-radius: 0.2em;
@@ -183,7 +164,7 @@ div#login-dialog #login-fields input {
width: 100%; width: 100%;
} }
div#login-dialog #login-fields img.logo { div.login-dialog .login-fields img.logo {
position: fixed; position: fixed;
margin: 10px; margin: 10px;
left: 0; left: 0;
@@ -192,7 +173,7 @@ div#login-dialog #login-fields img.logo {
z-index: -1; z-index: -1;
} }
div#version { div.version {
text-align: center; text-align: center;
font-style: italic; font-style: italic;
font-size: 0.75em; font-size: 0.75em;
@@ -206,12 +187,12 @@ img {
border: none; border: none;
} }
img#license { img.license {
float: right; float: right;
margin: 2px; margin: 2px;
} }
div#connection-list-ui h1 { div.connection-list-ui h1 {
margin: 0; margin: 0;
padding: 0.5em; padding: 0.5em;
@@ -222,7 +203,7 @@ div#connection-list-ui h1 {
} }
div#connection-list-ui h2 { div.connection-list-ui h2 {
padding: 0.5em; padding: 0.5em;
margin: 0; margin: 0;
@@ -237,38 +218,38 @@ div#connection-list-ui h2 {
} }
div#connection-list-ui img { div.connection-list-ui img {
vertical-align: middle; vertical-align: middle;
} }
div#logout-panel { div.logout-panel {
padding: 0.45em; padding: 0.45em;
text-align: right; text-align: right;
float: right; float: right;
} }
.history-unavailable div#recent-connections { .history-unavailable div.recent-connections {
display: none; display: none;
} }
div#recent-connections, div.recent-connections,
div#clipboardDiv, div.clipboardDiv,
div#settings, div.settings,
div#all-connections { div.all-connections {
margin: 1em; margin: 1em;
padding: 0; padding: 0;
} }
#all-connections .list-buttons { .all-connections .list-buttons {
text-align: center; text-align: center;
padding: 0; padding: 0;
} }
div#recent-connections { div.recent-connections {
text-align: center; text-align: center;
} }
#no-recent { .no-recent {
color: black; color: black;
text-shadow: 1px 1px white; text-shadow: 1px 1px white;
@@ -278,7 +259,7 @@ div#recent-connections {
font-weight: bolder; font-weight: bolder;
} }
div#recent-connections div.connection { div.recent-connections div.connection {
-moz-border-radius: 0.5em; -moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em; -webkit-border-radius: 0.5em;
-khtml-border-radius: 0.5em; -khtml-border-radius: 0.5em;
@@ -296,15 +277,12 @@ div#recent-connections div.connection {
cursor: pointer; cursor: pointer;
} }
.connection:hover { .group .connection .bears {
background: #CDA; display: none;
} }
.group, .connection:hover {
.connection .name { background: #CDA;
color: black;
font-weight: normal;
padding: 0.1em;
} }
.connection .thumbnail { .connection .thumbnail {
@@ -317,20 +295,11 @@ div#recent-connections div.connection {
max-width: 75%; max-width: 75%;
} }
div#all-connections .connection { div.recent-connections .connection .thumbnail {
display: block;
text-align: left;
}
div#recent-connections .connection .thumbnail {
display: block; display: block;
} }
div#all-connections .connection { div.recent-connections .protocol {
padding: 0.1em;
}
div#recent-connections .protocol {
display: none; display: none;
} }
@@ -342,7 +311,7 @@ div#recent-connections .protocol {
margin-left: 0.25em; margin-left: 0.25em;
} }
#clipboardDiv textarea { .clipboardDiv textarea {
width: 100%; width: 100%;
border: 1px solid #AAA; border: 1px solid #AAA;
-moz-border-radius: 0.25em; -moz-border-radius: 0.25em;
@@ -352,12 +321,12 @@ div#recent-connections .protocol {
white-space: pre; white-space: pre;
} }
#settings dt { .settings dt {
border-bottom: 1px dotted #AAA; border-bottom: 1px dotted #AAA;
padding-bottom: 0.25em; padding-bottom: 0.25em;
} }
#settings dd { .settings dd {
margin: 1.5em; margin: 1.5em;
margin-left: 2.5em; margin-left: 2.5em;
font-size: 0.75em; font-size: 0.75em;

View File

@@ -0,0 +1,54 @@
<!--
Copyright 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.
-->
<div class="login-ui" ng-class="{error: loginError}" >
<div class="login-dialog-middle">
<div class="login-dialog">
<p class="login-error">{{'login.loginError' | translate}}</p>
<form ng-submit="login()">
<div class="login-form login-fields">
<table>
<tr>
<th>{{'login.username' | translate}}</th>
<td><input ng-model="username" type="text" name="username" autofocus="autofocus" class="username"/></td>
</tr>
<tr>
<th>{{'login.password' | translate}}</th>
<td><input ng-model="password" type="password" name="password" class="password"/></td>
</tr>
</table>
<img class="logo" src="images/guac-mono-192.png" alt=""/>
</div>
<div class="buttons">
<input type="submit" name="login" class="login" value="{{'login.login' | translate}}"/>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,110 @@
/*
* 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 controller for the connection edit modal.
*/
angular.module('manage').controller('connectionEditModalController', ['$scope', '$injector',
function connectionEditModalController($scope, $injector) {
var connectionEditModal = $injector.get('connectionEditModal');
var connectionDAO = $injector.get('connectionDAO');
var displayObjectPreparationService = $injector.get('displayObjectPreparationService');
// Make a copy of the old connection so that we can copy over the changes when done
var oldConnection = $scope.connection;
// Copy data into a new conection object in case the user doesn't want to save
$scope.connection = angular.copy($scope.connection);
var newConnection = !$scope.connection.identifier;
if(newConnection)
// Prepare this connection for display
displayObjectPreparationService.prepareConnection($scope.connection);
// Set it to VNC by default
if(!$scope.connection.protocol)
$scope.connection.protocol = "vnc";
/**
* Close the modal.
*/
$scope.close = function close() {
connectionEditModal.deactivate();
};
/**
* Save the connection and close the modal.
*/
$scope.save = function save() {
connectionDAO.saveConnection($scope.connection).success(function successfullyUpdatedConnection() {
var oldParentID = oldConnection.parentIdentifier;
var newParentID = $scope.connection.parentIdentifier;
// Copy the data back to the original model
angular.extend(oldConnection, $scope.connection);
// We have to move this connection
if(oldParentID !== newParentID)
// New connections are created by default in root - don't try to move it if it's already there.
if(newConnection && newParentID === $scope.rootGroup.identifier) {
$scope.moveItem($scope.connection, oldParentID, newParentID);
} else {
connectionDAO.moveConnection($scope.connection).then(function moveConnection() {
$scope.moveItem($scope.connection, oldParentID, newParentID);
});
}
// Close the modal
connectionEditModal.deactivate();
});
};
/**
* Delete the connection and close the modal.
*/
$scope['delete'] = function deleteConnection() {
// Nothing to delete if the connection is new
var newConnection = !$scope.connection.identifier;
if(newConnection) {
// Close the modal
connectionEditModal.deactivate();
return;
}
connectionDAO.deleteConnection($scope.connection).success(function successfullyDeletedConnection() {
var oldParentID = oldConnection.parentIdentifier;
// We have to remove this connection from the heirarchy
$scope.moveItem($scope.connection, oldParentID);
// Close the modal
connectionEditModal.deactivate();
});
}
}]);

View File

@@ -0,0 +1,115 @@
/*
* 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 controller for the connection group edit modal.
*/
angular.module('manage').controller('connectionGroupEditModalController', ['$scope', '$injector',
function connectionEditModalController($scope, $injector) {
var connectionGroupEditModal = $injector.get('connectionGroupEditModal');
var connectionGroupDAO = $injector.get('connectionGroupDAO');
var displayObjectPreparationService = $injector.get('displayObjectPreparationService');
// Make a copy of the old connection group so that we can copy over the changes when done
var oldConnectionGroup = $scope.connectionGroup;
// Copy data into a new conection group object in case the user doesn't want to save
$scope.connectionGroup = angular.copy($scope.connectionGroup);
var newConnectionGroup = !$scope.connectionGroup.identifier;
$scope.types = [
{
label: "organizational",
value: "ORGANIZATIONAL"
},
{
label: "balancing",
value: "BALANCING"
}
];
// Set it to organizational by default
if(!$scope.connectionGroup.type)
$scope.connectionGroup.type = $scope.types[0].value;
/**
* Close the modal.
*/
$scope.close = function close() {
connectionGroupEditModal.deactivate();
};
/**
* Save the connection and close the modal.
*/
$scope.save = function save() {
connectionGroupDAO.saveConnectionGroup($scope.connectionGroup).success(function successfullyUpdatedConnectionGroup() {
// Prepare this connection group for display
displayObjectPreparationService.prepareConnectionGroup($scope.connectionGroup);
var oldParentID = oldConnectionGroup.parentIdentifier;
var newParentID = $scope.connectionGroup.parentIdentifier;
// Copy the data back to the original model
angular.extend(oldConnectionGroup, $scope.connectionGroup);
// New groups are created by default in root - don't try to move it if it's already there.
if(newConnectionGroup && newParentID === $scope.rootGroup.identifier) {
$scope.moveItem($scope.connectionGroup, oldParentID, newParentID);
} else {
connectionGroupDAO.moveConnectionGroup($scope.connectionGroup).then(function moveConnectionGroup() {
$scope.moveItem($scope.connectionGroup, oldParentID, newParentID);
});
}
// Close the modal
connectionGroupEditModal.deactivate();
});
};
/**
* Delete the connection and close the modal.
*/
$scope['delete'] = function deleteConnectionGroup() {
// Nothing to delete if the connection is new
if(newConnectionGroup)
// Close the modal
connectionGroupEditModal.deactivate();
connectionGroupDAO.deleteConnectionGroup($scope.connectionGroup).success(function successfullyDeletedConnectionGroup() {
var oldParentID = oldConnectionGroup.parentIdentifier;
// We have to remove this connection group from the heirarchy
$scope.moveItem($scope.connectionGroup, oldParentID);
// Close the modal
connectionGroupEditModal.deactivate();
});
}
}]);

View File

@@ -0,0 +1,242 @@
/*
* 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 controller for the administration page.
*/
angular.module('manage').controller('manageController', ['$scope', '$injector',
function manageController($scope, $injector) {
// Get the dependencies commonJS style
var connectionGroupService = $injector.get('connectionGroupService');
var connectionEditModal = $injector.get('connectionEditModal');
var connectionGroupEditModal = $injector.get('connectionGroupEditModal');
var userEditModal = $injector.get('userEditModal');
var protocolDAO = $injector.get('protocolDAO');
var userDAO = $injector.get('userDAO');
var userService = $injector.get('userService');
// All the connections and connection groups in root
$scope.connectionsAndGroups = [];
// All users that the current user has permission to edit
$scope.users = [];
$scope.basicPermissionsLoaded.then(function basicPermissionsHaveBeenLoaded() {
connectionGroupService.getAllGroupsAndConnections([], undefined, true, true).then(function filterConnectionsAndGroups(rootGroupList) {
$scope.rootGroup = rootGroupList[0];
$scope.connectionsAndGroups = $scope.rootGroup.children;
// Filter the items to only include ones that we have UPDATE for
if(!$scope.currentUserIsAdmin) {
connectionGroupService.filterConnectionsAndGroupByPermission(
$scope.connectionsAndGroups,
$scope.currentUserPermissions,
{
'CONNECTION': 'UPDATE',
'CONNECTION_GROUP': 'UPDATE'
}
);
}
});
userDAO.getUsers().success(function filterEditableUsers(users) {
$scope.users = users;
// Filter the users to only include ones that we have UPDATE for
if(!$scope.currentUserIsAdmin) {
userService.filterUsersByPermission(
$scope.users,
$scope.currentUserPermissions,
'UPDATE'
);
}
});
});
/**
* Move the connection or connection group within the group heirarchy,
* initially place a new item, or remove an item from the heirarchy.
* @param {object} item The connection or connection group to move.
* @param {string} fromID The ID of the group to move the item from, if relevant.
* @param {string} toID The ID of the group to move the item to, if relevant.
*/
$scope.moveItem = function moveItem(item, fromID, toID) {
// Remove the item from the old group, if there was one
if(fromID) {
var oldParent = findGroup($scope.rootGroup, fromID),
oldChildren = oldParent.children;
// Find and remove the item from the old group
for(var i = 0; i < oldChildren.length; i++) {
var child = oldChildren[i];
if(child.isConnection === item.isConnection &&
child.identifier === item.identifier) {
oldChildren.splice(i, 1);
break;
}
}
}
// Add the item to the new group, if there is one
if(toID) {
var newParent = findGroup($scope.rootGroup, toID);
newParent.children.push(item);
}
};
function findGroup(group, parentID) {
// Only searching in groups
if(group.isConnection)
return;
if(group.identifier === parentID)
return group;
for(var i = 0; i < group.children.length; i++) {
var child = group.children[i];
var foundGroup = findGroup(child, parentID);
if(foundGroup) return foundGroup;
}
}
$scope.protocols = {};
// Get the protocol information from the server and copy it into the scope
protocolDAO.getProtocols().success(function fetchProtocols(protocols) {
angular.extend($scope.protocols, protocols);
});
/**
* Toggle the open/closed status of the connectionGroup.
*
* @param {object} connectionGroup The connection group to toggle.
*/
$scope.toggleExpanded = function toggleExpanded(connectionGroup) {
connectionGroup.expanded = !connectionGroup.expanded;
};
/**
* Open a modal to edit the connection.
*
* @param {object} connection The connection to edit.
*/
$scope.editConnection = function editConnection(connection) {
connectionEditModal.activate(
{
connection : connection,
protocols : $scope.protocols,
moveItem : $scope.moveItem,
rootGroup : $scope.rootGroup
});
};
/**
* Open a modal to edit a new connection.
*/
$scope.newConnection = function newConnection() {
connectionEditModal.activate(
{
connection : {},
protocols : $scope.protocols,
moveItem : $scope.moveItem,
rootGroup : $scope.rootGroup
});
};
/**
* Open a modal to edit a new connection group.
*/
$scope.newConnectionGroup = function newConnectionGroup() {
connectionGroupEditModal.activate(
{
connectionGroup : {},
moveItem : $scope.moveItem,
rootGroup : $scope.rootGroup
});
};
/**
* Open a modal to edit the connection group.
*
* @param {object} connection The connection group to edit.
*/
$scope.editConnectionGroup = function editConnectionGroup(connectionGroup) {
connectionGroupEditModal.activate(
{
connectionGroup : connectionGroup,
moveItem : $scope.moveItem,
rootGroup : $scope.rootGroup
});
};
// Remove the user from the current list of users
function removeUser(user) {
for(var i = 0; i < $scope.users.length; i++) {
if($scope.users[i].username === user.username) {
$scope.users.splice(i, 1);
break;
}
}
}
/**
* Open a modal to edit the user.
*
* @param {object} user The user to edit.
*/
$scope.editUser = function editUser(user) {
userEditModal.activate(
{
user : user,
rootGroup : $scope.rootGroup,
removeUser : removeUser
});
};
$scope.newUsername = "";
/**
* Open a modal to edit the user.
*
* @param {object} user The user to edit.
*/
$scope.newUser = function newUser() {
if($scope.newUsername) {
var newUser = {
username: $scope.newUsername
};
userDAO.createUser(newUser).success(function addUserToList() {
$scope.users.push(newUser);
});
$scope.newUsername = "";
}
};
}]);

View File

@@ -0,0 +1,263 @@
/*
* 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 controller for the connection edit modal.
*/
angular.module('manage').controller('userEditModalController', ['$scope', '$injector',
function userEditModalController($scope, $injector) {
var userEditModal = $injector.get('userEditModal');
var userDAO = $injector.get('userDAO');
var permissionDAO = $injector.get('permissionDAO');
// Make a copy of the old user so that we can copy over the changes when done
var oldUser = $scope.user;
// Copy data into a new conection object in case the user doesn't want to save
$scope.user = angular.copy($scope.user);
/**
* Close the modal.
*/
$scope.close = function close() {
userEditModal.deactivate();
};
/*
* All the permissions that have been modified since this modal was opened.
* Maps of type or id to value.
*/
$scope.modifiedSystemPermissions = {};
$scope.modifiedConnectionPermissions = {};
$scope.modifiedConnectionGroupPermissions = {};
$scope.markSystemPermissionModified = function markSystemPermissionModified(type) {
$scope.modifiedSystemPermissions[type] = $scope.systemPermissions[type];
};
$scope.markConnectionPermissionModified = function markConnectionPermissionModified(id) {
$scope.modifiedConnectionPermissions[id] = $scope.connectionPermissions[id];
};
$scope.markConnectionGroupPermissionModified = function markConnectionGroupPermissionModified(id) {
$scope.modifiedConnectionGroupPermissions[id] = $scope.connectionGroupPermissions[id];
};
/**
* Save the user and close the modal.
*/
$scope.save = function save() {
if($scope.passwordMatch !== $scope.user.password) {
//TODO: Display an error
return;
}
userDAO.saveUser($scope.user).success(function successfullyUpdatedUser() {
//Figure out what permissions have changed
var connectionPermissionsToCreate = [],
connectionPermissionsToDelete = [],
connectionGroupPermissionsToCreate = [],
connectionGroupPermissionsToDelete = [],
systemPermissionsToCreate = [],
systemPermissionsToDelete = [];
for(var type in $scope.modifiedSystemPermissions) {
// It was added
if($scope.modifiedSystemPermissions[type] && !originalSystemPermissions[type]) {
systemPermissionsToCreate.push(type);
}
// It was removed
else if(!$scope.modifiedSystemPermissions[type] && originalSystemPermissions[type]) {
systemPermissionsToDelete.push(type);
}
}
for(var id in $scope.modifiedConnectionPermissions) {
// It was added
if($scope.modifiedConnectionPermissions[id] && !originalConnectionPermissions[id]) {
connectionPermissionsToCreate.push(id);
}
// It was removed
else if(!$scope.modifiedConnectionPermissions[id] && originalConnectionPermissions[id]) {
connectionPermissionsToDelete.push(id);
}
}
for(var id in $scope.modifiedConnectionGroupPermissions) {
// It was added
if($scope.modifiedConnectionGroupPermissions[id] && !originalConnectionGroupPermissions[id]) {
connectionGroupPermissionsToCreate.push(id);
}
// It was removed
else if(!$scope.modifiedConnectionGroupPermissions[id] && originalConnectionGroupPermissions[id]) {
connectionGroupPermissionsToDelete.push(id);
}
}
var permissionsToAdd = [];
var permissionsToRemove = [];
// Create new connection permissions
for(var i = 0; i < connectionPermissionsToCreate.length; i++) {
permissionsToAdd.push({
objectType : "CONNECTION",
objectIdentifier : connectionPermissionsToCreate[i],
permissionType : "READ"
});
}
// Delete old connection permissions
for(var i = 0; i < connectionPermissionsToDelete.length; i++) {
permissionsToRemove.push({
objectType : "CONNECTION",
objectIdentifier : connectionPermissionsToDelete[i],
permissionType : "READ"
});
}
// Create new connection group permissions
for(var i = 0; i < connectionGroupPermissionsToCreate.length; i++) {
permissionsToAdd.push({
objectType : "CONNECTION_GROUP",
objectIdentifier : connectionGroupPermissionsToCreate[i],
permissionType : "READ"
});
}
// Delete old connection group permissions
for(var i = 0; i < connectionGroupPermissionsToDelete.length; i++) {
permissionsToRemove.push({
objectType : "CONNECTION_GROUP",
objectIdentifier : connectionGroupPermissionsToDelete[i],
permissionType : "READ"
});
}
// Create new system permissions
for(var i = 0; i < systemPermissionsToCreate.length; i++) {
permissionsToAdd.push({
objectType : "SYSTEM",
permissionType : systemPermissionsToCreate[i]
});
}
// Delete old system permissions
for(var i = 0; i < systemPermissionsToDelete.length; i++) {
permissionsToRemove.push({
objectType : "SYSTEM",
permissionType : systemPermissionsToDelete[i]
});
}
function completeSaveProcess() {
// Close the modal
userEditModal.deactivate();
}
function handleFailure() {
//TODO: Handle the permission API call failure
}
if(permissionsToAdd.length || permissionsToRemove.length) {
// Make the call to update the permissions
permissionDAO.patchPermissions(
$scope.user.username, permissionsToAdd, permissionsToRemove)
.success(completeSaveProcess).error(handleFailure);
} else {
completeSaveProcess();
}
});
};
$scope.permissions = [];
// Maps of connection and connection group IDs to access permission booleans
$scope.connectionPermissions = {};
$scope.connectionGroupPermissions = {};
$scope.systemPermissions = {};
// The original permissions to compare against
var originalConnectionPermissions,
originalConnectionGroupPermissions,
originalSystemPermissions;
// Get the permissions for the user we are editing
permissionDAO.getPermissions($scope.user.username).success(function gotPermissions(permissions) {
$scope.permissions = permissions;
// Figure out if the user has any system level permissions
for(var i = 0; i < $scope.permissions.length; i++) {
var permission = $scope.permissions[i];
if(permission.objectType === "SYSTEM") {
$scope.systemPermissions[permission.permissionType] = true;
// Only READ permission is editable via this UI
} else if (permission.permissionType === "READ") {
switch(permission.objectType) {
case "CONNECTION":
$scope.connectionPermissions[permission.objectIdentifier] = true;
break;
case "CONNECTION_GROUP":
$scope.connectionGroupPermissions[permission.objectIdentifier] = true;
break;
}
}
}
// Copy the original permissions so we can compare later
originalConnectionPermissions = angular.copy($scope.connectionPermissions);
originalConnectionGroupPermissions = angular.copy($scope.connectionGroupPermissions);
originalSystemPermissions = angular.copy($scope.systemPermissions);
});
/**
* Delete the user and close the modal.
*/
$scope['delete'] = function deleteUser() {
userDAO.deleteUser($scope.user).success(function successfullyDeletedUser() {
// Remove the user from the list
$scope.removeUser($scope.user);
// Close the modal
userEditModal.deactivate();
});
}
/**
* Toggle the open/closed status of the connectionGroup.
*
* @param {object} connectionGroup The connection group to toggle.
*/
$scope.toggleExpanded = function toggleExpanded(connectionGroup) {
connectionGroup.expanded = !connectionGroup.expanded;
};
}]);

View File

@@ -0,0 +1,93 @@
/*
* 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 directive for choosing the location of a connection or connection group.
*/
angular.module('manage').directive('locationChooser', [function locationChooser() {
return {
// Element only
restrict: 'E',
replace: true,
scope: {
item: '=item',
root: '=root',
},
templateUrl: 'app/manage/templates/locationChooser.html',
controller: ['$scope', '$injector', function locationChooserController($scope, $injector) {
// The dropdown should start closed
$scope.showDropDown = false;
// Map of ID to name for all connection groups
$scope.connectionGroupNameMap = {};
// Set up the group for display and search
mapConnectionGroupNames($scope.root);
$scope.connectionGroups = [$scope.root];
// Should be in the root group by default
if(!$scope.item.parentIdentifier)
$scope.item.parentIdentifier = $scope.root.parentIdentifier;
setCurrentParentName();
// Add the name of all connection groups under group to the group name map
function mapConnectionGroupNames(group) {
$scope.connectionGroupNameMap[group.identifier] = group.name;
for(var i = 0; i < group.children.length; i++) {
var child = group.children[i];
if(!child.isConnection)
mapConnectionGroupNames(child);
}
}
//Set the current connection group name to the name of the connection group with the currently chosen ID
function setCurrentParentName() {
$scope.currentConnectionGroupName = $scope.connectionGroupNameMap[$scope.item.parentIdentifier];
}
// Watch for changes to the parentID, and update the current name as needed
$scope.currentConnectionGroupName = "";
$scope.$watch('item.parentIdentifier', function watchParentID() {
setCurrentParentName();
});
/**
* Toggle the drop down - open or closed.
*/
$scope.toggleDropDown = function toggleDropDown() {
$scope.showDropDown = !$scope.showDropDown;
}
/**
* Choose a new parent ID for the item.
* @param {type} parentID The new parentID.
*/
$scope.chooseParentID = function chooseParentID(parentID) {
$scope.item.parentIdentifier = parentID;
}
}]
};
}]);

View File

@@ -0,0 +1,27 @@
/*
* 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 the administration functionality.
*/
angular.module('manage', ['btford.modal', 'protocol', 'connectionGroup', 'util']);

View File

@@ -0,0 +1,35 @@
/*
* 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 modal for editing a connection.
*/
angular.module('manage').factory('connectionEditModal', ['btfModal',
function connectionEditModal(btfModal) {
// Create the modal object to be used later to actually create the modal
return btfModal({
controller: 'connectionEditModalController',
controllerAs: 'modal',
templateUrl: 'app/manage/templates/editableConnection.html',
});
}]);

View File

@@ -0,0 +1,35 @@
/*
* 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 modal for editing a connection group.
*/
angular.module('manage').factory('connectionGroupEditModal', ['btfModal',
function connectionGroupEditModal(btfModal) {
// Create the modal object to be used later to actually create the modal
return btfModal({
controller: 'connectionGroupEditModalController',
controllerAs: 'modal',
templateUrl: 'app/manage/templates/editableConnectionGroup.html',
});
}]);

View File

@@ -0,0 +1,35 @@
/*
* 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 modal for editing a connection.
*/
angular.module('manage').factory('userEditModal', ['btfModal',
function userEditModal(btfModal) {
// Create the modal object to be used later to actually create the modal
return btfModal({
controller: 'userEditModalController',
controllerAs: 'modal',
templateUrl: 'app/manage/templates/editableUser.html',
});
}]);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Glyptodon LLC * Copyright (C) 2014 Glyptodon LLC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -20,9 +20,9 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
button#back { button.add-user {
background-image: url('../images/action-icons/guac-back.png'); background-image: url('images/action-icons/guac-user-add.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1em; background-size: 1em;
background-position: 0.5em 0.45em; background-position: 0.5em 0.45em;
@@ -31,9 +31,9 @@ button#back {
} }
button#add-user { button.add-connection {
background-image: url('../images/action-icons/guac-user-add.png'); background-image: url('images/action-icons/guac-monitor-add.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1em; background-size: 1em;
background-position: 0.5em 0.45em; background-position: 0.5em 0.45em;
@@ -42,20 +42,9 @@ button#add-user {
} }
button#add-connection { button.add-connection-group {
background-image: url('../images/action-icons/guac-monitor-add.png'); background-image: url('images/action-icons/guac-group-add.png');
background-repeat: no-repeat;
background-size: 1em;
background-position: 0.5em 0.45em;
padding-left: 1.8em;
}
button#add-connection-group {
background-image: url('../images/action-icons/guac-group-add.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1em; background-size: 1em;
background-position: 0.5em 0.45em; background-position: 0.5em 0.45em;

View File

@@ -0,0 +1,131 @@
<!--
Copyright 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.
-->
<!-- Dialog container for the modal -->
<div class="dialog-container">
<div class="dialog edit">
<!-- Connection name -->
<div class="header">
<h2>{{connection.name}}</h2>
</div>
<!-- Main connection edit section -->
<div class="body">
<div class="form">
<div class="settings section">
<dl>
<dd>
<table class="fields section">
<!-- Edit connection name -->
<tr>
<th>{{'manage.edit.connection.name' | translate}}</th>
<td><input type="text" ng-model="connection.name"/></td>
</tr>
<!-- Edit connection location -->
<tr>
<th>{{'manage.edit.connection.location' | translate}}</th>
<td>
<location-chooser item="connection" root="rootGroup"/>
</td>
</tr>
<!-- Edit connection protocol -->
<tr>
<th>{{'manage.edit.connection.protocol' | translate}}</th>
<td>
<select ng-model="connection.protocol" ng-options="name as 'protocol.' + protocol.name + '.label' | translate for (name, protocol) in protocols | orderBy: name"></select>
</td>
</tr>
</table>
</dd>
<dd>
<table class="fields section">
<!-- All the different possible editable field types -->
<tr ng-repeat="parameter in protocols[connection.protocol].parameters">
<th>{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}:</th>
<td ng-show="parameter.type === 'TEXT'">
<input type="text" ng-model="connection.parameters[parameter.name]"/>
</td>
<td ng-show="parameter.type === 'NUMERIC'">
<input type="number" ng-model="connection.parameters[parameter.name]"/>
</td>
<td ng-show="parameter.type === 'PASSWORD'">
<input type="password" ng-model="connection.parameters[parameter.name]"/>
</td>
<td ng-show="parameter.type === 'BOOLEAN'">
<input type="checkbox" ng-model="connection.parameters[parameter.name]"/>
</td>
<td ng-show="parameter.type === 'ENUM'">
<select ng-model="connection.parameters[parameter.name]" ng-options="option.value as 'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.options.' + (option.value || 'empty') | translate for option in parameter.options | orderBy: value"></select>
</td>
</tr>
</table>
</dd>
<!-- History connection area -->
<dt>{{'manage.edit.connection.history.usageHistory' | translate}}</dt>
<dd ng-hide="connection.history.length">
<p>{{'manage.edit.connection.history.connectionNotUsed' | translate}}</p>
</dd>
<!-- History connection list -->
<dd ng-show="connection.history.length">
<table class="history section">
<tr>
<th>{{'manage.edit.connection.history.username' | translate}}</th>
<th>{{'manage.edit.connection.history.startTime' | translate}}</th>
<th>{{'manage.edit.connection.history.duration' | translate}}</th>
</tr>
<tbody>
<tr ng-repeat="record in connection.history">
<td class="username">{{record.username}}</td>
<td class="start">{{record.startDate | date:'short'}}</td>
<td ng-show="record.endDate" class="duration">{{record.endDate}}</td>
<td ng-hide="record.endDate" class="duration">{{'manage.edit.connection.history.activeNow' | translate}}</td>
</tr>
</tbody>
</table>
</dd>
</dl>
</div>
</div>
</div>
<!-- Control buttons -->
<div class="footer">
<button ng-click="save()">{{'manage.edit.connection.save' | translate}}</button>
<button ng-click="close()">{{'manage.edit.connection.cancel' | translate}}</button>
<button ng-click="delete()" class="danger">{{'manage.edit.connection.delete' | translate}}</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,79 @@
<!--
Copyright 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.
-->
<!-- Dialog container for the modal -->
<div class="dialog-container">
<div class="dialog edit">
<!-- Connection group name -->
<div class="header">
<h2>{{connectionGroup.name}}</h2>
</div>
<!-- Main connection group edit section -->
<div class="body">
<div class="form">
<div class="settings section">
<dl>
<dd>
<table class="fields section">
<!-- Edit connection group name -->
<tr>
<th>{{'manage.edit.connectionGroup.name' | translate}}</th>
<td><input type="text" ng-model="connectionGroup.name"/></td>
</tr>
<!-- Edit connection group location -->
<tr>
<th>{{'manage.edit.connectionGroup.location' | translate}}</th>
<td>
<location-chooser item="connectionGroup" root="rootGroup"/>
</td>
</tr>
<!-- Edit connection group type -->
<tr>
<th>{{'manage.edit.connectionGroup.type.label' | translate}}</th>
<td>
<select ng-model="connectionGroup.type" ng-options="type.value as 'manage.edit.connectionGroup.type.' + type.label | translate for type in types | orderBy: name"></select>
</td>
</tr>
</table>
</dd>
</dl>
</div>
</div>
</div>
<!-- Control buttons -->
<div class="footer">
<button ng-click="save()">{{'manage.edit.connectionGroup.save' | translate}}</button>
<button ng-click="close()">{{'manage.edit.connectionGroup.cancel' | translate}}</button>
<button ng-click="delete()" class="danger">{{'manage.edit.connectionGroup.delete' | translate}}</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,141 @@
<!--
Copyright 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.
-->
<!-- Hierarchical connection and connection group permission selector -->
<script type="text/ng-template" id="nestedUserPermissionEditGroup.html">
<!-- Connection -->
<div class="choice" ng-show="item.isConnection">
<input type="checkbox" ng-model="connectionPermissions[item.identifier]" ng-change="markConnectionPermissionModified(item.identifier)"/>
<div class="connection list-item">
<div class="caption">
<div class="protocol">
<div class="icon" ng-class="item.protocol"></div>
</div><span class="name">{{item.name}}</span>
</div>
</div>
</div>
<!-- Connection group -->
<div class="choice" ng-show="!item.isConnection">
<input type="checkbox" ng-model="connectionGroupPermissions[item.identifier]" ng-change="markConnectionGroupPermissionModified(item.identifier)"/>
<div class="group empty list-item balancer">
<div class="caption">
<div class="icon group" ng-click="toggleExpanded(item)" ng-class="{expanded: item.expanded, empty: !item.children.length, balancer: item.balancer && !item.children.length}"></div>
<span class="name">{{item.name}}</span>
</div>
<!-- Connection group children -->
<div class="children" ng-show="item.expanded">
<div ng-repeat="item in item.children | orderBy : 'name'" ng-include="'nestedUserPermissionEditGroup.html'">
</div>
</div>
</div>
</script>
<!-- User edit modal -->
<div class="dialog-container">
<div class="dialog edit">
<div class="header">
<h2>{{user.username}}</h2>
</div>
<div class="body">
<div class="form">
<div class="settings section">
<dl>
<!-- User properties section -->
<dt>{{'manage.edit.user.properties' | translate}}</dt>
<dd>
<table class="fields section">
<tr>
<th>{{'manage.edit.user.password' | translate}}</th>
<td><input ng-model="user.password" type="password" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.passwordMatch' | translate}}</th>
<td><input ng-model="passwordMatch" type="password" /></td>
</tr>
</table>
</dd>
<!-- System permissions section -->
<dt>{{'manage.edit.user.permissions' | translate}}</dt>
<dd>
<table class="permissions section">
<tr>
<th>{{'manage.edit.user.administerSystem' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.ADMINISTER" ng-change="markSystemPermissionModified('ADMINISTER')" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.createUser' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.CREATE_USER" ng-change="markSystemPermissionModified('CREATE_USER')" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.createConnection' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.CREATE_CONNECTION" ng-change="markSystemPermissionModified('CREATE_CONNECTION')" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.createConnectionGroup' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.CREATE_CONNECTION_GROUP" ng-change="markSystemPermissionModified('CREATE_CONNECTION_GROUP')" /></td>
</tr>
</table>
</dd>
<!-- Connection and connection group permission section -->
<dt>{{'manage.edit.user.connections' | translate}}</dt>
<dd>
<div class="group-view">
<div class="list">
<div ng-repeat="item in rootGroup.children | orderBy : 'name'" ng-include="'nestedUserPermissionEditGroup.html'"></div>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
<!-- Form controls -->
<div class="footer">
<button ng-click="save()">{{'manage.edit.user.save' | translate}}</button>
<button ng-click="close()">{{'manage.edit.user.cancel' | translate}}</button>
<button ng-click="delete()" class="danger">{{'manage.edit.user.delete' | translate}}</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,46 @@
<div>
<!--
Copyright 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.
-->
<script type="text/ng-template" id="nestedGroupSelect.html">
<div class="group" ng-show="!item.isConnection">
<div class="caption">
<div class="icon group type" ng-click="item.expanded = !item.expanded" ng-class="{expanded: item.expanded, empty: !item.children.length, balancer: item.balancer && !item.children.length}"></div>
<span class="name" ng-click="chooseParentID(item.identifier); toggleDropDown()">{{item.name}}</span>
</div>
<div class="children" ng-show="item.expanded">
<div class="list-item" ng-repeat="item in item.children | orderBy : 'name'" ng-include="'nestedGroupSelect.html'">
</div>
</div>
</script>
<!-- Open the dropdown -->
<div ng-click="toggleDropDown()" class="location">{{currentConnectionGroupName}}</div>
<div ng-show="showDropDown" class="dropdown">
<div class="group-view">
<div class="list">
<div class="list-item" ng-repeat="item in connectionGroups | orderBy : 'name'" ng-include="'nestedGroupSelect.html'"></div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,92 @@
<!--
Copyright 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.
-->
<script type="text/ng-template" id="nestedGroup.html">
<div class="connection" ng-click="editConnection(item)" ng-show="item.isConnection">
<div class="caption">
<div class="protocol">
<div class="icon type" ng-class="item.protocol"></div>
</div>
<span class="name">{{item.name}}</span>
</div>
</div>
<div class="group" ng-show="!item.isConnection">
<div class="caption">
<div class="icon group type" ng-click="toggleExpanded(item)" ng-class="{expanded: item.expanded, empty: !item.children.length, balancer: item.balancer && !item.children.length}"></div>
<span ng-click="editConnectionGroup(item)" class="name">
<span>{{item.name}}</span>
</span>
</div>
<div class="children" ng-show="item.expanded">
<div class="list-item" ng-repeat="item in item.children | orderBy : 'name'" ng-include="'nestedGroup.html'">
</div>
</div>
</script>
<div class="logout-panel">
<a class="back button" href="#/">{{'manage.back' | translate}}</a>
<a class="logout button" href="#/login">{{'home.logout' | translate}}</a>
</div>
<h2>{{'manage.administration' | translate}}</h2>
<div ng-show="currentUserHasUpdate" class="settings section">
<h3 class="require-manage-users">{{'manage.users' | translate}}</h3>
<div class="require-manage-users users">
<p>{{'manage.usersDescription' | translate}}</p>
<!-- Control to create a new user -->
<div class="user-add-form">
<input type="text" ng-model="newUsername" class="name username"/>
<button class="add-user" ng-click="newUser()">{{'manage.addUser' | translate}}</button>
</div>
<!-- List of users this user has access to -->
<div class="user-list">
<div ng-click="editUser(user)" ng-repeat="user in users | orderBy : 'username'" class="list-item">
<div class="caption">
<div class="icon user"></div>
<span class="name">{{user.username}}</span>
</div>
</div>
</div>
</div>
<h3 class="require-manage-connections">{{'manage.connections' | translate}}</h3>
<div class="require-manage-connections connections">
<p>{{'manage.connectionsDescription' | translate}}</p>
<!-- Control to create a new connection or group -->
<div class="connection-add-form">
<button ng-click="newConnection()" class="add-connection">{{'manage.newConnection' | translate}}</button>
<button ng-click="newConnectionGroup()" class="add-connection-group">{{'manage.newGroup' | translate}}</button>
</div>
<!-- List of connections and groups this user has access to -->
<div class="connection-list">
<div class="list-item" ng-repeat="item in connectionsAndGroups | orderBy : 'name'" ng-include="'nestedGroup.html'"></div>
</div>
</div>
</div>

View 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.
*/
/**
* A module for code relating to permissions.
*/
angular.module('permission', []);

View File

@@ -0,0 +1,73 @@
/*
* 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 checking if a specific permission exists
* in a given list of permissions.
*/
angular.module('permission').factory('permissionCheckService', [
function permissionCheckService() {
var service = {};
/**
* A service for checking if the given permission list contains the given
* permission, defined by the objectType, objectID, and permissionType.
* If the objectType or objectID are not passed, they will not be checked.
*
* For example, checkPermission(list, "CONNECTION", undefined, "READ") would
* check if the permission list contains permission to read any connection.
*
* @param {array} permissions The array of permissions to check.
* @param {string} objectType The object type for the permission.
* If not passed, this will not be checked.
* @param {string} objectID The ID of the object the permission is for.
* If not passed, this will not be checked.
* @param {string} permissionType The actual permission type to check for.
* @returns {boolean} True if the given permissions contain the requested permission, false otherwise.
*/
service.checkPermission = function checkPermission(permissions, objectType, objectID, permissionType) {
// Loop through all the permissions and check if any of them match the given parameters
for(var i = 0; i < permissions.length; i++) {
var permission = permissions[i];
if(objectType === "SYSTEM") {
// System permissions have no object ID, we only need to check the type.
if(permission.permissionType === permissionType)
return true;
}
else {
// Object permissions need to match the object ID and type if given.
if(permission.permissionType === permissionType &&
(!objectType || permission.objectType === objectType) &&
(!objectID || permission.objectID === objectID))
return true;
}
}
// Didn't find any that matched
return false;
}
return service;
}]);

View File

@@ -0,0 +1,115 @@
/*
* 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 DAO for permission operations agains the REST API.
*/
angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUtility',
function permissionDAO($http, localStorageUtility) {
var service = {};
/**
* Makes a request to the REST API to get the list of permissions for a given user,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} userID The ID of the user to retrieve the permissions for.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getPermissions = function getPermissions(userID) {
return $http.get("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken'));
};
/**
* Makes a request to the REST API to add a permission for a given user,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} userID The ID of the user to add the permission for.
* @param {object} permission The permission to add.
*
* @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);
};
/**
* Makes a request to the REST API to remove a permission for a given user,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} userID The ID of the user to remove the permission for.
* @param {object} permission The permission to remove.
*
* @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);
};
/**
* Makes a request to the REST API to modify the permissions for a given user,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} userID The ID of the user to remove the permission for.
* @param {array} permissionsToAdd The permissions to add.
* @param {array} permissionsToRemove The permissions to remove.
*
* @returns {promise} A promise for the HTTP call.
*/
service.patchPermissions = function patchPermissions(userID, permissionsToAdd, permissionsToRemove) {
var permissionPatch = [];
// Add all the add operations to the patch
for(var i = 0; i < permissionsToAdd.length; i++ ) {
permissionPatch.push({
op : "add",
path : userID,
value : permissionsToAdd[i]
});
}
// Add all the remove operations to the patch
for(var i = 0; i < permissionsToRemove.length; i++ ) {
permissionPatch.push({
op : "remove",
path : userID,
value : permissionsToRemove[i]
});
}
// Make the HTTP call
return $http({
method : 'PATCH',
url : "api/permission/?token=" + localStorageUtility.get('authToken'),
data : permissionPatch
});
}
return service;
}]);

View 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 the protocol functionality.
*/
angular.module('protocol', []);

View File

@@ -0,0 +1,41 @@
/*
* 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 DAO for protocol operations agains the REST API.
*/
angular.module('protocol').factory('protocolDAO', ['$http', function protocolDAO($http) {
var service = {};
/**
* Makes a request to the REST API to get the list of protocols,
* returning a promise that can be used for processing the results of the call.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getProtocols = function getProtocols() {
return $http.get("api/protocol");
};
return service;
}]);

View File

@@ -0,0 +1,100 @@
/*
* 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 DAO for connection operations agains the REST API.
*/
angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
function userDAO($http, localStorageUtility) {
var service = {};
/**
* Makes a request to the REST API to get the list of users,
* returning a promise that can be used for processing the results of the call.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getUsers = function getUsers() {
return $http.get("api/user?token=" + localStorageUtility.get('authToken'));
};
/**
* Makes a request to the REST API to get the list of users,
* returning a promise that can be used for processing the results of the call.
*
* @param {string} userID The ID of the user to retrieve.
*
* @returns {promise} A promise for the HTTP call.
*/
service.getUser = function getUser(userID) {
return $http.get("api/user/" + userID + "/?token=" + localStorageUtility.get('authToken'));
};
/**
* Makes a request to the REST API to delete a user,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} user The user to delete.
*
* @returns {promise} A promise for the HTTP call.
*/
service.deleteUser = function deleteUser(user) {
return $http['delete'](
"api/user/" + user.username +
"?token=" + localStorageUtility.get('authToken'));
};
/**
* Makes a request to the REST API to create a user,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} user The user to create.
*
* @returns {promise} A promise for the HTTP call.
*/
service.createUser = function createUser(user) {
return $http.post(
"api/user/"
+ "?token=" + localStorageUtility.get('authToken'),
user
);
}
/**
* Makes a request to the REST API to save a user,
* returning a promise that can be used for processing the results of the call.
*
* @param {object} user The user to update.
*
* @returns {promise} A promise for the HTTP call.
*/
service.saveUser = function saveUser(user) {
return $http.post(
"api/user/" + user.username +
"?token=" + localStorageUtility.get('authToken'),
user);
};
return service;
}]);

View File

@@ -0,0 +1,57 @@
/*
* 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 performing useful user related functionaltiy.
*/
angular.module('user').factory('userService', ['$injector', function userService($injector) {
var permissionCheckService = $injector.get('permissionCheckService');
var service = {};
/**
* Filters the list of users using the provided permissions.
*
* @param {array} users The user list.
*
* @param {object} permissionList The list of permissions to use
* when filtering.
*
* @param {object} permissionCriteria The required permission for each user.
*
* @return {array} The filtered list.
*/
service.filterUsersByPermission = function filterUsersByPermission(users, permissionList, permissionCriteria) {
for(var i = 0; i < users.length; i++) {
if(!permissionCheckService.checkPermission(permissionList,
"USER", user.username, permissionCriteria)) {
items.splice(i, 1);
continue;
}
}
return users;
};
return service;
}]);

View 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.
*/
/**
* A module for code relating to users.
*/
angular.module('user', []);

View File

@@ -0,0 +1,56 @@
/*
* 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 to help prepare objects from the REST API for display.
*/
angular.module('util').factory('displayObjectPreparationService', [function displayObjectPreparationService() {
var service = {};
/**
* Adds properties to the connection that will be useful for display.
*
* @param {object} connection The connection to add display properties to.
*/
service.prepareConnection = function prepareConnection(connection) {
// This is a connection
connection.isConnection = true;
};
/**
* Adds properties to the connection that will be useful for display.
*
* @param {object} connectionGroup The connection group to add display properties to.
*/
service.prepareConnectionGroup = function prepareConnectionGroup(connectionGroup) {
// This is not a connection
connectionGroup.isConnection = false;
connectionGroup.balancer = connectionGroup.type !== "ORGANIZATIONAL";
connectionGroup.expanded = false;
connectionGroup.children = [];
};
return service;
}]);

View File

@@ -0,0 +1,97 @@
/*
* 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;
}]);

View 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.
*/
/**
* A module for miscellaneous services and utilities that don't belong elsewhere.
*/
angular.module('util', ['ngCookies']);

View File

@@ -1,173 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<!--
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
<link rel="stylesheet" type="text/css" href="styles/ui.css"/>
<link rel="stylesheet" type="text/css" href="styles/client.css"/>
<link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>Guacamole ${project.version}</title>
</head>
<body>
<div id="main">
<!-- Display -->
<div class="displayOuter">
<div class="displayMiddle">
<div id="display">
</div>
</div>
</div>
</div>
<!-- Text input target -->
<div id="text-input"><div id="text-input-field"><div id="sent-history"></div><textarea rows="1" id="target"></textarea></div><div id="text-input-buttons"><button class="key" data-keysym="0xFFE3" data-sticky="true">Ctrl</button><button class="key" data-keysym="0xFFE9" data-sticky="true">Alt</button><button class="key" data-keysym="0xFF1B">Esc</button><button class="key" data-keysym="0xFF09">Tab</button></div></div>
<!-- Dimensional clone of viewport -->
<div id="viewportClone"/>
<!-- Notification area -->
<div id="notificationArea"/>
<!-- Menu -->
<div id="menu">
<h2 id="menu-title">Guacamole ${project.version}</h2>
<h3>Clipboard</h3>
<div class="content" id="clipboard-settings">
<p class="description">Text copied/cut within Guacamole will appear here. Changes to the text below will affect the remote clipboard.</p>
<textarea rows="10" cols="40" id="clipboard"></textarea>
</div>
<h3>Input method</h3>
<div class="content" id="keyboard-settings">
<!-- No IME -->
<div class="choice">
<label><input name="input-method" type="radio" value="ime-none" checked="checked" id="ime-none"/> None</label>
<p class="caption"><label for="ime-none">No input method is used. Keyboard input is accepted from
a connected, physical keyboard.</label></p>
</div>
<!-- Text input -->
<div class="choice">
<div class="figure"><label for="ime-text"><img src="images/settings/tablet-keys.png" alt=""/></label></div>
<label><input name="input-method" type="radio" value="ime-text" id="ime-text"/> Text input</label>
<p class="caption"><label for="ime-text">
Allow typing of text, and emulate keyboard events based on the
typed text. This is necessary for devices such as mobile phones that lack a physical keyboard.</label></p>
</div>
<!-- Guac OSK -->
<div class="choice">
<label><input name="input-method" type="radio" value="ime-osk" id="ime-osk"/> On-screen keyboard</label>
<p class="caption"><label for="ime-osk">Display and accept input from the built-in Guacamole on-screen
keyboard. The on-screen keyboard allows typing of key combinations that may otherwise be impossible
(such as Ctrl-Alt-Del).</label></p>
</div>
</div>
<h3>Mouse emulation mode</h3>
<div class="content" id="mouse-settings">
<p class="description">Determines how the remote mouse behaves with respect to touches.</p>
<!-- Touchscreen -->
<div class="choice">
<input name="mouse-mode" type="radio" value="absolute" checked="checked" id="absolute"/>
<div class="figure">
<label for="absolute"><img src="images/settings/touchscreen.png" alt=""/></label>
<p class="caption"><label for="absolute">Tap to click. The click occurs at the location of the touch.</label></p>
</div>
</div>
<!-- Touchpad -->
<div class="choice">
<input name="mouse-mode" type="radio" value="relative" id="relative"/>
<div class="figure">
<label for="relative"><img src="images/settings/touchpad.png" alt=""/></label>
<p class="caption"><label for="relative">Drag to move the mouse pointer and tap to click. The click occurs at the location of the pointer.</label></p>
</div>
</div>
</div>
<h3>Display</h3>
<div class="content">
<div id="zoom-settings">
<div id="zoom-out"><img src="images/settings/zoom-out.png" alt="-"/></div>
<div id="zoom-state">100%</div>
<div id="zoom-in"><img src="images/settings/zoom-in.png" alt="+"/></div>
</div>
<div><label><input type="checkbox" id="auto-fit" checked="checked"/> Automatically fit to browser window</label></div>
</div>
</div>
<!-- Images which should be preloaded -->
<div id="preload">
<img src="images/action-icons/guac-close.png"/>
<img src="images/progress.png"/>
</div>
<script type="text/javascript" src="scripts/lib/blob/blob.js"></script>
<script type="text/javascript" src="scripts/lib/filesaver/filesaver.js"></script>
<!-- guacamole-common-js -->
<script type="text/javascript" src="guacamole-common-js/all.min.js"></script>
<!-- guacamole-default-webapp scripts -->
<script type="text/javascript" src="scripts/session.js"></script>
<script type="text/javascript" src="scripts/history.js"></script>
<script type="text/javascript" src="scripts/service.js"></script>
<script type="text/javascript" src="scripts/guac-ui.js"></script>
<script type="text/javascript" src="scripts/client-ui.js"></script>
<!-- Init -->
<script type="text/javascript"> /* <![CDATA[ */
// Sanity check Guacamole JavaScript API version
if (Guacamole.API_VERSION !== "0.9.3")
GuacUI.Client.showStatus("Clear Your Cache", "An older version of Guacamole has been cached by your browser. Please clear your browser's cache and try again.");
// Start connect after control returns from onload (allow browser
// to consider the page loaded).
else
window.onload = function() {
window.setTimeout(GuacUI.Client.connect, 10);
};
/* ]]> */ </script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
<!--
Copyright 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.
-->
<!DOCTYPE html>
<html ng-app="index" ng-controller="indexController">
<head>
<link rel="icon" type="image/png" href="../images/guacamole-logo-64.png"/>
<title ng-bind="page.title | translate"></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<link rel="stylesheet" type="text/css" href="guacamole.css">
</head>
<body>
<div id="content" ng-view>
</div>
<script type="text/javascript" src="guacamole.js"></script>
</body>
</html>

View File

@@ -1,107 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<!--
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
<link rel="apple-touch-icon" type="image/png" href="images/guacamole-logo-144.png"/>
<link rel="stylesheet" type="text/css" href="styles/ui.css"/>
<link rel="stylesheet" type="text/css" href="styles/login.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi"/>
<title>Guacamole ${project.version}</title>
</head>
<body>
<div id="login-ui" style="display: none">
<div id="login-dialog-middle">
<div id="login-dialog">
<p id="login-error"></p>
<form id="login-form" action="#" method="post">
<div id="version">
Guacamole ${project.version}
</div>
<div id="login-fields">
<table>
<tr>
<th>Username</th>
<td><input type="text" name="username" id="username" autofocus="autofocus"/></td>
</tr>
<tr>
<th>Password</th>
<td><input type="password" name="password" id="password"/></td>
</tr>
</table>
<img class="logo" src="images/guac-mono-192.png" alt=""/>
</div>
<div id="buttons">
<input type="submit" name="login" id="login" value="Login"/>
</div>
</form>
</div>
</div>
</div>
<!-- Connection list UI -->
<div id="connection-list-ui" style="display: none">
<div id="logout-panel">
<button id="manage">Manage</button>
<button id="logout">Logout</button>
</div>
<h2>Recent Connections</h2>
<div id="recent-connections">
<p id="no-recent">No recent connections.</p>
</div>
<h2>All Connections</h2>
<div id="all-connections">
</div>
</div>
<!-- guacamole-common-js -->
<script type="text/javascript" src="guacamole-common-js/all.min.js"></script>
<script type="text/javascript" src="scripts/service.js"></script>
<script type="text/javascript" src="scripts/session.js"></script>
<script type="text/javascript" src="scripts/history.js"></script>
<script type="text/javascript" src="scripts/guac-ui.js"></script>
<script type="text/javascript" src="scripts/root-ui.js"></script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
/**
* Workaround to make defining and retrieving angular modules easier and more intuitive.
* https://gist.github.com/hiddentao/7300694
*/
(function(angular) {
var origMethod = angular.module;
var alreadyRegistered = {};
/**
* Register/fetch a module.
*
* @param name {string} module name.
* @param reqs {array} list of modules this module depends upon.
* @param configFn {function} config function to run when module loads (only applied for the first call to create this module).
* @returns {*} the created/existing module.
*/
angular.module = function(name, reqs, configFn) {
reqs = reqs || [];
var module = null;
if (alreadyRegistered[name]) {
module = origMethod(name);
module.requires.push.apply(module.requires, reqs);
} else {
module = origMethod(name, reqs, configFn);
alreadyRegistered[name] = module;
}
return module;
};
})(angular);

202
guacamole/src/main/webapp/lib/angular.js vendored Normal file
View File

@@ -0,0 +1,202 @@
/*!
AngularJS v1.2.8
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(Z,Q,r){'use strict';function F(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.2.8/"+(b?b+"/":"")+a;for(c=1;c<arguments.length;c++)a=a+(1==c?"?":"&")+"p"+(c-1)+"="+encodeURIComponent("function"==typeof arguments[c]?arguments[c].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[c]?"undefined":"string"!=typeof arguments[c]?JSON.stringify(arguments[c]):arguments[c]);return Error(a)}}function rb(b){if(null==b||Aa(b))return!1;var a=
b.length;return 1===b.nodeType&&a?!0:D(b)||K(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function q(b,a,c){var d;if(b)if(L(b))for(d in b)"prototype"==d||("length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d))||a.call(c,b[d],d);else if(b.forEach&&b.forEach!==q)b.forEach(a,c);else if(rb(b))for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function Pb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function Pc(b,
a,c){for(var d=Pb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function Qb(b){return function(a,c){b(c,a)}}function Za(){for(var b=ka.length,a;b;){b--;a=ka[b].charCodeAt(0);if(57==a)return ka[b]="A",ka.join("");if(90==a)ka[b]="0";else return ka[b]=String.fromCharCode(a+1),ka.join("")}ka.unshift("0");return ka.join("")}function Rb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function t(b){var a=b.$$hashKey;q(arguments,function(a){a!==b&&q(a,function(a,c){b[c]=a})});Rb(b,a);return b}function S(b){return parseInt(b,
10)}function Sb(b,a){return t(new (t(function(){},{prototype:b})),a)}function w(){}function Ba(b){return b}function $(b){return function(){return b}}function z(b){return"undefined"===typeof b}function B(b){return"undefined"!==typeof b}function X(b){return null!=b&&"object"===typeof b}function D(b){return"string"===typeof b}function sb(b){return"number"===typeof b}function La(b){return"[object Date]"===$a.call(b)}function K(b){return"[object Array]"===$a.call(b)}function L(b){return"function"===typeof b}
function ab(b){return"[object RegExp]"===$a.call(b)}function Aa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Qc(b){return!(!b||!(b.nodeName||b.on&&b.find))}function Rc(b,a,c){var d=[];q(b,function(b,g,f){d.push(a.call(c,b,g,f))});return d}function bb(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Ma(b,a){var c=bb(b,a);0<=c&&b.splice(c,1);return a}function fa(b,a){if(Aa(b)||b&&b.$evalAsync&&b.$watch)throw Na("cpws");if(a){if(b===
a)throw Na("cpi");if(K(b))for(var c=a.length=0;c<b.length;c++)a.push(fa(b[c]));else{c=a.$$hashKey;q(a,function(b,c){delete a[c]});for(var d in b)a[d]=fa(b[d]);Rb(a,c)}}else(a=b)&&(K(b)?a=fa(b,[]):La(b)?a=new Date(b.getTime()):ab(b)?a=RegExp(b.source):X(b)&&(a=fa(b,{})));return a}function Tb(b,a){a=a||{};for(var c in b)b.hasOwnProperty(c)&&("$"!==c.charAt(0)&&"$"!==c.charAt(1))&&(a[c]=b[c]);return a}function ua(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,
d;if(c==typeof a&&"object"==c)if(K(b)){if(!K(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ua(b[d],a[d]))return!1;return!0}}else{if(La(b))return La(a)&&b.getTime()==a.getTime();if(ab(b)&&ab(a))return b.toString()==a.toString();if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||Aa(b)||Aa(a)||K(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!L(b[d])){if(!ua(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==r&&!L(a[d]))return!1;return!0}return!1}
function Ub(){return Q.securityPolicy&&Q.securityPolicy.isActive||Q.querySelector&&!(!Q.querySelector("[ng-csp]")&&!Q.querySelector("[data-ng-csp]"))}function cb(b,a){var c=2<arguments.length?va.call(arguments,2):[];return!L(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(va.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Sc(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)?c=r:Aa(a)?c="$WINDOW":
a&&Q===a?c="$DOCUMENT":a&&(a.$evalAsync&&a.$watch)&&(c="$SCOPE");return c}function qa(b,a){return"undefined"===typeof b?r:JSON.stringify(b,Sc,a?" ":null)}function Vb(b){return D(b)?JSON.parse(b):b}function Oa(b){"function"===typeof b?b=!0:b&&0!==b.length?(b=x(""+b),b=!("f"==b||"0"==b||"false"==b||"no"==b||"n"==b||"[]"==b)):b=!1;return b}function ga(b){b=A(b).clone();try{b.empty()}catch(a){}var c=A("<div>").append(b).html();try{return 3===b[0].nodeType?x(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,
function(a,b){return"<"+x(b)})}catch(d){return x(c)}}function Wb(b){try{return decodeURIComponent(b)}catch(a){}}function Xb(b){var a={},c,d;q((b||"").split("&"),function(b){b&&(c=b.split("="),d=Wb(c[0]),B(d)&&(b=B(c[1])?Wb(c[1]):!0,a[d]?K(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Yb(b){var a=[];q(b,function(b,d){K(b)?q(b,function(b){a.push(wa(d,!0)+(!0===b?"":"="+wa(b,!0)))}):a.push(wa(d,!0)+(!0===b?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function tb(b){return wa(b,
!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function Tc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,f=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;q(f,function(a){f[a]=!0;c(Q.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(q(b.querySelectorAll("."+a),c),q(b.querySelectorAll("."+
a+"\\:"),c),q(b.querySelectorAll("["+a+"]"),c))});q(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):q(a.attributes,function(b){!e&&f[b.name]&&(e=a,g=b.value)})}});e&&a(e,g?[g]:[])}function Zb(b,a){var c=function(){b=A(b);if(b.injector()){var c=b[0]===Q?"document":ga(b);throw Na("btstrpd",c);}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=$b(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",
function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(Z&&!d.test(Z.name))return c();Z.name=Z.name.replace(d,"");Ca.resumeBootstrap=function(b){q(b,function(b){a.push(b)});c()}}function db(b,a){a=a||"_";return b.replace(Uc,function(b,d){return(d?a:"")+b.toLowerCase()})}function ub(b,a,c){if(!b)throw Na("areq",a||"?",c||"required");return b}function Pa(b,a,c){c&&K(b)&&(b=b[b.length-1]);ub(L(b),a,"not a function, got "+(b&&"object"==typeof b?
b.constructor.name||"Object":typeof b));return b}function xa(b,a){if("hasOwnProperty"===b)throw Na("badname",a);}function vb(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,g=a.length,f=0;f<g;f++)d=a[f],b&&(b=(e=b)[d]);return!c&&L(b)?cb(e,b):b}function wb(b){var a=b[0];b=b[b.length-1];if(a===b)return A(a);var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return A(c)}function Vc(b){var a=F("$injector"),c=F("ng");b=b.angular||(b.angular={});b.$$minErr=b.$$minErr||F;return b.module||
(b.module=function(){var b={};return function(e,g,f){if("hasOwnProperty"===e)throw c("badname","module");g&&b.hasOwnProperty(e)&&(b[e]=null);return b[e]||(b[e]=function(){function b(a,d,e){return function(){c[e||"push"]([a,d,arguments]);return n}}if(!g)throw a("nomod",e);var c=[],d=[],l=b("$injector","invoke"),n={_invokeQueue:c,_runBlocks:d,requires:g,name:e,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:b("$provide","value"),constant:b("$provide",
"constant","unshift"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:l,run:function(a){d.push(a);return this}};f&&l(f);return n}())}}())}function Qa(b){return b.replace(Wc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(Xc,"Moz$1")}function xb(b,a,c,d){function e(b){var e=c&&b?[this.filter(b)]:[this],m=a,k,l,n,p,s,C;if(!d||null!=b)for(;e.length;)for(k=e.shift(),
l=0,n=k.length;l<n;l++)for(p=A(k[l]),m?p.triggerHandler("$destroy"):m=!m,s=0,p=(C=p.children()).length;s<p;s++)e.push(Da(C[s]));return g.apply(this,arguments)}var g=Da.fn[b],g=g.$original||g;e.$original=g;Da.fn[b]=e}function O(b){if(b instanceof O)return b;if(!(this instanceof O)){if(D(b)&&"<"!=b.charAt(0))throw yb("nosel");return new O(b)}if(D(b)){var a=Q.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);zb(this,a.childNodes);A(Q.createDocumentFragment()).append(this)}else zb(this,
b)}function Ab(b){return b.cloneNode(!0)}function Ea(b){ac(b);var a=0;for(b=b.childNodes||[];a<b.length;a++)Ea(b[a])}function bc(b,a,c,d){if(B(d))throw yb("offargs");var e=la(b,"events");la(b,"handle")&&(z(a)?q(e,function(a,c){Bb(b,c,a);delete e[c]}):q(a.split(" "),function(a){z(c)?(Bb(b,a,e[a]),delete e[a]):Ma(e[a]||[],c)}))}function ac(b,a){var c=b[eb],d=Ra[c];d&&(a?delete Ra[c].data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),bc(b)),delete Ra[c],b[eb]=r))}function la(b,a,c){var d=
b[eb],d=Ra[d||-1];if(B(c))d||(b[eb]=d=++Yc,d=Ra[d]={}),d[a]=c;else return d&&d[a]}function cc(b,a,c){var d=la(b,"data"),e=B(c),g=!e&&B(a),f=g&&!X(a);d||f||la(b,"data",d={});if(e)d[a]=c;else if(g){if(f)return d&&d[a];t(d,a)}else return d}function Cb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function Db(b,a){a&&b.setAttribute&&q(a.split(" "),function(a){b.setAttribute("class",aa((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g,
" ").replace(" "+aa(a)+" "," ")))})}function Eb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(a.split(" "),function(a){a=aa(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",aa(c))}}function zb(b,a){if(a){a=a.nodeName||!B(a.length)||Aa(a)?[a]:a;for(var c=0;c<a.length;c++)b.push(a[c])}}function dc(b,a){return fb(b,"$"+(a||"ngController")+"Controller")}function fb(b,a,c){b=A(b);9==b[0].nodeType&&(b=b.find("html"));for(a=K(a)?a:[a];b.length;){for(var d=
0,e=a.length;d<e;d++)if((c=b.data(a[d]))!==r)return c;b=b.parent()}}function ec(b){for(var a=0,c=b.childNodes;a<c.length;a++)Ea(c[a]);for(;b.firstChild;)b.removeChild(b.firstChild)}function fc(b,a){var c=gb[a.toLowerCase()];return c&&gc[b.nodeName]&&c}function Zc(b,a){var c=function(c,e){c.preventDefault||(c.preventDefault=function(){c.returnValue=!1});c.stopPropagation||(c.stopPropagation=function(){c.cancelBubble=!0});c.target||(c.target=c.srcElement||Q);if(z(c.defaultPrevented)){var g=c.preventDefault;
c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||!1===c.returnValue};var f=Tb(a[e||c.type]||[]);q(f,function(a){a.call(b,c)});8>=M?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Fa(b){var a=typeof b,c;"object"==a&&null!==b?"function"==typeof(c=b.$$hashKey)?c=b.$$hashKey():c===
r&&(c=b.$$hashKey=Za()):c=b;return a+":"+c}function Sa(b){q(b,this.put,this)}function hc(b){var a,c;"function"==typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace($c,""),c=c.match(ad),q(c[1].split(bd),function(b){b.replace(cd,function(b,c,d){a.push(d)})})),b.$inject=a):K(b)?(c=b.length-1,Pa(b[c],"fn"),a=b.slice(0,c)):Pa(b,"fn",!0);return a}function $b(b){function a(a){return function(b,c){if(X(b))q(b,Qb(a));else return a(b,c)}}function c(a,b){xa(a,"service");if(L(b)||K(b))b=n.instantiate(b);
if(!b.$get)throw Ta("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,g,h;q(a,function(a){if(!k.get(a)){k.put(a,!0);try{if(D(a))for(c=Ua(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,g=0,h=d.length;g<h;g++){var f=d[g],m=n.get(f[0]);m[f[1]].apply(m,f[2])}else L(a)?b.push(n.invoke(a)):K(a)?b.push(n.invoke(a)):Pa(a,"module")}catch(s){throw K(a)&&(a=a[a.length-1]),s.message&&(s.stack&&-1==s.stack.indexOf(s.message))&&(s=s.message+"\n"+s.stack),
Ta("modulerr",a,s.stack||s.message||s);}}});return b}function g(a,b){function c(d){if(a.hasOwnProperty(d)){if(a[d]===f)throw Ta("cdep",m.join(" <- "));return a[d]}try{return m.unshift(d),a[d]=f,a[d]=b(d)}catch(e){throw a[d]===f&&delete a[d],e;}finally{m.shift()}}function d(a,b,e){var g=[],h=hc(a),f,k,m;k=0;for(f=h.length;k<f;k++){m=h[k];if("string"!==typeof m)throw Ta("itkn",m);g.push(e&&e.hasOwnProperty(m)?e[m]:c(m))}a.$inject||(a=a[f]);return a.apply(b,g)}return{invoke:d,instantiate:function(a,
b){var c=function(){},e;c.prototype=(K(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return X(e)||L(e)?e:c},get:c,annotate:hc,has:function(b){return l.hasOwnProperty(b+h)||a.hasOwnProperty(b)}}}var f={},h="Provider",m=[],k=new Sa,l={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,$(b))}),constant:a(function(a,b){xa(a,"constant");l[a]=b;p[a]=b}),decorator:function(a,b){var c=n.get(a+h),
d=c.$get;c.$get=function(){var a=s.invoke(d,c);return s.invoke(b,null,{$delegate:a})}}}},n=l.$injector=g(l,function(){throw Ta("unpr",m.join(" <- "));}),p={},s=p.$injector=g(p,function(a){a=n.get(a+h);return s.invoke(a.$get,a)});q(e(b),function(a){s.invoke(a||w)});return s}function dd(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;q(a,function(a){b||"a"!==x(a.nodeName)||(b=a)});return b}function g(){var b=
c.hash(),d;b?(d=f.getElementById(b))?d.scrollIntoView():(d=e(f.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var f=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(g)});return g}]}function ed(b,a,c,d){function e(a){try{a.apply(null,va.call(arguments,1))}finally{if(C--,0===C)for(;y.length;)try{y.pop()()}catch(b){c.error(b)}}}function g(a,b){(function T(){q(E,function(a){a()});u=b(T,a)})()}function f(){v=null;R!=h.url()&&(R=h.url(),q(ha,
function(a){a(h.url())}))}var h=this,m=a[0],k=b.location,l=b.history,n=b.setTimeout,p=b.clearTimeout,s={};h.isMock=!1;var C=0,y=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){C++};h.notifyWhenNoOutstandingRequests=function(a){q(E,function(a){a()});0===C?a():y.push(a)};var E=[],u;h.addPollFn=function(a){z(u)&&g(100,n);E.push(a);return a};var R=k.href,H=a.find("base"),v=null;h.url=function(a,c){k!==b.location&&(k=b.location);l!==b.history&&(l=b.history);if(a){if(R!=a)return R=
a,d.history?c?l.replaceState(null,"",a):(l.pushState(null,"",a),H.attr("href",H.attr("href"))):(v=a,c?k.replace(a):k.href=a),h}else return v||k.href.replace(/%27/g,"'")};var ha=[],N=!1;h.onUrlChange=function(a){if(!N){if(d.history)A(b).on("popstate",f);if(d.hashchange)A(b).on("hashchange",f);else h.addPollFn(f);N=!0}ha.push(a);return a};h.baseHref=function(){var a=H.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};var V={},J="",ba=h.baseHref();h.cookies=function(a,b){var d,e,g,h;if(a)b===
r?m.cookie=escape(a)+"=;path="+ba+";expires=Thu, 01 Jan 1970 00:00:00 GMT":D(b)&&(d=(m.cookie=escape(a)+"="+escape(b)+";path="+ba).length+1,4096<d&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(m.cookie!==J)for(J=m.cookie,d=J.split("; "),V={},g=0;g<d.length;g++)e=d[g],h=e.indexOf("="),0<h&&(a=unescape(e.substring(0,h)),V[a]===r&&(V[a]=unescape(e.substring(h+1))));return V}};h.defer=function(a,b){var c;C++;c=n(function(){delete s[c];
e(a)},b||0);s[c]=!0;return c};h.defer.cancel=function(a){return s[a]?(delete s[a],p(a),e(w),!0):!1}}function fd(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new ed(b,d,a,c)}]}function gd(){this.$get=function(){function b(b,d){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,g(a.n,a.p),g(a,n),n=a,n.n=null)}function g(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw F("$cacheFactory")("iid",b);var f=0,h=t({},d,{id:b}),m={},k=d&&d.capacity||Number.MAX_VALUE,l={},n=null,p=null;
return a[b]={put:function(a,b){var c=l[a]||(l[a]={key:a});e(c);if(!z(b))return a in m||f++,m[a]=b,f>k&&this.remove(p.key),b},get:function(a){var b=l[a];if(b)return e(b),m[a]},remove:function(a){var b=l[a];b&&(b==n&&(n=b.p),b==p&&(p=b.n),g(b.n,b.p),delete l[a],delete m[a],f--)},removeAll:function(){m={};f=0;l={};n=p=null},destroy:function(){l=h=m=null;delete a[b]},info:function(){return t({},h,{size:f})}}}var a={};b.info=function(){var b={};q(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};
return b}}function hd(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function jc(b,a){var c={},d="Directive",e=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,g=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,f=/^(on[a-z]+|formaction)$/;this.directive=function m(a,e){xa(a,"directive");D(a)?(ub(e,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+d,["$injector","$exceptionHandler",function(b,d){var e=[];q(c[a],function(c,g){try{var f=b.invoke(c);L(f)?f={compile:$(f)}:!f.compile&&f.link&&(f.compile=
$(f.link));f.priority=f.priority||0;f.index=g;f.name=f.name||a;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(m){d(m)}});return e}])),c[a].push(e)):q(a,Qb(m));return this};this.aHrefSanitizationWhitelist=function(b){return B(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};this.$get=["$injector","$interpolate",
"$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,l,n,p,s,C,y,E,u,R,H){function v(a,b,c,d,e){a instanceof A||(a=A(a));q(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=A(b).wrap("<span></span>").parent()[0])});var g=N(a,b,a,c,d,e);ha(a,"ng-scope");return function(b,c,d){ub(b,"scope");var e=c?Ga.clone.call(a):a;q(d,function(a,b){e.data("$"+b+"Controller",a)});d=0;for(var f=e.length;d<f;d++){var m=
e[d].nodeType;1!==m&&9!==m||e.eq(d).data("$scope",b)}c&&c(e,b);g&&g(b,e,e);return e}}function ha(a,b){try{a.addClass(b)}catch(c){}}function N(a,b,c,d,e,g){function f(a,c,d,e){var g,k,s,l,n,p,I;g=c.length;var C=Array(g);for(n=0;n<g;n++)C[n]=c[n];I=n=0;for(p=m.length;n<p;I++)k=C[I],c=m[n++],g=m[n++],s=A(k),c?(c.scope?(l=a.$new(),s.data("$scope",l)):l=a,(s=c.transclude)||!e&&b?c(g,l,k,d,V(a,s||b)):c(g,l,k,d,e)):g&&g(a,k.childNodes,r,e)}for(var m=[],k,s,l,n,p=0;p<a.length;p++)k=new Fb,s=J(a[p],[],k,0===
p?d:r,e),(g=s.length?ia(s,a[p],k,b,c,null,[],[],g):null)&&g.scope&&ha(A(a[p]),"ng-scope"),k=g&&g.terminal||!(l=a[p].childNodes)||!l.length?null:N(l,g?g.transclude:b),m.push(g,k),n=n||g||k,g=null;return n?f:null}function V(a,b){return function(c,d,e){var g=!1;c||(c=a.$new(),g=c.$$transcluded=!0);d=b(c,d,e);if(g)d.on("$destroy",cb(c,c.$destroy));return d}}function J(a,b,c,d,f){var k=c.$attr,m;switch(a.nodeType){case 1:T(b,ma(Ha(a).toLowerCase()),"E",d,f);var s,l,n;m=a.attributes;for(var p=0,C=m&&m.length;p<
C;p++){var y=!1,R=!1;s=m[p];if(!M||8<=M||s.specified){l=s.name;n=ma(l);W.test(n)&&(l=db(n.substr(6),"-"));var v=n.replace(/(Start|End)$/,"");n===v+"Start"&&(y=l,R=l.substr(0,l.length-5)+"end",l=l.substr(0,l.length-6));n=ma(l.toLowerCase());k[n]=l;c[n]=s=aa(s.value);fc(a,n)&&(c[n]=!0);S(a,b,s,n);T(b,n,"A",d,f,y,R)}}a=a.className;if(D(a)&&""!==a)for(;m=g.exec(a);)n=ma(m[2]),T(b,n,"C",d,f)&&(c[n]=aa(m[3])),a=a.substr(m.index+m[0].length);break;case 3:F(b,a.nodeValue);break;case 8:try{if(m=e.exec(a.nodeValue))n=
ma(m[1]),T(b,n,"M",d,f)&&(c[n]=aa(m[2]))}catch(E){}}b.sort(z);return b}function ba(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ja("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return A(d)}function P(a,b,c){return function(d,e,g,f,m){e=ba(e[0],b,c);return a(d,e,g,f,m)}}function ia(a,c,d,e,g,f,m,n,p){function y(a,b,c,d){if(a){c&&(a=P(a,c,d));a.require=G.require;if(H===G||G.$$isolateScope)a=
kc(a,{isolateScope:!0});m.push(a)}if(b){c&&(b=P(b,c,d));b.require=G.require;if(H===G||G.$$isolateScope)b=kc(b,{isolateScope:!0});n.push(b)}}function R(a,b,c){var d,e="data",g=!1;if(D(a)){for(;"^"==(d=a.charAt(0))||"?"==d;)a=a.substr(1),"^"==d&&(e="inheritedData"),g=g||"?"==d;d=null;c&&"data"===e&&(d=c[a]);d=d||b[e]("$"+a+"Controller");if(!d&&!g)throw ja("ctreq",a,ca);}else K(a)&&(d=[],q(a,function(a){d.push(R(a,b,c))}));return d}function E(a,e,g,f,p){function y(a,b){var c;2>arguments.length&&(b=a,
a=r);z&&(c=ba);return p(a,b,c)}var I,v,N,u,P,J,ba={},hb;I=c===g?d:Tb(d,new Fb(A(g),d.$attr));v=I.$$element;if(H){var T=/^\s*([@=&])(\??)\s*(\w*)\s*$/;f=A(g);J=e.$new(!0);ia&&ia===H.$$originalDirective?f.data("$isolateScope",J):f.data("$isolateScopeNoTemplate",J);ha(f,"ng-isolate-scope");q(H.scope,function(a,c){var d=a.match(T)||[],g=d[3]||c,f="?"==d[2],d=d[1],m,l,n,p;J.$$isolateBindings[c]=d+g;switch(d){case "@":I.$observe(g,function(a){J[c]=a});I.$$observers[g].$$scope=e;I[g]&&(J[c]=b(I[g])(e));
break;case "=":if(f&&!I[g])break;l=s(I[g]);p=l.literal?ua:function(a,b){return a===b};n=l.assign||function(){m=J[c]=l(e);throw ja("nonassign",I[g],H.name);};m=J[c]=l(e);J.$watch(function(){var a=l(e);p(a,J[c])||(p(a,m)?n(e,a=J[c]):J[c]=a);return m=a},null,l.literal);break;case "&":l=s(I[g]);J[c]=function(a){return l(e,a)};break;default:throw ja("iscp",H.name,c,a);}})}hb=p&&y;V&&q(V,function(a){var b={$scope:a===H||a.$$isolateScope?J:e,$element:v,$attrs:I,$transclude:hb},c;P=a.controller;"@"==P&&(P=
I[a.name]);c=C(P,b);ba[a.name]=c;z||v.data("$"+a.name+"Controller",c);a.controllerAs&&(b.$scope[a.controllerAs]=c)});f=0;for(N=m.length;f<N;f++)try{u=m[f],u(u.isolateScope?J:e,v,I,u.require&&R(u.require,v,ba),hb)}catch(G){l(G,ga(v))}f=e;H&&(H.template||null===H.templateUrl)&&(f=J);a&&a(f,g.childNodes,r,p);for(f=n.length-1;0<=f;f--)try{u=n[f],u(u.isolateScope?J:e,v,I,u.require&&R(u.require,v,ba),hb)}catch(B){l(B,ga(v))}}p=p||{};var N=-Number.MAX_VALUE,u,V=p.controllerDirectives,H=p.newIsolateScopeDirective,
ia=p.templateDirective;p=p.nonTlbTranscludeDirective;for(var T=!1,z=!1,t=d.$$element=A(c),G,ca,U,F=e,O,M=0,na=a.length;M<na;M++){G=a[M];var Va=G.$$start,S=G.$$end;Va&&(t=ba(c,Va,S));U=r;if(N>G.priority)break;if(U=G.scope)u=u||G,G.templateUrl||(x("new/isolated scope",H,G,t),X(U)&&(H=G));ca=G.name;!G.templateUrl&&G.controller&&(U=G.controller,V=V||{},x("'"+ca+"' controller",V[ca],G,t),V[ca]=G);if(U=G.transclude)T=!0,G.$$tlb||(x("transclusion",p,G,t),p=G),"element"==U?(z=!0,N=G.priority,U=ba(c,Va,S),
t=d.$$element=A(Q.createComment(" "+ca+": "+d[ca]+" ")),c=t[0],ib(g,A(va.call(U,0)),c),F=v(U,e,N,f&&f.name,{nonTlbTranscludeDirective:p})):(U=A(Ab(c)).contents(),t.empty(),F=v(U,e));if(G.template)if(x("template",ia,G,t),ia=G,U=L(G.template)?G.template(t,d):G.template,U=Y(U),G.replace){f=G;U=A("<div>"+aa(U)+"</div>").contents();c=U[0];if(1!=U.length||1!==c.nodeType)throw ja("tplrt",ca,"");ib(g,t,c);na={$attr:{}};U=J(c,[],na);var W=a.splice(M+1,a.length-(M+1));H&&ic(U);a=a.concat(U).concat(W);B(d,na);
na=a.length}else t.html(U);if(G.templateUrl)x("template",ia,G,t),ia=G,G.replace&&(f=G),E=w(a.splice(M,a.length-M),t,d,g,F,m,n,{controllerDirectives:V,newIsolateScopeDirective:H,templateDirective:ia,nonTlbTranscludeDirective:p}),na=a.length;else if(G.compile)try{O=G.compile(t,d,F),L(O)?y(null,O,Va,S):O&&y(O.pre,O.post,Va,S)}catch(Z){l(Z,ga(t))}G.terminal&&(E.terminal=!0,N=Math.max(N,G.priority))}E.scope=u&&!0===u.scope;E.transclude=T&&F;return E}function ic(a){for(var b=0,c=a.length;b<c;b++)a[b]=Sb(a[b],
{$$isolateScope:!0})}function T(b,e,g,f,k,s,n){if(e===k)return null;k=null;if(c.hasOwnProperty(e)){var p;e=a.get(e+d);for(var C=0,y=e.length;C<y;C++)try{p=e[C],(f===r||f>p.priority)&&-1!=p.restrict.indexOf(g)&&(s&&(p=Sb(p,{$$start:s,$$end:n})),b.push(p),k=p)}catch(v){l(v)}}return k}function B(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,g){"class"==g?(ha(e,b),a["class"]=(a["class"]?a["class"]+
" ":"")+b):"style"==g?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==g.charAt(0)||a.hasOwnProperty(g)||(a[g]=b,d[g]=c[g])})}function w(a,b,c,d,e,g,f,m){var k=[],s,l,C=b[0],y=a.shift(),v=t({},y,{templateUrl:null,transclude:null,replace:null,$$originalDirective:y}),R=L(y.templateUrl)?y.templateUrl(b,c):y.templateUrl;b.empty();n.get(u.getTrustedResourceUrl(R),{cache:p}).success(function(n){var p,E;n=Y(n);if(y.replace){n=A("<div>"+aa(n)+"</div>").contents();p=n[0];if(1!=
n.length||1!==p.nodeType)throw ja("tplrt",y.name,R);n={$attr:{}};ib(d,b,p);var u=J(p,[],n);X(y.scope)&&ic(u);a=u.concat(a);B(c,n)}else p=C,b.html(n);a.unshift(v);s=ia(a,p,c,e,b,y,g,f,m);q(d,function(a,c){a==p&&(d[c]=b[0])});for(l=N(b[0].childNodes,e);k.length;){n=k.shift();E=k.shift();var H=k.shift(),ha=k.shift(),u=b[0];E!==C&&(u=Ab(p),ib(H,A(E),u));E=s.transclude?V(n,s.transclude):ha;s(l,n,u,d,E)}k=null}).error(function(a,b,c,d){throw ja("tpload",d.url);});return function(a,b,c,d,e){k?(k.push(b),
k.push(c),k.push(d),k.push(e)):s(l,b,c,d,e)}}function z(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function x(a,b,c,d){if(b)throw ja("multidir",b.name,c.name,a,ga(d));}function F(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:$(function(a,b){var c=b.parent(),e=c.data("$binding")||[];e.push(d);ha(c.data("$binding",e),"ng-binding");a.$watch(d,function(a){b[0].nodeValue=a})})})}function O(a,b){if("srcdoc"==b)return u.HTML;var c=Ha(a);if("xlinkHref"==
b||"FORM"==c&&"action"==b||"IMG"!=c&&("src"==b||"ngSrc"==b))return u.RESOURCE_URL}function S(a,c,d,e){var g=b(d,!0);if(g){if("multiple"===e&&"SELECT"===Ha(a))throw ja("selmulti",ga(a));c.push({priority:100,compile:function(){return{pre:function(c,d,m){d=m.$$observers||(m.$$observers={});if(f.test(e))throw ja("nodomevents");if(g=b(m[e],!0,O(a,e)))m[e]=g(c),(d[e]||(d[e]=[])).$$inter=!0,(m.$$observers&&m.$$observers[e].$$scope||c).$watch(g,function(a,b){"class"===e&&a!=b?m.$updateClass(a,b):m.$set(e,
a)})}}}})}}function ib(a,b,c){var d=b[0],e=b.length,g=d.parentNode,f,m;if(a)for(f=0,m=a.length;f<m;f++)if(a[f]==d){a[f++]=c;m=f+e-1;for(var k=a.length;f<k;f++,m++)m<k?a[f]=a[m]:delete a[f];a.length-=e-1;break}g&&g.replaceChild(c,d);a=Q.createDocumentFragment();a.appendChild(d);c[A.expando]=d[A.expando];d=1;for(e=b.length;d<e;d++)g=b[d],A(g).remove(),a.appendChild(g),delete b[d];b[0]=c;b.length=1}function kc(a,b){return t(function(){return a.apply(null,arguments)},a,b)}var Fb=function(a,b){this.$$element=
a;this.$attr=b||{}};Fb.prototype={$normalize:ma,$addClass:function(a){a&&0<a.length&&R.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&R.removeClass(this.$$element,a)},$updateClass:function(a,b){this.$removeClass(lc(b,a));this.$addClass(lc(a,b))},$set:function(a,b,c,d){var e=fc(this.$$element[0],a);e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=db(a,"-"));e=Ha(this.$$element);if("A"===e&&"href"===a||"IMG"===e&&"src"===a)this[a]=
b=H(b,"src"===a);!1!==c&&(null===b||b===r?this.$$element.removeAttr(d):this.$$element.attr(d,b));(c=this.$$observers)&&q(c[a],function(a){try{a(b)}catch(c){l(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);y.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var ca=b.startSymbol(),na=b.endSymbol(),Y="{{"==ca||"}}"==na?Ba:function(a){return a.replace(/\{\{/g,ca).replace(/}}/g,na)},W=/^ngAttr[A-Z]/;return v}]}function ma(b){return Qa(b.replace(id,
""))}function lc(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),g=0;a:for(;g<d.length;g++){for(var f=d[g],h=0;h<e.length;h++)if(f==e[h])continue a;c+=(0<c.length?" ":"")+f}return c}function jd(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,d){xa(a,"controller");X(a)?t(b,a):b[a]=d};this.$get=["$injector","$window",function(c,d){return function(e,g){var f,h,m;D(e)&&(f=e.match(a),h=f[1],m=f[3],e=b.hasOwnProperty(h)?b[h]:vb(g.$scope,h,!0)||vb(d,h,!0),Pa(e,h,!0));f=c.instantiate(e,g);
if(m){if(!g||"object"!=typeof g.$scope)throw F("$controller")("noscp",h||e.name,m);g.$scope[m]=f}return f}}]}function kd(){this.$get=["$window",function(b){return A(b.document)}]}function ld(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function mc(b){var a={},c,d,e;if(!b)return a;q(b.split("\n"),function(b){e=b.indexOf(":");c=x(aa(b.substr(0,e)));d=aa(b.substr(e+1));c&&(a[c]=a[c]?a[c]+(", "+d):d)});return a}function nc(b){var a=X(b)?b:r;return function(c){a||
(a=mc(b));return c?a[x(c)]||null:a}}function oc(b,a,c){if(L(c))return c(b,a);q(c,function(c){b=c(b,a)});return b}function md(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults={transformResponse:[function(d){D(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=Vb(d)));return d}],transformRequest:[function(a){return X(a)&&"[object File]"!==$a.call(a)?qa(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,
put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],f=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,n,p){function s(a){function c(a){var b=t({},a,{data:oc(a.data,a.headers,d.transformResponse)});return 200<=a.status&&300>a.status?b:n.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},g=function(a){function b(a){var c;q(a,function(b,
d){L(b)&&(c=b(),null!=c?a[d]=c:delete a[d])})}var c=e.headers,d=t({},a.headers),g,f,c=t({},c.common,c[x(a.method)]);b(c);b(d);a:for(g in c){a=x(g);for(f in d)if(x(f)===a)continue a;d[g]=c[g]}return d}(a);t(d,a);d.headers=g;d.method=Ia(d.method);(a=Gb(d.url)?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:r)&&(g[d.xsrfHeaderName||e.xsrfHeaderName]=a);var f=[function(a){g=a.headers;var b=oc(a.data,nc(g),a.transformRequest);z(a.data)&&q(g,function(a,b){"content-type"===x(b)&&delete g[b]});z(a.withCredentials)&&
!z(e.withCredentials)&&(a.withCredentials=e.withCredentials);return C(a,b,g).then(c,c)},r],h=n.when(d);for(q(u,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var k=f.shift(),h=h.then(a,k)}h.success=function(a){h.then(function(b){a(b.data,b.status,b.headers,d)});return h};h.error=function(a){h.then(null,function(b){a(b.data,b.status,b.headers,d)});return h};return h}function C(b,
c,g){function f(a,b,c){u&&(200<=a&&300>a?u.put(r,[a,b,mc(c)]):u.remove(r));m(b,a,c);d.$$phase||d.$apply()}function m(a,c,d){c=Math.max(c,0);(200<=c&&300>c?p.resolve:p.reject)({data:a,status:c,headers:nc(d),config:b})}function k(){var a=bb(s.pendingRequests,b);-1!==a&&s.pendingRequests.splice(a,1)}var p=n.defer(),C=p.promise,u,q,r=y(b.url,b.params);s.pendingRequests.push(b);C.then(k,k);(b.cache||e.cache)&&(!1!==b.cache&&"GET"==b.method)&&(u=X(b.cache)?b.cache:X(e.cache)?e.cache:E);if(u)if(q=u.get(r),
B(q)){if(q.then)return q.then(k,k),q;K(q)?m(q[1],q[0],fa(q[2])):m(q,200,{})}else u.put(r,C);z(q)&&a(b.method,r,c,f,g,b.timeout,b.withCredentials,b.responseType);return C}function y(a,b){if(!b)return a;var c=[];Pc(b,function(a,b){null===a||z(a)||(K(a)||(a=[a]),q(a,function(a){X(a)&&(a=qa(a));c.push(wa(b)+"="+wa(a))}))});return a+(-1==a.indexOf("?")?"?":"&")+c.join("&")}var E=c("$http"),u=[];q(g,function(a){u.unshift(D(a)?p.get(a):p.invoke(a))});q(f,function(a,b){var c=D(a)?p.get(a):p.invoke(a);u.splice(b,
0,{response:function(a){return c(n.when(a))},responseError:function(a){return c(n.reject(a))}})});s.pendingRequests=[];(function(a){q(arguments,function(a){s[a]=function(b,c){return s(t(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){s[a]=function(b,c,d){return s(t(d||{},{method:a,url:b,data:c}))}})})("post","put");s.defaults=e;return s}]}function nd(b){return 8>=M&&"patch"===x(b)?new ActiveXObject("Microsoft.XMLHTTP"):new Z.XMLHttpRequest}function od(){this.$get=
["$browser","$window","$document",function(b,a,c){return pd(b,nd,b.defer,a.angular.callbacks,c[0])}]}function pd(b,a,c,d,e){function g(a,b){var c=e.createElement("script"),d=function(){c.onreadystatechange=c.onload=c.onerror=null;e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;M&&8>=M?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=function(){d()};e.body.appendChild(c);return d}var f=-1;return function(e,m,k,l,n,p,s,C){function y(){u=f;
H&&H();v&&v.abort()}function E(a,d,e,g){r&&c.cancel(r);H=v=null;d=0===d?e?200:404:d;a(1223==d?204:d,e,g);b.$$completeOutstandingRequest(w)}var u;b.$$incOutstandingRequestCount();m=m||b.url();if("jsonp"==x(e)){var R="_"+(d.counter++).toString(36);d[R]=function(a){d[R].data=a};var H=g(m.replace("JSON_CALLBACK","angular.callbacks."+R),function(){d[R].data?E(l,200,d[R].data):E(l,u||-2);d[R]=Ca.noop})}else{var v=a(e);v.open(e,m,!0);q(n,function(a,b){B(a)&&v.setRequestHeader(b,a)});v.onreadystatechange=
function(){if(v&&4==v.readyState){var a=null,b=null;u!==f&&(a=v.getAllResponseHeaders(),b="response"in v?v.response:v.responseText);E(l,u||v.status,b,a)}};s&&(v.withCredentials=!0);C&&(v.responseType=C);v.send(k||null)}if(0<p)var r=c(y,p);else p&&p.then&&p.then(y)}}function qd(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function g(g,k,l){for(var n,p,s=0,C=[],
y=g.length,E=!1,u=[];s<y;)-1!=(n=g.indexOf(b,s))&&-1!=(p=g.indexOf(a,n+f))?(s!=n&&C.push(g.substring(s,n)),C.push(s=c(E=g.substring(n+f,p))),s.exp=E,s=p+h,E=!0):(s!=y&&C.push(g.substring(s)),s=y);(y=C.length)||(C.push(""),y=1);if(l&&1<C.length)throw pc("noconcat",g);if(!k||E)return u.length=y,s=function(a){try{for(var b=0,c=y,f;b<c;b++)"function"==typeof(f=C[b])&&(f=f(a),f=l?e.getTrusted(l,f):e.valueOf(f),null===f||z(f)?f="":"string"!=typeof f&&(f=qa(f))),u[b]=f;return u.join("")}catch(h){a=pc("interr",
g,h.toString()),d(a)}},s.exp=g,s.parts=C,s}var f=b.length,h=a.length;g.startSymbol=function(){return b};g.endSymbol=function(){return a};return g}]}function rd(){this.$get=["$rootScope","$window","$q",function(b,a,c){function d(d,f,h,m){var k=a.setInterval,l=a.clearInterval,n=c.defer(),p=n.promise,s=0,C=B(m)&&!m;h=B(h)?h:0;p.then(null,null,d);p.$$intervalId=k(function(){n.notify(s++);0<h&&s>=h&&(n.resolve(s),l(p.$$intervalId),delete e[p.$$intervalId]);C||b.$apply()},f);e[p.$$intervalId]=n;return p}
var e={};d.cancel=function(a){return a&&a.$$intervalId in e?(e[a.$$intervalId].reject("canceled"),clearInterval(a.$$intervalId),delete e[a.$$intervalId],!0):!1};return d}]}function sd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),
SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function qc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=
tb(b[a]);return b.join("/")}function rc(b,a,c){b=ya(b,c);a.$$protocol=b.protocol;a.$$host=b.hostname;a.$$port=S(b.port)||td[b.protocol]||null}function sc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=ya(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=Xb(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function oa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Wa(b){var a=
b.indexOf("#");return-1==a?b:b.substr(0,a)}function Hb(b){return b.substr(0,Wa(b).lastIndexOf("/")+1)}function tc(b,a){this.$$html5=!0;a=a||"";var c=Hb(b);rc(b,this,b);this.$$parse=function(a){var e=oa(c,a);if(!D(e))throw Ib("ipthprfx",a,c);sc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Yb(this.$$search),b=this.$$hash?"#"+tb(this.$$hash):"";this.$$url=qc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;
if((e=oa(b,d))!==r)return d=e,(e=oa(a,e))!==r?c+(oa("/",e)||e):b+d;if((e=oa(c,d))!==r)return c+e;if(c==d+"/")return c}}function Jb(b,a){var c=Hb(b);rc(b,this,b);this.$$parse=function(d){var e=oa(b,d)||oa(c,d),e="#"==e.charAt(0)?oa(a,e):this.$$html5?e:"";if(!D(e))throw Ib("ihshprfx",d,a);sc(e,this,b);d=this.$$path;var g=/^\/?.*?:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));g.exec(e)||(d=(e=g.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Yb(this.$$search),e=this.$$hash?
"#"+tb(this.$$hash):"";this.$$url=qc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$rewrite=function(a){if(Wa(b)==Wa(a))return a}}function uc(b,a){this.$$html5=!0;Jb.apply(this,arguments);var c=Hb(b);this.$$rewrite=function(d){var e;if(b==Wa(d))return d;if(e=oa(c,d))return b+a+e;if(c===d+"/")return c}}function jb(b){return function(){return this[b]}}function vc(b,a){return function(c){if(z(c))return this[b];this[b]=a(c);this.$$compose();return this}}function ud(){var b=
"",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function f(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,m=d.baseHref(),k=d.url();a?(m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/"),e=e.history?tc:uc):(m=Wa(k),e=Jb);h=new e(m,"#"+b);h.$$parse(h.$$rewrite(k));g.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=
A(a.target);"a"!==x(b[0].nodeName);)if(b[0]===g[0]||!(b=b.parent())[0])return;var e=b.prop("href");X(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ya(e.animVal).href);var f=h.$$rewrite(e);e&&(!b.attr("target")&&f&&!a.isDefaultPrevented())&&(a.preventDefault(),f!=d.url()&&(h.$$parse(f),c.$apply(),Z.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",
a,b).defaultPrevented?(h.$$parse(b),d.url(b)):f(b)}),c.$$phase||c.$digest())});var l=0;c.$watch(function(){var a=d.url(),b=h.$$replace;l&&a==h.absUrl()||(l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),f(a))}));h.$$replace=!1;return l});return h}]}function vd(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&
-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||w;a=!1;try{a=!!e.apply}catch(m){}return a?function(){var a=[];q(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function da(b,
a){if("constructor"===b)throw za("isecfld",a);return b}function Xa(b,a){if(b){if(b.constructor===b)throw za("isecfn",a);if(b.document&&b.location&&b.alert&&b.setInterval)throw za("isecwindow",a);if(b.children&&(b.nodeName||b.on&&b.find))throw za("isecdom",a);}return b}function kb(b,a,c,d,e){e=e||{};a=a.split(".");for(var g,f=0;1<a.length;f++){g=da(a.shift(),d);var h=b[g];h||(h={},b[g]=h);b=h;b.then&&e.unwrapPromises&&(ra(d),"$$v"in b||function(a){a.then(function(b){a.$$v=b})}(b),b.$$v===r&&(b.$$v=
{}),b=b.$$v)}g=da(a.shift(),d);return b[g]=c}function wc(b,a,c,d,e,g,f){da(b,g);da(a,g);da(c,g);da(d,g);da(e,g);return f.unwrapPromises?function(f,m){var k=m&&m.hasOwnProperty(b)?m:f,l;if(null==k)return k;(k=k[b])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);if(!a)return k;if(null==k)return r;(k=k[a])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);if(!c)return k;if(null==k)return r;(k=k[c])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=
a})),k=k.$$v);if(!d)return k;if(null==k)return r;(k=k[d])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);if(!e)return k;if(null==k)return r;(k=k[e])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);return k}:function(g,f){var k=f&&f.hasOwnProperty(b)?f:g;if(null==k)return k;k=k[b];if(!a)return k;if(null==k)return r;k=k[a];if(!c)return k;if(null==k)return r;k=k[c];if(!d)return k;if(null==k)return r;k=k[d];return e?null==k?r:k=k[e]:k}}function wd(b,
a){da(b,a);return function(a,d){return null==a?r:(d&&d.hasOwnProperty(b)?d:a)[b]}}function xd(b,a,c){da(b,c);da(a,c);return function(c,e){if(null==c)return r;c=(e&&e.hasOwnProperty(b)?e:c)[b];return null==c?r:c[a]}}function xc(b,a,c){if(Kb.hasOwnProperty(b))return Kb[b];var d=b.split("."),e=d.length,g;if(a.unwrapPromises||1!==e)if(a.unwrapPromises||2!==e)if(a.csp)g=6>e?wc(d[0],d[1],d[2],d[3],d[4],c,a):function(b,g){var f=0,h;do h=wc(d[f++],d[f++],d[f++],d[f++],d[f++],c,a)(b,g),g=r,b=h;while(f<e);
return h};else{var f="var p;\n";q(d,function(b,d){da(b,c);f+="if(s == null) return undefined;\ns="+(d?"s":'((k&&k.hasOwnProperty("'+b+'"))?k:s)')+'["'+b+'"];\n'+(a.unwrapPromises?'if (s && s.then) {\n pw("'+c.replace(/(["\r\n])/g,"\\$1")+'");\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n':"")});var f=f+"return s;",h=new Function("s","k","pw",f);h.toString=$(f);g=a.unwrapPromises?function(a,b){return h(a,b,ra)}:h}else g=xd(d[0],d[1],c);else g=
wd(d[0],c);"hasOwnProperty"!==b&&(Kb[b]=g);return g}function yd(){var b={},a={csp:!1,unwrapPromises:!1,logPromiseWarnings:!0};this.unwrapPromises=function(b){return B(b)?(a.unwrapPromises=!!b,this):a.unwrapPromises};this.logPromiseWarnings=function(b){return B(b)?(a.logPromiseWarnings=b,this):a.logPromiseWarnings};this.$get=["$filter","$sniffer","$log",function(c,d,e){a.csp=d.csp;ra=function(b){a.logPromiseWarnings&&!yc.hasOwnProperty(b)&&(yc[b]=!0,e.warn("[$parse] Promise found in the expression `"+
b+"`. Automatic unwrapping of promises in Angular expressions is deprecated."))};return function(d){var e;switch(typeof d){case "string":if(b.hasOwnProperty(d))return b[d];e=new Lb(a);e=(new Ya(e,c,a)).parse(d,!1);"hasOwnProperty"!==d&&(b[d]=e);return e;case "function":return d;default:return w}}}]}function zd(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Ad(function(a){b.$evalAsync(a)},a)}]}function Ad(b,a){function c(a){return a}function d(a){return f(a)}var e=function(){var h=
[],m,k;return k={resolve:function(a){if(h){var c=h;h=r;m=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],m.then(a[0],a[1],a[2])})}},reject:function(a){k.resolve(f(a))},notify:function(a){if(h){var c=h;h.length&&b(function(){for(var b,d=0,e=c.length;d<e;d++)b=c[d],b[2](a)})}},promise:{then:function(b,g,f){var k=e(),C=function(d){try{k.resolve((L(b)?b:c)(d))}catch(e){k.reject(e),a(e)}},y=function(b){try{k.resolve((L(g)?g:d)(b))}catch(c){k.reject(c),a(c)}},E=function(b){try{k.notify((L(f)?
f:c)(b))}catch(d){a(d)}};h?h.push([C,y,E]):m.then(C,y,E);return k.promise},"catch":function(a){return this.then(null,a)},"finally":function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,g){var f=null;try{f=(a||c)()}catch(h){return b(h,!1)}return f&&L(f.then)?f.then(function(){return b(e,g)},function(a){return b(a,!1)}):b(e,g)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},g=function(a){return a&&L(a.then)?a:{then:function(c){var d=
e();b(function(){d.resolve(c(a))});return d.promise}}},f=function(c){return{then:function(g,f){var l=e();b(function(){try{l.resolve((L(f)?f:d)(c))}catch(b){l.reject(b),a(b)}});return l.promise}}};return{defer:e,reject:f,when:function(h,m,k,l){var n=e(),p,s=function(b){try{return(L(m)?m:c)(b)}catch(d){return a(d),f(d)}},C=function(b){try{return(L(k)?k:d)(b)}catch(c){return a(c),f(c)}},y=function(b){try{return(L(l)?l:c)(b)}catch(d){a(d)}};b(function(){g(h).then(function(a){p||(p=!0,n.resolve(g(a).then(s,
C,y)))},function(a){p||(p=!0,n.resolve(C(a)))},function(a){p||n.notify(y(a))})});return n.promise},all:function(a){var b=e(),c=0,d=K(a)?[]:{};q(a,function(a,e){c++;g(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise}}}function Bd(){var b=10,a=F("$rootScope"),c=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(d,
e,g,f){function h(){this.$id=Za();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$listenerCount={};this.$$isolateBindings={}}function m(b){if(p.$$phase)throw a("inprog",p.$$phase);p.$$phase=b}function k(a,b){var c=g(a);Pa(c,b);return c}function l(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&
delete a.$$listenerCount[c];while(a=a.$parent)}function n(){}h.prototype={constructor:h,$new:function(a){a?(a=new h,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue,a.$$postDigestQueue=this.$$postDigestQueue):(a=function(){},a.prototype=this,a=new a,a.$id=Za());a["this"]=a;a.$$listeners={};a.$$listenerCount={};a.$parent=this;a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=
this.$$childTail=a;return a},$watch:function(a,b,d){var e=k(a,"watch"),g=this.$$watchers,f={fn:b,last:n,get:e,exp:a,eq:!!d};c=null;if(!L(b)){var h=k(b||w,"listener");f.fn=function(a,b,c){h(c)}}if("string"==typeof a&&e.constant){var m=f.fn;f.fn=function(a,b,c){m.call(this,a,b,c);Ma(g,f)}}g||(g=this.$$watchers=[]);g.unshift(f);return function(){Ma(g,f);c=null}},$watchCollection:function(a,b){var c=this,d,e,f=0,h=g(a),m=[],k={},l=0;return this.$watch(function(){e=h(c);var a,b;if(X(e))if(rb(e))for(d!==
m&&(d=m,l=d.length=0,f++),a=e.length,l!==a&&(f++,d.length=l=a),b=0;b<a;b++)d[b]!==e[b]&&(f++,d[b]=e[b]);else{d!==k&&(d=k={},l=0,f++);a=0;for(b in e)e.hasOwnProperty(b)&&(a++,d.hasOwnProperty(b)?d[b]!==e[b]&&(f++,d[b]=e[b]):(l++,d[b]=e[b],f++));if(l>a)for(b in f++,d)d.hasOwnProperty(b)&&!e.hasOwnProperty(b)&&(l--,delete d[b])}else d!==e&&(d=e,f++);return f},function(){b(e,d,c)})},$digest:function(){var d,f,g,h,k=this.$$asyncQueue,l=this.$$postDigestQueue,q,v,r=b,N,V=[],J,A,P;m("$digest");c=null;do{v=
!1;for(N=this;k.length;){try{P=k.shift(),P.scope.$eval(P.expression)}catch(B){p.$$phase=null,e(B)}c=null}a:do{if(h=N.$$watchers)for(q=h.length;q--;)try{if(d=h[q])if((f=d.get(N))!==(g=d.last)&&!(d.eq?ua(f,g):"number"==typeof f&&"number"==typeof g&&isNaN(f)&&isNaN(g)))v=!0,c=d,d.last=d.eq?fa(f):f,d.fn(f,g===n?f:g,N),5>r&&(J=4-r,V[J]||(V[J]=[]),A=L(d.exp)?"fn: "+(d.exp.name||d.exp.toString()):d.exp,A+="; newVal: "+qa(f)+"; oldVal: "+qa(g),V[J].push(A));else if(d===c){v=!1;break a}}catch(t){p.$$phase=
null,e(t)}if(!(h=N.$$childHead||N!==this&&N.$$nextSibling))for(;N!==this&&!(h=N.$$nextSibling);)N=N.$parent}while(N=h);if(v&&!r--)throw p.$$phase=null,a("infdig",b,qa(V));}while(v||k.length);for(p.$$phase=null;l.length;)try{l.shift()()}catch(z){e(z)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this!==p&&(q(this.$$listenerCount,cb(null,l,this)),a.$$childHead==this&&(a.$$childHead=this.$$nextSibling),a.$$childTail==this&&(a.$$childTail=
this.$$prevSibling),this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling),this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling),this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a){p.$$phase||p.$$asyncQueue.length||f.defer(function(){p.$$asyncQueue.length&&p.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},
$apply:function(a){try{return m("$apply"),this.$eval(a)}catch(b){e(b)}finally{p.$$phase=null;try{p.$digest()}catch(c){throw e(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){c[bb(c,b)]=null;l(e,1,a)}},$emit:function(a,b){var c=[],d,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=
!0},defaultPrevented:!1},m=[h].concat(va.call(arguments,1)),k,l;do{d=f.$$listeners[a]||c;h.currentScope=f;k=0;for(l=d.length;k<l;k++)if(d[k])try{d[k].apply(null,m)}catch(p){e(p)}else d.splice(k,1),k--,l--;if(g)break;f=f.$parent}while(f);return h},$broadcast:function(a,b){for(var c=this,d=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(va.call(arguments,1)),h,k;c=d;){f.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,
g)}catch(m){e(m)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}return f}};var p=new h;return p}]}function Cd(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*(https?|ftp|file):|data:image\//;this.aHrefSanitizationWhitelist=function(a){return B(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,g;if(!M||8<=
M)if(g=ya(c).href,""!==g&&!g.match(e))return"unsafe:"+g;return c}}}function Dd(b){if("self"===b)return b;if(D(b)){if(-1<b.indexOf("***"))throw sa("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return RegExp("^"+b+"$")}if(ab(b))return RegExp("^"+b.source+"$");throw sa("imatcher");}function zc(b){var a=[];B(b)&&q(b,function(b){a.push(Dd(b))});return a}function Ed(){this.SCE_CONTEXTS=ea;var b=["self"],a=[];this.resourceUrlWhitelist=
function(a){arguments.length&&(b=zc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=zc(b));return a};this.$get=["$injector",function(c){function d(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var e=function(a){throw sa("unsafe");};c.has("$sanitize")&&(e=c.get("$sanitize"));
var g=d(),f={};f[ea.HTML]=d(g);f[ea.CSS]=d(g);f[ea.URL]=d(g);f[ea.JS]=d(g);f[ea.RESOURCE_URL]=d(f[ea.URL]);return{trustAs:function(a,b){var c=f.hasOwnProperty(a)?f[a]:null;if(!c)throw sa("icontext",a,b);if(null===b||b===r||""===b)return b;if("string"!==typeof b)throw sa("itype",a);return new c(b)},getTrusted:function(c,d){if(null===d||d===r||""===d)return d;var g=f.hasOwnProperty(c)?f[c]:null;if(g&&d instanceof g)return d.$$unwrapTrustedValue();if(c===ea.RESOURCE_URL){var g=ya(d.toString()),l,n,p=
!1;l=0;for(n=b.length;l<n;l++)if("self"===b[l]?Gb(g):b[l].exec(g.href)){p=!0;break}if(p)for(l=0,n=a.length;l<n;l++)if("self"===a[l]?Gb(g):a[l].exec(g.href)){p=!1;break}if(p)return d;throw sa("insecurl",d.toString());}if(c===ea.HTML)return e(d);throw sa("unsafe");},valueOf:function(a){return a instanceof g?a.$$unwrapTrustedValue():a}}}]}function Fd(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sniffer","$sceDelegate",function(a,c,d){if(b&&c.msie&&8>c.msieDocumentMode)throw sa("iequirks");
var e=fa(ea);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=Ba);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:function(a,c){return e.getTrusted(b,d(a,c))}};var g=e.parseAs,f=e.getTrusted,h=e.trustAs;q(ea,function(a,b){var c=x(b);e[Qa("parse_as_"+c)]=function(b){return g(a,b)};e[Qa("get_trusted_"+c)]=function(b){return f(a,b)};e[Qa("trust_as_"+c)]=function(b){return h(a,
b)}});return e}]}function Gd(){this.$get=["$window","$document",function(b,a){var c={},d=S((/android (\d+)/.exec(x((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),g=a[0]||{},f=g.documentMode,h,m=/^(Moz|webkit|O|ms)(?=[A-Z])/,k=g.body&&g.body.style,l=!1,n=!1;if(k){for(var p in k)if(l=m.exec(p)){h=l[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||h+"Transition"in k);n=!!("animation"in k||h+"Animation"in
k);!d||l&&n||(l=D(g.body.style.webkitTransition),n=D(g.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!f||7<f),hasEvent:function(a){if("input"==a&&9==M)return!1;if(z(c[a])){var b=g.createElement("div");c[a]="on"+a in b}return c[a]},csp:Ub(),vendorPrefix:h,transitions:l,animations:n,android:d,msie:M,msieDocumentMode:f}}]}function Hd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,h,
m){var k=c.defer(),l=k.promise,n=B(m)&&!m;h=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),d(a)}finally{delete g[l.$$timeoutId]}n||b.$apply()},h);l.$$timeoutId=h;g[h]=k;return l}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),delete g[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return e}]}function ya(b,a){var c=b;M&&(Y.setAttribute("href",c),c=Y.href);Y.setAttribute("href",c);return{href:Y.href,protocol:Y.protocol?Y.protocol.replace(/:$/,
""):"",host:Y.host,search:Y.search?Y.search.replace(/^\?/,""):"",hash:Y.hash?Y.hash.replace(/^#/,""):"",hostname:Y.hostname,port:Y.port,pathname:"/"===Y.pathname.charAt(0)?Y.pathname:"/"+Y.pathname}}function Gb(b){b=D(b)?ya(b):b;return b.protocol===Ac.protocol&&b.host===Ac.host}function Id(){this.$get=$(Z)}function Bc(b){function a(d,e){if(X(d)){var g={};q(d,function(b,c){g[c]=a(c,b)});return g}return b.factory(d+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+
c)}}];a("currency",Cc);a("date",Dc);a("filter",Jd);a("json",Kd);a("limitTo",Ld);a("lowercase",Md);a("number",Ec);a("orderBy",Fc);a("uppercase",Nd)}function Jd(){return function(b,a,c){if(!K(b))return b;var d=typeof c,e=[];e.check=function(a){for(var b=0;b<e.length;b++)if(!e[b](a))return!1;return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return Ca.equals(a,b)}:function(a,b){b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var g=function(a,b){if("string"==typeof b&&"!"===
b.charAt(0))return!g(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if("$"!==d.charAt(0)&&g(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(g(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var f in a)(function(b){"undefined"!=typeof a[b]&&e.push(function(c){return g("$"==b?c:
vb(c,b),a[b])})})(f);break;case "function":e.push(a);break;default:return b}d=[];for(f=0;f<b.length;f++){var h=b[f];e.check(h)&&d.push(h)}return d}}function Cc(b){var a=b.NUMBER_FORMATS;return function(b,d){z(d)&&(d=a.CURRENCY_SYM);return Gc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Ec(b){var a=b.NUMBER_FORMATS;return function(b,d){return Gc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Gc(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=0>b;b=Math.abs(b);
var f=b+"",h="",m=[],k=!1;if(-1!==f.indexOf("e")){var l=f.match(/([\d\.]+)e(-?)(\d+)/);l&&"-"==l[2]&&l[3]>e+1?f="0":(h=f,k=!0)}if(k)0<e&&(-1<b&&1>b)&&(h=b.toFixed(e));else{f=(f.split(Hc)[1]||"").length;z(e)&&(e=Math.min(Math.max(a.minFrac,f),a.maxFrac));f=Math.pow(10,e);b=Math.round(b*f)/f;b=(""+b).split(Hc);f=b[0];b=b[1]||"";var l=0,n=a.lgSize,p=a.gSize;if(f.length>=n+p)for(l=f.length-n,k=0;k<l;k++)0===(l-k)%p&&0!==k&&(h+=c),h+=f.charAt(k);for(k=l;k<f.length;k++)0===(f.length-k)%n&&0!==k&&(h+=c),
h+=f.charAt(k);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,e))}m.push(g?a.negPre:a.posPre);m.push(h);m.push(g?a.negSuf:a.posSuf);return m.join("")}function Mb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function W(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Mb(e,a,d)}}function lb(b,a){return function(c,d){var e=c["get"+b](),g=Ia(a?"SHORT"+b:b);return d[g][e]}}function Dc(b){function a(a){var b;
if(b=a.match(c)){a=new Date(0);var g=0,f=0,h=b[8]?a.setUTCFullYear:a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=S(b[9]+b[10]),f=S(b[9]+b[11]));h.call(a,S(b[1]),S(b[2])-1,S(b[3]));g=S(b[4]||0)-g;f=S(b[5]||0)-f;h=S(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));m.call(a,g,f,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",f=[],h,m;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;D(c)&&
(c=Od.test(c)?S(c):a(c));sb(c)&&(c=new Date(c));if(!La(c))return c;for(;e;)(m=Pd.exec(e))?(f=f.concat(va.call(m,1)),e=f.pop()):(f.push(e),e=null);q(f,function(a){h=Qd[a];g+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Kd(){return function(b){return qa(b,!0)}}function Ld(){return function(b,a){if(!K(b)&&!D(b))return b;a=S(a);if(D(b))return a?0<=a?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,
e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Fc(b){return function(a,c,d){function e(a,b){return Oa(b)?function(b,c){return a(c,b)}:a}if(!K(a)||!c)return a;c=K(c)?c:[c];c=Rc(c,function(a){var c=!1,d=a||Ba;if(D(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c="-"==a.charAt(0),a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),g=typeof c,f=typeof e;g==f?("string"==g&&(c=c.toLowerCase(),e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=g<f?-1:1;return c},c)});for(var g=
[],f=0;f<a.length;f++)g.push(a[f]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function ta(b){L(b)&&(b={link:b});b.restrict=b.restrict||"AC";return $(b)}function Ic(b,a){function c(a,c){c=c?"-"+db(c,"-"):"";b.removeClass((a?mb:nb)+c).addClass((a?nb:mb)+c)}var d=this,e=b.parent().controller("form")||ob,g=0,f=d.$error={},h=[];d.$name=a.name||a.ngForm;d.$dirty=!1;d.$pristine=!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(Ja);c(!0);
d.$addControl=function(a){xa(a.$name,"input");h.push(a);a.$name&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];q(f,function(b,c){d.$setValidity(c,!0,a)});Ma(h,a)};d.$setValidity=function(a,b,h){var n=f[a];if(b)n&&(Ma(n,h),n.length||(g--,g||(c(b),d.$valid=!0,d.$invalid=!1),f[a]=!1,c(!0,a),e.$setValidity(a,!0,d)));else{g||c(b);if(n){if(-1!=bb(n,h))return}else f[a]=n=[],g++,c(!1,a),e.$setValidity(a,!1,d);n.push(h);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(Ja).addClass(pb);
d.$dirty=!0;d.$pristine=!1;e.$setDirty()};d.$setPristine=function(){b.removeClass(pb).addClass(Ja);d.$dirty=!1;d.$pristine=!0;q(h,function(a){a.$setPristine()})}}function pa(b,a,c,d){b.$setValidity(a,c);return c?d:r}function qb(b,a,c,d,e,g){if(!e.android){var f=!1;a.on("compositionstart",function(a){f=!0});a.on("compositionend",function(){f=!1})}var h=function(){if(!f){var e=a.val();Oa(c.ngTrim||"T")&&(e=aa(e));d.$viewValue!==e&&(b.$$phase?d.$setViewValue(e):b.$apply(function(){d.$setViewValue(e)}))}};
if(e.hasEvent("input"))a.on("input",h);else{var m,k=function(){m||(m=g.defer(function(){h();m=null}))};a.on("keydown",function(a){a=a.keyCode;91===a||(15<a&&19>a||37<=a&&40>=a)||k()});if(e.hasEvent("paste"))a.on("paste cut",k)}a.on("change",h);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var l=c.ngPattern;l&&((e=l.match(/^\/(.*)\/([gim]*)$/))?(l=RegExp(e[1],e[2]),e=function(a){return pa(d,"pattern",d.$isEmpty(a)||l.test(a),a)}):e=function(c){var e=b.$eval(l);if(!e||!e.test)throw F("ngPattern")("noregexp",
l,e,ga(a));return pa(d,"pattern",d.$isEmpty(c)||e.test(c),c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var n=S(c.ngMinlength);e=function(a){return pa(d,"minlength",d.$isEmpty(a)||a.length>=n,a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var p=S(c.ngMaxlength);e=function(a){return pa(d,"maxlength",d.$isEmpty(a)||a.length<=p,a)};d.$parsers.push(e);d.$formatters.push(e)}}function Nb(b,a){b="ngClass"+b;return function(){return{restrict:"AC",link:function(c,d,e){function g(b){if(!0===
a||c.$index%2===a){var d=f(b||"");h?ua(b,h)||e.$updateClass(d,f(h)):e.$addClass(d)}h=fa(b)}function f(a){if(K(a))return a.join(" ");if(X(a)){var b=[];q(a,function(a,c){a&&b.push(c)});return b.join(" ")}return a}var h;c.$watch(e[b],g,!0);e.$observe("class",function(a){g(c.$eval(e[b]))});"ngClass"!==b&&c.$watch("$index",function(d,g){var h=d&1;if(h!==g&1){var n=f(c.$eval(e[b]));h===a?e.$addClass(n):e.$removeClass(n)}})}}}}var x=function(b){return D(b)?b.toLowerCase():b},Ia=function(b){return D(b)?b.toUpperCase():
b},M,A,Da,va=[].slice,Rd=[].push,$a=Object.prototype.toString,Na=F("ng"),Ca=Z.angular||(Z.angular={}),Ua,Ha,ka=["0","0","0"];M=S((/msie (\d+)/.exec(x(navigator.userAgent))||[])[1]);isNaN(M)&&(M=S((/trident\/.*; rv:(\d+)/.exec(x(navigator.userAgent))||[])[1]));w.$inject=[];Ba.$inject=[];var aa=function(){return String.prototype.trim?function(b){return D(b)?b.trim():b}:function(b){return D(b)?b.replace(/^\s\s*/,"").replace(/\s\s*$/,""):b}}();Ha=9>M?function(b){b=b.nodeName?b:b[0];return b.scopeName&&
"HTML"!=b.scopeName?Ia(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var Uc=/[A-Z]/g,Sd={full:"1.2.8",major:1,minor:2,dot:8,codeName:"interdimensional-cartography"},Ra=O.cache={},eb=O.expando="ng-"+(new Date).getTime(),Yc=1,Jc=Z.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},Bb=Z.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+
a,c)},Wc=/([\:\-\_]+(.))/g,Xc=/^moz([A-Z])/,yb=F("jqLite"),Ga=O.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===Q.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),O(Z).on("load",a))},toString:function(){var b=[];q(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?A(this[b]):A(this[this.length+b])},length:0,push:Rd,sort:[].sort,splice:[].splice},gb={};q("multiple selected checked disabled readOnly required open".split(" "),function(b){gb[x(b)]=
b});var gc={};q("input select option textarea button form details".split(" "),function(b){gc[Ia(b)]=!0});q({data:cc,inheritedData:fb,scope:function(b){return A(b).data("$scope")||fb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return A(b).data("$isolateScope")||A(b).data("$isolateScopeNoTemplate")},controller:dc,injector:function(b){return fb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Cb,css:function(b,a,c){a=Qa(a);if(B(c))b.style[a]=c;else{var d;
8>=M&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=M&&(d=""===d?r:d);return d}},attr:function(b,a,c){var d=x(a);if(gb[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||w).specified?d:r;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?r:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:function(){function b(b,d){var e=a[b.nodeType];if(z(d))return e?
b[e]:"";b[e]=d}var a=[];9>M?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,a){if(z(a)){if("SELECT"===Ha(b)&&b.multiple){var c=[];q(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(z(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)Ea(d[c]);b.innerHTML=a},empty:ec},function(b,a){O.prototype[a]=function(a,d){var e,g;if(b!==ec&&(2==b.length&&b!==Cb&&b!==
dc?a:d)===r){if(X(a)){for(e=0;e<this.length;e++)if(b===cc)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}e=b.$dv;g=e===r?Math.min(this.length,1):this.length;for(var f=0;f<g;f++){var h=b(this[f],a,d);e=e?e+h:h}return e}for(e=0;e<this.length;e++)b(this[e],a,d);return this}});q({removeData:ac,dealoc:Ea,on:function a(c,d,e,g){if(B(g))throw yb("onargs");var f=la(c,"events"),h=la(c,"handle");f||la(c,"events",f={});h||la(c,"handle",h=Zc(c,f));q(d.split(" "),function(d){var g=f[d];if(!g){if("mouseenter"==
d||"mouseleave"==d){var l=Q.body.contains||Q.body.compareDocumentPosition?function(a,c){var d=9===a.nodeType?a.documentElement:a,e=c&&c.parentNode;return a===e||!!(e&&1===e.nodeType&&(d.contains?d.contains(e):a.compareDocumentPosition&&a.compareDocumentPosition(e)&16))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};f[d]=[];a(c,{mouseleave:"mouseout",mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;c&&(c===this||l(this,c))||h(a,d)})}else Jc(c,d,h),f[d]=[];g=f[d]}g.push(e)})},
off:bc,one:function(a,c,d){a=A(a);a.on(c,function g(){a.off(c,d);a.off(c,g)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;Ea(a);q(new O(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];q(a.childNodes,function(a){1===a.nodeType&&c.push(a)});return c},contents:function(a){return a.childNodes||[]},append:function(a,c){q(new O(c),function(c){1!==a.nodeType&&11!==a.nodeType||a.appendChild(c)})},prepend:function(a,c){if(1===a.nodeType){var d=
a.firstChild;q(new O(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=A(c)[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){Ea(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;q(new O(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:Eb,removeClass:Db,toggleClass:function(a,c,d){z(d)&&(d=!Cb(a,c));(d?Eb:Db)(a,c)},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;
for(a=a.nextSibling;null!=a&&1!==a.nodeType;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Ab,triggerHandler:function(a,c,d){c=(la(a,"events")||{})[c];d=d||[];var e=[{preventDefault:w,stopPropagation:w}];q(c,function(c){c.apply(a,e.concat(d))})}},function(a,c){O.prototype[c]=function(c,e,g){for(var f,h=0;h<this.length;h++)z(f)?(f=a(this[h],c,e,g),B(f)&&(f=A(f))):zb(f,a(this[h],c,e,g));return B(f)?f:this};O.prototype.bind=O.prototype.on;
O.prototype.unbind=O.prototype.off});Sa.prototype={put:function(a,c){this[Fa(a)]=c},get:function(a){return this[Fa(a)]},remove:function(a){var c=this[a=Fa(a)];delete this[a];return c}};var ad=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,bd=/,/,cd=/^\s*(_?)(\S+?)\1\s*$/,$c=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ta=F("$injector"),Td=F("$animate"),Ud=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=c+"-animation";if(c&&"."!=c.charAt(0))throw Td("notcsel",c);this.$$selectors[c.substr(1)]=
e;a.factory(e,d)};this.classNameFilter=function(a){1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null);return this.$$classNameFilter};this.$get=["$timeout",function(a){return{enter:function(d,e,g,f){g?g.after(d):(e&&e[0]||(e=g.parent()),e.append(d));f&&a(f,0,!1)},leave:function(d,e){d.remove();e&&a(e,0,!1)},move:function(a,c,g,f){this.enter(a,c,g,f)},addClass:function(d,e,g){e=D(e)?e:K(e)?e.join(" "):"";q(d,function(a){Eb(a,e)});g&&a(g,0,!1)},removeClass:function(d,e,g){e=D(e)?
e:K(e)?e.join(" "):"";q(d,function(a){Db(a,e)});g&&a(g,0,!1)},enabled:w}}]}],ja=F("$compile");jc.$inject=["$provide","$$sanitizeUriProvider"];var id=/^(x[\:\-_]|data[\:\-_])/i,pc=F("$interpolate"),Vd=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,td={http:80,https:443,ftp:21},Ib=F("$location");uc.prototype=Jb.prototype=tc.prototype={$$html5:!1,$$replace:!1,absUrl:jb("$$absUrl"),url:function(a,c){if(z(a))return this.$$url;var d=Vd.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));(d[2]||d[1])&&this.search(d[3]||
"");this.hash(d[5]||"",c);return this},protocol:jb("$$protocol"),host:jb("$$host"),port:jb("$$port"),path:vc("$$path",function(a){return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(D(a))this.$$search=Xb(a);else if(X(a))this.$$search=a;else throw Ib("isrcharg");break;default:z(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:vc("$$hash",Ba),replace:function(){this.$$replace=!0;return this}};
var za=F("$parse"),yc={},ra,Ka={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:w,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:r},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":w,"===":function(a,c,d,e){return d(a,c)===e(a,c)},
"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},
"!":function(a,c,d){return!d(a,c)}},Wd={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Lb=function(a){this.options=a};Lb.prototype={constructor:Lb,lex:function(a){this.text=a;this.index=0;this.ch=r;this.lastCh=":";this.tokens=[];var c;for(a=[];this.index<this.text.length;){this.ch=this.text.charAt(this.index);if(this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent(),this.was("{,")&&
("{"===a[0]&&(c=this.tokens[this.tokens.length-1]))&&(c.json=-1===c.text.indexOf("."));else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch,json:this.was(":[,")&&this.is("{[")||this.is("}]:,")}),this.is("{[")&&a.unshift(this.ch),this.is("}]")&&a.shift(),this.index++;else if(this.isWhitespace(this.ch)){this.index++;continue}else{var d=this.ch+this.peek(),e=d+this.peek(2),g=Ka[this.ch],f=Ka[d],h=Ka[e];h?(this.tokens.push({index:this.index,text:e,fn:h}),this.index+=3):f?(this.tokens.push({index:this.index,
text:d,fn:f}),this.index+=2):g?(this.tokens.push({index:this.index,text:this.ch,fn:g,json:this.was("[,:")&&this.is("+-")}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+1)}this.lastCh=this.ch}return this.tokens},is:function(a){return-1!==a.indexOf(this.ch)},was:function(a){return-1!==a.indexOf(this.lastCh)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===
a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=B(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw za("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=x(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=
this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}a*=1;this.tokens.push({index:c,text:a,json:!0,fn:function(){return a}})},readIdent:function(){for(var a=this,c="",d=this.index,e,g,f,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===
h&&(e=this.index),c+=h;else break;this.index++}if(e)for(g=this.index;g<this.text.length;){h=this.text.charAt(g);if("("===h){f=c.substr(e-d+1);c=c.substr(0,e-d);this.index=g;break}if(this.isWhitespace(h))g++;else break}d={index:d,text:c};if(Ka.hasOwnProperty(c))d.fn=Ka[c],d.json=Ka[c];else{var m=xc(c,this.options,this.text);d.fn=t(function(a,c){return m(a,c)},{assign:function(d,e){return kb(d,c,e,a.text,a.options)}})}this.tokens.push(d);f&&(this.tokens.push({index:e,text:".",json:!1}),this.tokens.push({index:e+
1,text:f,json:!1}))},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,g=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),e=e+f;if(g)"u"===f?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d=(g=Wd[f])?d+g:d+f,g=!1;else if("\\"===f)g=!0;else{if(f===a){this.index++;this.tokens.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});
return}d+=f}this.index++}this.throwError("Unterminated quote",c)}};var Ya=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d};Ya.ZERO=function(){return 0};Ya.prototype={constructor:Ya,parse:function(a,c){this.text=a;this.json=c;this.tokens=this.lexer.lex(a);c&&(this.assignment=this.logicalOR,this.functionCall=this.fieldAccess=this.objectIndex=this.filterChain=function(){this.throwError("is not valid json",{text:a,index:0})});var d=c?this.primary():this.statements();0!==this.tokens.length&&
this.throwError("is an unexpected token",this.tokens[0]);d.literal=!!d.literal;d.constant=!!d.constant;return d},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();else if(this.expect("{"))a=this.object();else{var c=this.expect();(a=c.fn)||this.throwError("not a primary expression",c);c.json&&(a.constant=!0,a.literal=!0)}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?
(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw za("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw za("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){if(0<this.tokens.length){var g=this.tokens[0],f=g.text;if(f===a||f===c||f===d||f===e||!(a||c||d||e))return g}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,
e))?(this.json&&!a.json&&this.throwError("is not valid json",a),this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return t(function(d,e){return a(d,e,c)},{constant:c.constant})},ternaryFn:function(a,c,d){return t(function(e,g){return a(e,g)?c(e,g):d(e,g)},{constant:a.constant&&c.constant&&d.constant})},binaryFn:function(a,c,d){return t(function(e,g){return c(e,g,a,d)},{constant:a.constant&&d.constant})},
statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,g=0;g<a.length;g++){var f=a[g];f&&(e=f(c,d))}return e}},filterChain:function(){for(var a=this.expression(),c;;)if(c=this.expect("|"))a=this.binaryFn(a,c.fn,this.filter());else return a},filter:function(){for(var a=this.expect(),c=this.$filter(a.text),d=[];;)if(a=this.expect(":"))d.push(this.expression());else{var e=
function(a,e,h){h=[h];for(var m=0;m<d.length;m++)h.push(d[m](a,e));return c.apply(a,h)};return function(){return e}}},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+this.text.substring(0,d.index)+"] can not be assigned to",d),c=this.ternary(),function(d,g){return a.assign(d,c(d,g),g)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.ternary();
if(d=this.expect(":"))return this.ternaryFn(a,c,this.ternary());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;;)if(c=this.expect("||"))a=this.binaryFn(a,c.fn,this.logicalAND());else return a},logicalAND:function(){var a=this.equality(),c;if(c=this.expect("&&"))a=this.binaryFn(a,c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(),c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},
relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(Ya.ZERO,a.fn,
this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=xc(d,this.options,this.text);return t(function(c,d,h){return e(h||a(c,d),d)},{assign:function(e,f,h){return kb(a(e,h),d,f,c.text,c.options)}})},objectIndex:function(a){var c=this,d=this.expression();this.consume("]");return t(function(e,g){var f=a(e,g),h=d(e,g),m;if(!f)return r;(f=Xa(f[h],c.text))&&(f.then&&c.options.unwrapPromises)&&(m=f,"$$v"in f||(m.$$v=r,
m.then(function(a){m.$$v=a})),f=f.$$v);return f},{assign:function(e,g,f){var h=d(e,f);return Xa(a(e,f),c.text)[h]=g}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this;return function(g,f){for(var h=[],m=c?c(g,f):g,k=0;k<d.length;k++)h.push(d[k](g,f));k=a(g,f,m)||w;Xa(m,e.text);Xa(k,e.text);h=k.apply?k.apply(m,h):k(h[0],h[1],h[2],h[3],h[4]);return Xa(h,e.text)}},arrayDeclaration:function(){var a=
[],c=!0;if("]"!==this.peekToken().text){do{var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return t(function(c,d){for(var f=[],h=0;h<a.length;h++)f.push(a[h](c,d));return f},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{var d=this.expect(),d=d.string||d.text;this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return t(function(c,d){for(var e=
{},m=0;m<a.length;m++){var k=a[m];e[k.key]=k.value(c,d)}return e},{literal:!0,constant:c})}};var Kb={},sa=F("$sce"),ea={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},Y=Q.createElement("a"),Ac=ya(Z.location.href,!0);Bc.$inject=["$provide"];Cc.$inject=["$locale"];Ec.$inject=["$locale"];var Hc=".",Qd={yyyy:W("FullYear",4),yy:W("FullYear",2,0,!0),y:W("FullYear",1),MMMM:lb("Month"),MMM:lb("Month",!0),MM:W("Month",2,1),M:W("Month",1,1),dd:W("Date",2),d:W("Date",1),HH:W("Hours",2),
H:W("Hours",1),hh:W("Hours",2,-12),h:W("Hours",1,-12),mm:W("Minutes",2),m:W("Minutes",1),ss:W("Seconds",2),s:W("Seconds",1),sss:W("Milliseconds",3),EEEE:lb("Day"),EEE:lb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Mb(Math[0<a?"floor":"ceil"](a/60),2)+Mb(Math.abs(a%60),2))}},Pd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,Od=/^\-?\d+$/;Dc.$inject=["$locale"];var Md=$(x),Nd=
$(Ia);Fc.$inject=["$parse"];var Xd=$({restrict:"E",compile:function(a,c){8>=M&&(c.href||c.name||c.$set("href",""),a.append(Q.createComment("IE fix")));if(!c.href&&!c.name)return function(a,c){c.on("click",function(a){c.attr("href")||a.preventDefault()})}}}),Ob={};q(gb,function(a,c){if("multiple"!=a){var d=ma("ng-"+c);Ob[d]=function(){return{priority:100,link:function(a,g,f){a.$watch(f[d],function(a){f.$set(c,!!a)})}}}}});q(["src","srcset","href"],function(a){var c=ma("ng-"+a);Ob[c]=function(){return{priority:99,
link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),M&&e.prop(a,g[a]))})}}}});var ob={$addControl:w,$removeControl:w,$setValidity:w,$setDirty:w,$setPristine:w};Ic.$inject=["$element","$attrs","$scope"];var Kc=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:Ic,compile:function(){return{pre:function(a,e,g,f){if(!g.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};Jc(e[0],"submit",h);e.on("$destroy",function(){c(function(){Bb(e[0],
"submit",h)},0,!1)})}var m=e.parent().controller("form"),k=g.name||g.ngForm;k&&kb(a,k,f,k);if(m)e.on("$destroy",function(){m.$removeControl(f);k&&kb(a,k,r,k);t(f,ob)})}}}}}]},Yd=Kc(),Zd=Kc(!0),$d=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,ae=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/,be=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Lc={text:qb,number:function(a,c,d,e,g,f){qb(a,c,d,e,g,f);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||be.test(a))return e.$setValidity("number",
!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return r});e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=parseFloat(d.min);return pa(e,"min",e.$isEmpty(a)||a>=c,a)},e.$parsers.push(a),e.$formatters.push(a));d.max&&(a=function(a){var c=parseFloat(d.max);return pa(e,"max",e.$isEmpty(a)||a<=c,a)},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){return pa(e,"number",e.$isEmpty(a)||sb(a),a)})},url:function(a,c,d,e,g,f){qb(a,
c,d,e,g,f);a=function(a){return pa(e,"url",e.$isEmpty(a)||$d.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,f){qb(a,c,d,e,g,f);a=function(a){return pa(e,"email",e.$isEmpty(a)||ae.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){z(d.name)&&c.attr("name",Za());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,
c,d,e){var g=d.ngTrueValue,f=d.ngFalseValue;D(g)||(g=!0);D(f)||(f=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==g};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:f})},hidden:w,button:w,submit:w,reset:w},Mc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,f){f&&(Lc[x(g.type)]||Lc.text)(d,e,g,f,c,a)}}}],
nb="ng-valid",mb="ng-invalid",Ja="ng-pristine",pb="ng-dirty",ce=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function f(a,c){c=c?"-"+db(c,"-"):"";e.removeClass((a?mb:nb)+c).addClass((a?nb:mb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var h=g(d.ngModel),m=h.assign;if(!m)throw F("ngModel")("nonassign",d.ngModel,ga(e));
this.$render=w;this.$isEmpty=function(a){return z(a)||""===a||null===a||a!==a};var k=e.inheritedData("$formController")||ob,l=0,n=this.$error={};e.addClass(Ja);f(!0);this.$setValidity=function(a,c){n[a]!==!c&&(c?(n[a]&&l--,l||(f(!0),this.$valid=!0,this.$invalid=!1)):(f(!1),this.$invalid=!0,this.$valid=!1,l++),n[a]=!c,f(c,a),k.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(pb).addClass(Ja)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&
(this.$dirty=!0,this.$pristine=!1,e.removeClass(Ja).addClass(pb),k.$setDirty());q(this.$parsers,function(a){d=a(d)});this.$modelValue!==d&&(this.$modelValue=d,m(a,d),q(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var p=this;a.$watch(function(){var c=h(a);if(p.$modelValue!==c){var d=p.$formatters,e=d.length;for(p.$modelValue=c;e--;)c=d[e](c);p.$viewValue!==c&&(p.$viewValue=c,p.$render())}return c})}],de=function(){return{require:["ngModel","^?form"],controller:ce,link:function(a,
c,d,e){var g=e[0],f=e[1]||ob;f.$addControl(g);a.$on("$destroy",function(){f.$removeControl(g)})}}},ee=$({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Nc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},
fe=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){if(!z(a)){var c=[];a&&q(a.split(g),function(a){a&&c.push(aa(a))});return c}});e.$formatters.push(function(a){return K(a)?a.join(", "):r});e.$isEmpty=function(a){return!a||!a.length}}}},ge=/^(true|false|\d+)$/,he=function(){return{priority:100,compile:function(a,c){return ge.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,
c,g){a.$watch(g.ngValue,function(a){g.$set("value",a)})}}}},ie=ta(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==r?"":a)})}),je=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],ke=["$sce","$parse",function(a,c){return function(d,e,g){e.addClass("ng-binding").data("$binding",g.ngBindHtml);var f=c(g.ngBindHtml);
d.$watch(function(){return(f(d)||"").toString()},function(c){e.html(a.getTrustedHtml(f(d))||"")})}}],le=Nb("",!0),me=Nb("Odd",0),ne=Nb("Even",1),oe=ta({compile:function(a,c){c.$set("ngCloak",r);a.removeClass("ng-cloak")}}),pe=[function(){return{scope:!0,controller:"@",priority:500}}],Oc={};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ma("ng-"+a);Oc[c]=["$parse",function(d){return{compile:function(e,
g){var f=d(g[c]);return function(c,d,e){d.on(x(a),function(a){c.$apply(function(){f(c,{$event:a})})})}}}}]});var qe=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,g,f){var h,m;c.$watch(e.ngIf,function(g){Oa(g)?m||(m=c.$new(),f(m,function(c){c[c.length++]=Q.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)})):(m&&(m.$destroy(),m=null),h&&(a.leave(wb(h.clone)),h=null))})}}}],re=["$http","$templateCache",
"$anchorScroll","$animate","$sce",function(a,c,d,e,g){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:Ca.noop,compile:function(f,h){var m=h.ngInclude||h.src,k=h.onload||"",l=h.autoscroll;return function(f,h,q,r,y){var A=0,u,t,H=function(){u&&(u.$destroy(),u=null);t&&(e.leave(t),t=null)};f.$watch(g.parseAsResourceUrl(m),function(g){var m=function(){!B(l)||l&&!f.$eval(l)||d()},q=++A;g?(a.get(g,{cache:c}).success(function(a){if(q===A){var c=f.$new();r.template=a;a=y(c,
function(a){H();e.enter(a,null,h,m)});u=c;t=a;u.$emit("$includeContentLoaded");f.$eval(k)}}).error(function(){q===A&&H()}),f.$emit("$includeContentRequested")):(H(),r.template=null)})}}}}],se=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,g){d.html(g.template);a(d.contents())(c)}}}],te=ta({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),ue=ta({terminal:!0,priority:1E3}),ve=["$locale","$interpolate",function(a,c){var d=
/{}/g;return{restrict:"EA",link:function(e,g,f){var h=f.count,m=f.$attr.when&&g.attr(f.$attr.when),k=f.offset||0,l=e.$eval(m)||{},n={},p=c.startSymbol(),s=c.endSymbol(),r=/^when(Minus)?(.+)$/;q(f,function(a,c){r.test(c)&&(l[x(c.replace("when","").replace("Minus","-"))]=g.attr(f.$attr[c]))});q(l,function(a,e){n[e]=c(a.replace(d,p+h+"-"+k+s))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-k));return n[c](e,g,!0)},function(a){g.text(a)})}}}],we=["$parse",
"$animate",function(a,c){var d=F("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,link:function(e,g,f,h,m){var k=f.ngRepeat,l=k.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),n,p,s,r,y,t,u={$id:Fa};if(!l)throw d("iexp",k);f=l[1];h=l[2];(l=l[3])?(n=a(l),p=function(a,c,d){t&&(u[t]=a);u[y]=c;u.$index=d;return n(e,u)}):(s=function(a,c){return Fa(c)},r=function(a){return a});l=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!l)throw d("iidexp",
f);y=l[3]||l[1];t=l[2];var B={};e.$watchCollection(h,function(a){var f,h,l=g[0],n,u={},z,P,D,x,T,w,F=[];if(rb(a))T=a,n=p||s;else{n=p||r;T=[];for(D in a)a.hasOwnProperty(D)&&"$"!=D.charAt(0)&&T.push(D);T.sort()}z=T.length;h=F.length=T.length;for(f=0;f<h;f++)if(D=a===T?f:T[f],x=a[D],x=n(D,x,f),xa(x,"`track by` id"),B.hasOwnProperty(x))w=B[x],delete B[x],u[x]=w,F[f]=w;else{if(u.hasOwnProperty(x))throw q(F,function(a){a&&a.scope&&(B[a.id]=a)}),d("dupes",k,x);F[f]={id:x};u[x]=!1}for(D in B)B.hasOwnProperty(D)&&
(w=B[D],f=wb(w.clone),c.leave(f),q(f,function(a){a.$$NG_REMOVED=!0}),w.scope.$destroy());f=0;for(h=T.length;f<h;f++){D=a===T?f:T[f];x=a[D];w=F[f];F[f-1]&&(l=F[f-1].clone[F[f-1].clone.length-1]);if(w.scope){P=w.scope;n=l;do n=n.nextSibling;while(n&&n.$$NG_REMOVED);w.clone[0]!=n&&c.move(wb(w.clone),null,A(l));l=w.clone[w.clone.length-1]}else P=e.$new();P[y]=x;t&&(P[t]=D);P.$index=f;P.$first=0===f;P.$last=f===z-1;P.$middle=!(P.$first||P.$last);P.$odd=!(P.$even=0===(f&1));w.scope||m(P,function(a){a[a.length++]=
Q.createComment(" end ngRepeat: "+k+" ");c.enter(a,null,A(l));l=a;w.scope=P;w.clone=a;u[w.id]=w})}B=u})}}}],xe=["$animate",function(a){return function(c,d,e){c.$watch(e.ngShow,function(c){a[Oa(c)?"removeClass":"addClass"](d,"ng-hide")})}}],ye=["$animate",function(a){return function(c,d,e){c.$watch(e.ngHide,function(c){a[Oa(c)?"addClass":"removeClass"](d,"ng-hide")})}}],ze=ta(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Ae=["$animate",
function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,g){var f,h,m=[];c.$watch(e.ngSwitch||e.on,function(d){for(var l=0,n=m.length;l<n;l++)m[l].$destroy(),a.leave(h[l]);h=[];m=[];if(f=g.cases["!"+d]||g.cases["?"])c.$eval(e.change),q(f,function(d){var e=c.$new();m.push(e);d.transclude(e,function(c){var e=d.element;h.push(c);a.enter(c,e.parent(),e)})})})}}}],Be=ta({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,
c,d,e,g){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:g,element:c})}}),Ce=ta({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,c,d,e,g){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:g,element:c})}}),De=ta({controller:["$element","$transclude",function(a,c){if(!c)throw F("ngTransclude")("orphan",ga(a));this.$transclude=c}],link:function(a,c,d,e){e.$transclude(function(a){c.empty();c.append(a)})}}),Ee=["$templateCache",
function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Fe=F("ngOptions"),Ge=$({terminal:!0}),He=["$compile","$parse",function(a,c){var d=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,e={$setViewValue:w};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope",
"$attrs",function(a,c,d){var m=this,k={},l=e,n;m.databound=d.ngModel;m.init=function(a,c,d){l=a;n=d};m.addOption=function(c){xa(c,'"option value"');k[c]=!0;l.$viewValue==c&&(a.val(c),n.parent()&&n.remove())};m.removeOption=function(a){this.hasOption(a)&&(delete k[a],l.$viewValue==a&&this.renderUnknownOption(a))};m.renderUnknownOption=function(c){c="? "+Fa(c)+" ?";n.val(c);a.prepend(n);a.val(c);n.prop("selected",!0)};m.hasOption=function(a){return k.hasOwnProperty(a)};c.$on("$destroy",function(){m.renderUnknownOption=
w})}],link:function(e,f,h,m){function k(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(x.parent()&&x.remove(),c.val(a),""===a&&w.prop("selected",!0)):z(a)&&w?c.val(""):e.renderUnknownOption(a)};c.on("change",function(){a.$apply(function(){x.parent()&&x.remove();d.$setViewValue(c.val())})})}function l(a,c,d){var e;d.$render=function(){var a=new Sa(d.$viewValue);q(c.find("option"),function(c){c.selected=B(a.get(c.value))})};a.$watch(function(){ua(e,d.$viewValue)||(e=fa(d.$viewValue),
d.$render())});c.on("change",function(){a.$apply(function(){var a=[];q(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function n(e,f,g){function h(){var a={"":[]},c=[""],d,k,r,t,v;t=g.$modelValue;v=A(e)||[];var C=n?Pb(v):v,F,I,z;I={};r=!1;var E,H;if(s)if(w&&K(t))for(r=new Sa([]),z=0;z<t.length;z++)I[m]=t[z],r.put(w(e,I),t[z]);else r=new Sa(t);for(z=0;F=C.length,z<F;z++){k=z;if(n){k=C[z];if("$"===k.charAt(0))continue;I[n]=k}I[m]=v[k];d=p(e,I)||"";(k=a[d])||(k=a[d]=
[],c.push(d));s?d=B(r.remove(w?w(e,I):q(e,I))):(w?(d={},d[m]=t,d=w(e,d)===w(e,I)):d=t===q(e,I),r=r||d);E=l(e,I);E=B(E)?E:"";k.push({id:w?w(e,I):n?C[z]:z,label:E,selected:d})}s||(y||null===t?a[""].unshift({id:"",label:"",selected:!r}):r||a[""].unshift({id:"?",label:"",selected:!0}));I=0;for(C=c.length;I<C;I++){d=c[I];k=a[d];x.length<=I?(t={element:D.clone().attr("label",d),label:k.label},v=[t],x.push(v),f.append(t.element)):(v=x[I],t=v[0],t.label!=d&&t.element.attr("label",t.label=d));E=null;z=0;for(F=
k.length;z<F;z++)r=k[z],(d=v[z+1])?(E=d.element,d.label!==r.label&&E.text(d.label=r.label),d.id!==r.id&&E.val(d.id=r.id),E[0].selected!==r.selected&&E.prop("selected",d.selected=r.selected)):(""===r.id&&y?H=y:(H=u.clone()).val(r.id).attr("selected",r.selected).text(r.label),v.push({element:H,label:r.label,id:r.id,selected:r.selected}),E?E.after(H):t.element.append(H),E=H);for(z++;v.length>z;)v.pop().element.remove()}for(;x.length>I;)x.pop()[0].element.remove()}var k;if(!(k=t.match(d)))throw Fe("iexp",
t,ga(f));var l=c(k[2]||k[1]),m=k[4]||k[6],n=k[5],p=c(k[3]||""),q=c(k[2]?k[1]:m),A=c(k[7]),w=k[8]?c(k[8]):null,x=[[{element:f,label:""}]];y&&(a(y)(e),y.removeClass("ng-scope"),y.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=A(e)||[],d={},h,k,l,p,t,u,v;if(s)for(k=[],p=0,u=x.length;p<u;p++)for(a=x[p],l=1,t=a.length;l<t;l++){if((h=a[l].element)[0].selected){h=h.val();n&&(d[n]=h);if(w)for(v=0;v<c.length&&(d[m]=c[v],w(e,d)!=h);v++);else d[m]=c[h];k.push(q(e,d))}}else if(h=f.val(),
"?"==h)k=r;else if(""===h)k=null;else if(w)for(v=0;v<c.length;v++){if(d[m]=c[v],w(e,d)==h){k=q(e,d);break}}else d[m]=c[h],n&&(d[n]=h),k=q(e,d);g.$setViewValue(k)})});g.$render=h;e.$watch(h)}if(m[1]){var p=m[0];m=m[1];var s=h.multiple,t=h.ngOptions,y=!1,w,u=A(Q.createElement("option")),D=A(Q.createElement("optgroup")),x=u.clone();h=0;for(var v=f.children(),F=v.length;h<F;h++)if(""===v[h].value){w=y=v.eq(h);break}p.init(m,y,x);s&&(m.$isEmpty=function(a){return!a||0===a.length});t?n(e,f,m):s?l(e,f,m):
k(e,f,m,p)}}}}],Ie=["$interpolate",function(a){var c={addOption:w,removeOption:w};return{restrict:"E",priority:100,compile:function(d,e){if(z(e.value)){var g=a(d.text(),!0);g||e.$set("value",d.text())}return function(a,d,e){var k=d.parent(),l=k.data("$selectController")||k.parent().data("$selectController");l&&l.databound?d.prop("selected",!1):l=c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&l.removeOption(c);l.addOption(a)}):l.addOption(e.value);d.on("$destroy",function(){l.removeOption(e.value)})}}}}],
Je=$({restrict:"E",terminal:!0});(Da=Z.jQuery)?(A=Da,t(Da.fn,{scope:Ga.scope,isolateScope:Ga.isolateScope,controller:Ga.controller,injector:Ga.injector,inheritedData:Ga.inheritedData}),xb("remove",!0,!0,!1),xb("empty",!1,!1,!1),xb("html",!1,!1,!0)):A=O;Ca.element=A;(function(a){t(a,{bootstrap:Zb,copy:fa,extend:t,equals:ua,element:A,forEach:q,injector:$b,noop:w,bind:cb,toJson:qa,fromJson:Vb,identity:Ba,isUndefined:z,isDefined:B,isString:D,isFunction:L,isObject:X,isNumber:sb,isElement:Qc,isArray:K,
version:Sd,isDate:La,lowercase:x,uppercase:Ia,callbacks:{counter:0},$$minErr:F,$$csp:Ub});Ua=Vc(Z);try{Ua("ngLocale")}catch(c){Ua("ngLocale",[]).provider("$locale",sd)}Ua("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Cd});a.provider("$compile",jc).directive({a:Xd,input:Mc,textarea:Mc,form:Yd,script:Ee,select:He,style:Je,option:Ie,ngBind:ie,ngBindHtml:ke,ngBindTemplate:je,ngClass:le,ngClassEven:ne,ngClassOdd:me,ngCloak:oe,ngController:pe,ngForm:Zd,ngHide:ye,ngIf:qe,ngInclude:re,
ngInit:te,ngNonBindable:ue,ngPluralize:ve,ngRepeat:we,ngShow:xe,ngStyle:ze,ngSwitch:Ae,ngSwitchWhen:Be,ngSwitchDefault:Ce,ngOptions:Ge,ngTransclude:De,ngModel:de,ngList:fe,ngChange:ee,required:Nc,ngRequired:Nc,ngValue:he}).directive({ngInclude:se}).directive(Ob).directive(Oc);a.provider({$anchorScroll:dd,$animate:Ud,$browser:fd,$cacheFactory:gd,$controller:jd,$document:kd,$exceptionHandler:ld,$filter:Bc,$interpolate:qd,$interval:rd,$http:md,$httpBackend:od,$location:ud,$log:vd,$parse:yd,$rootScope:Bd,
$q:zd,$sce:Fd,$sceDelegate:Ed,$sniffer:Gd,$templateCache:hd,$timeout:Hd,$window:Id})}])})(Ca);A(Q).ready(function(){Tc(Q,Zb)})})(window,document);!angular.$$csp()&&angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}</style>');
//# sourceMappingURL=angular.min.js.map

8829
guacamole/src/main/webapp/lib/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
/*!
* @license
* Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
* Build: `lodash modern -o ./dist/lodash.js`
*/
;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:m+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:m+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var i=e[u],a=r[u];if(i!==a){if(i>a||typeof i=="undefined")return 1;if(i<a||typeof a=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],i=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&i&&typeof i=="object")return false;for(u=f(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=f(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function i(n){return"\\"+U[n]
}function a(){return h.pop()||[]}function f(){return g.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function l(n){n.length=0,h.length<_&&h.push(n)}function c(n){var t=n.l;t&&c(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,g.length<_&&g.push(n)}function p(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];return u}function s(e){function h(n,t,e){if(!n||!V[typeof n])return n;
t=t&&typeof e=="undefined"?t:tt(t,e,3);for(var r=-1,u=V[typeof n]&&Fe(n),o=u?u.length:0;++r<o&&(e=u[r],false!==t(n[e],e,n)););return n}function g(n,t,e){var r;if(!n||!V[typeof n])return n;t=t&&typeof e=="undefined"?t:tt(t,e,3);for(r in n)if(false===t(n[r],r,n))break;return n}function _(n,t,e){var r,u=n,o=u;if(!u)return o;for(var i=arguments,a=0,f=typeof e=="number"?2:i.length;++a<f;)if((u=i[a])&&V[typeof u])for(var l=-1,c=V[typeof u]&&Fe(u),p=c?c.length:0;++l<p;)r=c[l],"undefined"==typeof o[r]&&(o[r]=u[r]);
return o}function U(n,t,e){var r,u=n,o=u;if(!u)return o;var i=arguments,a=0,f=typeof e=="number"?2:i.length;if(3<f&&"function"==typeof i[f-2])var l=tt(i[--f-1],i[f--],2);else 2<f&&"function"==typeof i[f-1]&&(l=i[--f]);for(;++a<f;)if((u=i[a])&&V[typeof u])for(var c=-1,p=V[typeof u]&&Fe(u),s=p?p.length:0;++c<s;)r=p[c],o[r]=l?l(o[r],u[r]):u[r];return o}function H(n){var t,e=[];if(!n||!V[typeof n])return e;for(t in n)me.call(n,t)&&e.push(t);return e}function J(n){return n&&typeof n=="object"&&!Te(n)&&me.call(n,"__wrapped__")?n:new Q(n)
}function Q(n,t){this.__chain__=!!t,this.__wrapped__=n}function X(n){function t(){if(r){var n=p(r);be.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return wt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return $e(t,n),t}function Z(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!wt(n))return n;var i=ce.call(n);if(!K[i])return n;var f=Ae[i];switch(i){case T:case F:return new f(+n);case W:case P:return new f(n);case z:return o=f(n.source,C.exec(n)),o.lastIndex=n.lastIndex,o
}if(i=Te(n),t){var c=!r;r||(r=a()),u||(u=a());for(var s=r.length;s--;)if(r[s]==n)return u[s];o=i?f(n.length):{}}else o=i?p(n):U({},n);return i&&(me.call(n,"index")&&(o.index=n.index),me.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(i?St:h)(n,function(n,i){o[i]=Z(n,t,e,r,u)}),c&&(l(r),l(u)),o):o}function nt(n){return wt(n)?ke(n):{}}function tt(n,t,e){if(typeof n!="function")return Ut;if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(De.funcNames&&(r=!n.name),r=r||!De.funcDecomp,!r)){var u=ge.call(n);
De.funcNames||(r=!O.test(u)),r||(r=E.test(u),$e(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=f?i:this;if(u){var h=p(u);be.apply(h,arguments)}return(o||c)&&(h||(h=p(arguments)),o&&be.apply(h,o),c&&h.length<a)?(r|=16,et([e,s?r:-4&r,h,null,i,a])):(h||(h=arguments),l&&(e=n[v]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),wt(h)?h:n):e.apply(n,h))
}var e=n[0],r=n[1],u=n[2],o=n[3],i=n[4],a=n[5],f=1&r,l=2&r,c=4&r,s=8&r,v=e;return $e(t,n),t}function rt(e,r){var u=-1,i=st(),a=e?e.length:0,f=a>=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++u<a;)p=e[u],0>i(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r<u;){var i=n[r];if(i&&typeof i=="object"&&typeof i.length=="number"&&(Te(i)||yt(i))){t||(i=ut(i,t,e));var a=-1,f=i.length,l=o.length;for(o.length+=f;++a<f;)o[l++]=i[a]}else e||o.push(i)}return o
}function ot(n,t,e,r,u,o){if(e){var i=e(n,t);if(typeof i!="undefined")return!!i}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&V[typeof n]||t&&V[typeof t]))return false;if(null==n||null==t)return n===t;var f=ce.call(n),c=ce.call(t);if(f==D&&(f=q),c==D&&(c=q),f!=c)return false;switch(f){case T:case F:return+n==+t;case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case z:case P:return n==oe(t)}if(c=f==$,!c){var p=me.call(n,"__wrapped__"),s=me.call(t,"__wrapped__");if(p||s)return ot(p?n.__wrapped__:n,s?t.__wrapped__:t,e,r,u,o);
if(f!=q)return false;if(f=n.constructor,p=t.constructor,f!=p&&!(dt(f)&&f instanceof f&&dt(p)&&p instanceof p)&&"constructor"in n&&"constructor"in t)return false}for(f=!u,u||(u=a()),o||(o=a()),p=u.length;p--;)if(u[p]==n)return o[p]==t;var v=0,i=true;if(u.push(n),o.push(t),c){if(p=n.length,v=t.length,(i=v==p)||r)for(;v--;)if(c=p,s=t[v],r)for(;c--&&!(i=ot(n[c],s,e,r,u,o)););else if(!(i=ot(n[v],s,e,r,u,o)))break}else g(t,function(t,a,f){return me.call(f,a)?(v++,i=me.call(n,a)&&ot(n[a],t,e,r,u,o)):void 0}),i&&!r&&g(n,function(n,t,e){return me.call(e,t)?i=-1<--v:void 0
});return u.pop(),o.pop(),f&&(l(u),l(o)),i}function it(n,t,e,r,u){(Te(t)?St:h)(t,function(t,o){var i,a,f=t,l=n[o];if(t&&((a=Te(t))||Pe(t))){for(f=r.length;f--;)if(i=r[f]==t){l=u[f];break}if(!i){var c;e&&(f=e(l,t),c=typeof f!="undefined")&&(l=f),c||(l=a?Te(l)?l:[]:Pe(l)?l:{}),r.push(t),u.push(l),c||it(l,t,e,r,u)}}else e&&(f=e(l,t),typeof f=="undefined"&&(f=t)),typeof f!="undefined"&&(l=f);n[o]=l})}function at(n,t){return n+he(Re()*(t-n+1))}function ft(e,r,u){var i=-1,f=st(),p=e?e.length:0,s=[],v=!r&&p>=b&&f===n,h=u||v?a():s;
for(v&&(h=o(h),f=t);++i<p;){var g=e[i],y=u?u(g,i,e):g;(r?!i||h[h.length-1]!==y:0>f(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++r<o;){var i=t[r];n(u,i,e(i,r,t),t)}else h(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function ct(n,t,e,r,u,o){var i=1&t,a=4&t,f=16&t,l=32&t;if(!(2&t||dt(n)))throw new ie;f&&!e.length&&(t&=-17,f=e=false),l&&!r.length&&(t&=-33,l=r=false);
var c=n&&n.__bindData__;return c&&true!==c?(c=p(c),c[2]&&(c[2]=p(c[2])),c[3]&&(c[3]=p(c[3])),!i||1&c[1]||(c[4]=u),!i&&1&c[1]&&(t|=8),!a||4&c[1]||(c[5]=o),f&&be.apply(c[2]||(c[2]=[]),e),l&&we.apply(c[3]||(c[3]=[]),r),c[1]|=t,ct.apply(null,c)):(1==t||17===t?X:et)([n,t,e,r,u,o])}function pt(n){return Be[n]}function st(){var t=(t=J.indexOf)===Wt?n:t;return t}function vt(n){return typeof n=="function"&&pe.test(n)}function ht(n){var t,e;return n&&ce.call(n)==q&&(t=n.constructor,!dt(t)||t instanceof t)?(g(n,function(n,t){e=t
}),typeof e=="undefined"||me.call(n,e)):false}function gt(n){return We[n]}function yt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==D||false}function mt(n,t,e){var r=Fe(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function bt(n){var t=[];return g(n,function(n,e){dt(n)&&t.push(e)}),t.sort()}function _t(n){for(var t=-1,e=Fe(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function dt(n){return typeof n=="function"}function wt(n){return!(!n||!V[typeof n])
}function jt(n){return typeof n=="number"||n&&typeof n=="object"&&ce.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&ce.call(n)==P||false}function xt(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;)u[t]=n[e[t]];return u}function Ct(n,t,e){var r=-1,u=st(),o=n?n.length:0,i=false;return e=(0>e?Ie(0,o+e):e)||0,Te(n)?i=-1<u(n,t,e):typeof o=="number"?i=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):h(n,function(n){return++r<e?void 0:!(i=n===t)}),i}function Ot(n,t,e){var r=true;t=J.createCallback(t,e,3),e=-1;
var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&(r=!!t(n[e],e,n)););else h(n,function(n,e,u){return r=!!t(n,e,u)});return r}function Nt(n,t,e){var r=[];t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}else h(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){t=J.createCallback(t,e,3),e=-1;var r=n?n.length:0;if(typeof r!="number"){var u;return h(n,function(n,e,r){return t(n,e,r)?(u=n,false):void 0}),u}for(;++e<r;){var o=n[e];
if(t(o,e,n))return o}}function St(n,t,e){var r=-1,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof u=="number")for(;++r<u&&false!==t(n[r],r,n););else h(n,t);return n}function Et(n,t,e){var r=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof r=="number")for(;r--&&false!==t(n[r],r,n););else{var u=Fe(n),r=u.length;h(n,function(n,e,o){return e=u?u[--r]:--r,t(o[e],e,o)})}return n}function Rt(n,t,e){var r=-1,u=n?n.length:0;if(t=J.createCallback(t,e,3),typeof u=="number")for(var o=Xt(u);++r<u;)o[r]=t(n[r],r,n);
else o=[],h(n,function(n,e,u){o[++r]=t(n,e,u)});return o}function At(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a>o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++o<i;)e=t(e,n[o],o,n);else h(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)
});return e}function $t(n,t,e,r){var u=3>arguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&!(r=t(n[e],e,n)););else h(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Bt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;
for(t=J.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[0]:v;return p(n,0,Se(Ie(0,r),u))}function Wt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Ie(0,t);return p(n,r)}function zt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?J.createCallback(e,r,1):Ut,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
return u}function Pt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=J.createCallback(e,r,3)),ft(n,t,e)}function Kt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?At(Ve(n,"length")):0,r=Xt(0>e?0:e);++t<e;)r[t]=Ve(n,t);return r}function Lt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||Te(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?ct(n,17,p(arguments,2),null,t):ct(n,1,null,null,t)
}function Vt(n,t,e){function r(){c&&ve(c),i=c=p=v,(g||h!==t)&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null))}function u(){var e=t-(Ue()-f);0<e?c=_e(u,e):(i&&ve(i),e=p,i=c=p=v,e&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null)))}var o,i,a,f,l,c,p,s=0,h=false,g=true;if(!dt(n))throw new ie;if(t=Ie(0,t)||0,true===e)var y=true,g=false;else wt(e)&&(y=e.leading,h="maxWait"in e&&(Ie(t,e.maxWait)||0),g="trailing"in e?e.trailing:g);return function(){if(o=arguments,f=Ue(),l=this,p=g&&(c||!y),false===h)var e=y&&!c;else{i||y||(s=f);var v=h-(f-s),m=0>=v;
m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this;
i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t
}catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t;
var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n)
}:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e<t;)u[e]=n[r[e]];
return u},J.bind=Mt,J.bindAll=function(n){for(var t=1<arguments.length?ut(arguments,true,false,1):bt(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=ct(n[u],1,null,null,n)}return n},J.bindKey=function(n,t){return 2<arguments.length?ct(t,19,p(arguments,2),null,n):ct(t,3,null,null,n)},J.chain=function(n){return n=new Q(n),n.__chain__=true,n},J.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},J.compose=function(){for(var n=arguments,t=n.length;t--;)if(!dt(n[t]))throw new ie;
return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},J.constant=function(n){return function(){return n}},J.countBy=Ke,J.create=function(n,t){var e=nt(n);return t?U(e,t):e},J.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);if("object"!=r)return Jt(n);var u=Fe(n),o=u[0],i=n[o];return 1!=u.length||i!==i||wt(i)?function(t){for(var e=u.length,r=false;e--&&(r=ot(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],i===n&&(0!==i||1/i==1/n)
}},J.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,ct(n,4,null,null,null,t)},J.debounce=Vt,J.defaults=_,J.defer=function(n){if(!dt(n))throw new ie;var t=p(arguments,1);return _e(function(){n.apply(v,t)},1)},J.delay=function(n,t){if(!dt(n))throw new ie;var e=p(arguments,2);return _e(function(){n.apply(v,e)},t)},J.difference=function(n){return rt(n,ut(arguments,true,true,1))},J.filter=Nt,J.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Rt(n,e,r)),ut(n,t)
},J.forEach=St,J.forEachRight=Et,J.forIn=g,J.forInRight=function(n,t,e){var r=[];g(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},J.forOwn=h,J.forOwnRight=mt,J.functions=bt,J.groupBy=Le,J.indexBy=Me,J.initial=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return p(n,0,Se(Ie(0,u-r),u))},J.intersection=function(){for(var e=[],r=-1,u=arguments.length,i=a(),f=st(),p=f===n,s=a();++r<u;){var v=arguments[r];
(Te(v)||yt(v))&&(e.push(v),i.push(p&&v.length>=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h<g;){var m=i[0],v=p[h];if(0>(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={};
return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=p(arguments,1,e),u=-1,o=a(),i=a();++u<e;)it(n,t[u],r,o,i);
return l(o),l(i),n},J.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a<o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)});return o},J.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];g(n,function(n,t){u.push(t)});for(var u=rt(u,ut(arguments,true,false,1)),o=-1,i=u.length;++o<i;){var a=u[o];r[a]=n[a]}}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)||(r[e]=n)
});return r},J.once=function(n){var t,e;if(!dt(n))throw new ie;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},J.pairs=function(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u},J.partial=function(n){return ct(n,16,p(arguments,1))},J.partialRight=function(n){return ct(n,32,null,p(arguments,1))},J.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ut(arguments,true,false,1),i=wt(n)?o.length:0;++u<i;){var a=o[u];a in n&&(r[a]=n[a])
}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},J.pluck=Ve,J.property=Jt,J.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,i=t[e];++o<u;)n[o]===i&&(de.call(n,o--,1),u--);return n},J.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Ie(0,se((t-n)/(e||1)));for(var u=Xt(t);++r<t;)u[r]=n,n+=e;return u},J.reject=function(n,t,e){return t=J.createCallback(t,e,3),Nt(n,function(n,e,r){return!t(n,e,r)
})},J.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=J.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),de.call(n,r--,1),u--);return o},J.rest=qt,J.shuffle=Tt,J.sortBy=function(n,t,e){var r=-1,o=Te(t),i=n?n.length:0,p=Xt(typeof i=="number"?i:0);for(o||(t=J.createCallback(t,e,3)),St(n,function(n,e,u){var i=p[++r]=f();o?i.m=Rt(t,function(t){return n[t]}):(i.m=a())[0]=t(n,e,u),i.n=r,i.o=n}),i=p.length,p.sort(u);i--;)n=p[i],p[i]=n.o,o||l(n.m),c(n);return p},J.tap=function(n,t){return t(n),n
},J.throttle=function(n,t,e){var r=true,u=true;if(!dt(n))throw new ie;return false===e?r=false:wt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),L.leading=r,L.maxWait=t,L.trailing=u,Vt(n,t,L)},J.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Xt(n);for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},J.toArray=function(n){return n&&typeof n.length=="number"?p(n):xt(n)},J.transform=function(n,t,e,r){var u=Te(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=J.createCallback(t,r,4),(u?St:h)(n,function(n,r,u){return t(e,n,r,u)
})),e},J.union=function(){return ft(ut(arguments,true,true))},J.uniq=Pt,J.values=xt,J.where=Nt,J.without=function(n){return rt(n,p(arguments,1))},J.wrap=function(n,t){return ct(t,16,[n])},J.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(Te(e)||yt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},J.zip=Kt,J.zipObject=Lt,J.collect=Rt,J.drop=qt,J.each=St,J.eachRight=Et,J.extend=U,J.methods=bt,J.object=Lt,J.select=Nt,J.tail=qt,J.unique=Pt,J.unzip=Kt,Gt(J),J.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Z(n,t,typeof e=="function"&&tt(e,r,1))
},J.cloneDeep=function(n,t,e){return Z(n,true,typeof t=="function"&&tt(t,e,1))},J.contains=Ct,J.escape=function(n){return null==n?"":oe(n).replace(ze,pt)},J.every=Ot,J.find=It,J.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=J.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},J.findKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),h(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.findLast=function(n,t,e){var r;return t=J.createCallback(t,e,3),Et(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0
}),r},J.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=J.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;return-1},J.findLastKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),mt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.has=function(n,t){return n?me.call(n,t):false},J.identity=Ut,J.indexOf=Wt,J.isArguments=yt,J.isArray=Te,J.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&ce.call(n)==T||false},J.isDate=function(n){return n&&typeof n=="object"&&ce.call(n)==F||false
},J.isElement=function(n){return n&&1===n.nodeType||false},J.isEmpty=function(n){var t=true;if(!n)return t;var e=ce.call(n),r=n.length;return e==$||e==P||e==D||e==q&&typeof r=="number"&&dt(n.splice)?!r:(h(n,function(){return t=false}),t)},J.isEqual=function(n,t,e,r){return ot(n,t,typeof e=="function"&&tt(e,r,2))},J.isFinite=function(n){return Ce(n)&&!Oe(parseFloat(n))},J.isFunction=dt,J.isNaN=function(n){return jt(n)&&n!=+n},J.isNull=function(n){return null===n},J.isNumber=jt,J.isObject=wt,J.isPlainObject=Pe,J.isRegExp=function(n){return n&&typeof n=="object"&&ce.call(n)==z||false
},J.isString=kt,J.isUndefined=function(n){return typeof n=="undefined"},J.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t)
},J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t
}),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t
},J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n)
},J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={};
K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X);
var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this);

View File

@@ -0,0 +1,8 @@
/*
AngularJS v1.2.7
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(p,f,n){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,k=!1,l=f.copy,m=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,l(a,g),l(a,c),k&&d.$apply())})();k=!0;d.$watch(function(){var a,e,d;for(a in g)m(c[a])&&b.cookies(a,n);for(a in c)(e=c[a],f.isString(e))?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(m(e[a])?delete c[a]:c[a]=e[a])});
return c}]).factory("$cookieStore",["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular);
//# sourceMappingURL=angular-cookies.min.js.map

View File

@@ -0,0 +1,911 @@
/**
* @license AngularJS v1.2.5
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/**
* @ngdoc overview
* @name ngRoute
* @description
*
* # ngRoute
*
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* {@installModule route}
*
* <div doc-module-components="ngRoute"></div>
*/
/* global -ngRouteModule */
var ngRouteModule = angular.module('ngRoute', ['ng']).
provider('$route', $RouteProvider);
/**
* @ngdoc object
* @name ngRoute.$routeProvider
* @function
*
* @description
*
* Used for configuring routes.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* ## Dependencies
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function $RouteProvider(){
function inherit(parent, extra) {
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra);
}
var routes = {};
/**
* @ngdoc method
* @name ngRoute.$routeProvider#when
* @methodOf ngRoute.$routeProvider
*
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
* contains redundant trailing slash or is missing one, the route will still match and the
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
* route definition.
*
* * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up
* to the next slash are matched and stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain named groups starting with a colon and ending with a star:
* e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain optional named groups with a question mark: e.g.`:name?`.
*
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
* `/color/brown/largecode/code/with/slashs/edit` and extract:
*
* * `color: brown`
* * `largecode: code/with/slashs`.
*
*
* @param {Object} route Mapping information to be assigned to `$route.current` on route
* match.
*
* Object properties:
*
* - `controller` âEUR" `{(string|function()=}` âEUR" Controller fn that should be associated with
* newly created scope or the name of a {@link angular.Module#controller registered
* controller} if passed as a string.
* - `controllerAs` âEUR" `{string=}` âEUR" A controller alias name. If present the controller will be
* published to scope under the `controllerAs` name.
* - `template` âEUR" `{string=|function()=}` âEUR" html template as a string or a function that
* returns an html template as a string which should be used by {@link
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
* This property takes precedence over `templateUrl`.
*
* If `template` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `templateUrl` âEUR" `{string=|function()=}` âEUR" path or function that returns a path to an html
* template that should be used by {@link ngRoute.directive:ngView ngView}.
*
* If `templateUrl` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, the router
* will wait for them all to be resolved or one to be rejected before the controller is
* instantiated.
* If all the promises are resolved successfully, the values of the resolved promises are
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
* fired. If any of the promises are rejected the
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
* is:
*
* - `key` âEUR" `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
* Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
* and the return value is treated as the dependency. If the result is a promise, it is
* resolved before its value is injected into the controller. Be aware that
* `ngRoute.$routeParams` will still refer to the previous route within these resolve
* functions. Use `$route.current.params` to access the new route parameters, instead.
*
* - `redirectTo` âEUR" {(string|function())=} âEUR" value to update
* {@link ng.$location $location} path with and trigger route redirection.
*
* If `redirectTo` is a function, it will be called with the following parameters:
*
* - `{Object.<string>}` - route parameters extracted from the current
* `$location.path()` by applying the current route templateUrl.
* - `{string}` - current `$location.path()`
* - `{Object}` - current `$location.search()`
*
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
* or `$location.hash()` changes.
*
* If the option is set to `false` and url in the browser changes, then
* `$routeUpdate` event is broadcasted on the root scope.
*
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
*
* If the option is set to `true`, then the particular route can be matched without being
* case sensitive
*
* @returns {Object} self
*
* @description
* Adds a new route definition to the `$route` service.
*/
this.when = function(path, route) {
routes[path] = angular.extend(
{reloadOnSearch: true},
route,
path && pathRegExp(path, route)
);
// create redirection for trailing slashes
if (path) {
var redirectPath = (path[path.length-1] == '/')
? path.substr(0, path.length-1)
: path +'/';
routes[redirectPath] = angular.extend(
{redirectTo: path},
pathRegExp(redirectPath, route)
);
}
return this;
};
/**
* @param path {string} path
* @param opts {Object} options
* @return {?Object}
*
* @description
* Normalizes the given path, returning a regular expression
* and the original path.
*
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
*/
function pathRegExp(path, opts) {
var insensitive = opts.caseInsensitiveMatch,
ret = {
originalPath: path,
regexp: path
},
keys = ret.keys = [];
path = path
.replace(/([().])/g, '\\$1')
.replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){
var optional = option === '?' ? option : null;
var star = option === '*' ? option : null;
keys.push({ name: key, optional: !!optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (star && '(.+?)' || '([^/]+)')
+ (optional || '')
+ ')'
+ (optional || '');
})
.replace(/([\/$\*])/g, '\\$1');
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
return ret;
}
/**
* @ngdoc method
* @name ngRoute.$routeProvider#otherwise
* @methodOf ngRoute.$routeProvider
*
* @description
* Sets route definition that will be used on route change when no other route definition
* is matched.
*
* @param {Object} params Mapping information to be assigned to `$route.current`.
* @returns {Object} self
*/
this.otherwise = function(params) {
this.when(null, params);
return this;
};
this.$get = ['$rootScope',
'$location',
'$routeParams',
'$q',
'$injector',
'$http',
'$templateCache',
'$sce',
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
/**
* @ngdoc object
* @name ngRoute.$route
* @requires $location
* @requires $routeParams
*
* @property {Object} current Reference to the current route definition.
* The route definition contains:
*
* - `controller`: The controller constructor as define in route definition.
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
* controller instantiation. The `locals` contain
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
*
* - `$scope` - The current route scope.
* - `$template` - The current route template HTML.
*
* @property {Array.<Object>} routes Array of all configured routes.
*
* @description
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
* It watches `$location.url()` and tries to map the path to an existing route definition.
*
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
*
* The `$route` service is typically used in conjunction with the
* {@link ngRoute.directive:ngView `ngView`} directive and the
* {@link ngRoute.$routeParams `$routeParams`} service.
*
* @example
This example shows how changing the URL hash causes the `$route` to match a route against the
URL, and the `ngView` pulls in the partial.
Note that this example is using {@link ng.directive:script inlined templates}
to get it working on jsfiddle as well.
<example module="ngViewExample" deps="angular-route.js">
<file name="index.html">
<div ng-controller="MainCntl">
Choose:
<a href="Book/Moby">Moby</a> |
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
<a href="Book/Gatsby">Gatsby</a> |
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
<a href="Book/Scarlet">Scarlet Letter</a><br/>
<div ng-view></div>
<hr />
<pre>$location.path() = {{$location.path()}}</pre>
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
<pre>$route.current.params = {{$route.current.params}}</pre>
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
<pre>$routeParams = {{$routeParams}}</pre>
</div>
</file>
<file name="book.html">
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
</file>
<file name="chapter.html">
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
Chapter Id: {{params.chapterId}}
</file>
<file name="script.js">
angular.module('ngViewExample', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: BookCntl,
resolve: {
// I will cause a 1 second delay
delay: function($q, $timeout) {
var delay = $q.defer();
$timeout(delay.resolve, 1000);
return delay.promise;
}
}
});
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
templateUrl: 'chapter.html',
controller: ChapterCntl
});
// configure html5 to get links working on jsfiddle
$locationProvider.html5Mode(true);
});
function MainCntl($scope, $route, $routeParams, $location) {
$scope.$route = $route;
$scope.$location = $location;
$scope.$routeParams = $routeParams;
}
function BookCntl($scope, $routeParams) {
$scope.name = "BookCntl";
$scope.params = $routeParams;
}
function ChapterCntl($scope, $routeParams) {
$scope.name = "ChapterCntl";
$scope.params = $routeParams;
}
</file>
<file name="scenario.js">
it('should load and compile correct template', function() {
element('a:contains("Moby: Ch1")').click();
var content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: ChapterCntl/);
expect(content).toMatch(/Book Id\: Moby/);
expect(content).toMatch(/Chapter Id\: 1/);
element('a:contains("Scarlet")').click();
sleep(2); // promises are not part of scenario waiting
content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: BookCntl/);
expect(content).toMatch(/Book Id\: Scarlet/);
});
</file>
</example>
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeChangeStart
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
* Broadcasted before a route change. At this point the route services starts
* resolving all of the dependencies needed for the route change to occurs.
* Typically this involves fetching the view template as well as any dependencies
* defined in `resolve` route property. Once all of the dependencies are resolved
* `$routeChangeSuccess` is fired.
*
* @param {Object} angularEvent Synthetic event object.
* @param {Route} next Future route information.
* @param {Route} current Current route information.
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeChangeSuccess
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
* Broadcasted after a route dependencies are resolved.
* {@link ngRoute.directive:ngView ngView} listens for the directive
* to instantiate the controller and render the view.
*
* @param {Object} angularEvent Synthetic event object.
* @param {Route} current Current route information.
* @param {Route|Undefined} previous Previous route information, or undefined if current is
* first route entered.
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeChangeError
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
* Broadcasted if any of the resolve promises are rejected.
*
* @param {Object} angularEvent Synthetic event object
* @param {Route} current Current route information.
* @param {Route} previous Previous route information.
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeUpdate
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
*
* The `reloadOnSearch` property has been set to false, and we are reusing the same
* instance of the Controller.
*/
var forceReload = false,
$route = {
routes: routes,
/**
* @ngdoc method
* @name ngRoute.$route#reload
* @methodOf ngRoute.$route
*
* @description
* Causes `$route` service to reload the current route even if
* {@link ng.$location $location} hasn't changed.
*
* As a result of that, {@link ngRoute.directive:ngView ngView}
* creates new scope, reinstantiates the controller.
*/
reload: function() {
forceReload = true;
$rootScope.$evalAsync(updateRoute);
}
};
$rootScope.$on('$locationChangeSuccess', updateRoute);
return $route;
/////////////////////////////////////////////////////
/**
* @param on {string} current url
* @param route {Object} route regexp to match the url against
* @return {?Object}
*
* @description
* Check if the route matches the current url.
*
* Inspired by match in
* visionmedia/express/lib/router/router.js.
*/
function switchRouteMatcher(on, route) {
var keys = route.keys,
params = {};
if (!route.regexp) return null;
var m = route.regexp.exec(on);
if (!m) return null;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
if (key && val) {
params[key.name] = val;
}
}
return params;
}
function updateRoute() {
var next = parseRoute(),
last = $route.current;
if (next && last && next.$$route === last.$$route
&& angular.equals(next.pathParams, last.pathParams)
&& !next.reloadOnSearch && !forceReload) {
last.params = next.params;
angular.copy(last.params, $routeParams);
$rootScope.$broadcast('$routeUpdate', last);
} else if (next || last) {
forceReload = false;
$rootScope.$broadcast('$routeChangeStart', next, last);
$route.current = next;
if (next) {
if (next.redirectTo) {
if (angular.isString(next.redirectTo)) {
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
.replace();
} else {
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
.replace();
}
}
}
$q.when(next).
then(function() {
if (next) {
var locals = angular.extend({}, next.resolve),
template, templateUrl;
angular.forEach(locals, function(value, key) {
locals[key] = angular.isString(value) ?
$injector.get(value) : $injector.invoke(value);
});
if (angular.isDefined(template = next.template)) {
if (angular.isFunction(template)) {
template = template(next.params);
}
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
if (angular.isFunction(templateUrl)) {
templateUrl = templateUrl(next.params);
}
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
if (angular.isDefined(templateUrl)) {
next.loadedTemplateUrl = templateUrl;
template = $http.get(templateUrl, {cache: $templateCache}).
then(function(response) { return response.data; });
}
}
if (angular.isDefined(template)) {
locals['$template'] = template;
}
return $q.all(locals);
}
}).
// after route change
then(function(locals) {
if (next == $route.current) {
if (next) {
next.locals = locals;
angular.copy(next.params, $routeParams);
}
$rootScope.$broadcast('$routeChangeSuccess', next, last);
}
}, function(error) {
if (next == $route.current) {
$rootScope.$broadcast('$routeChangeError', next, last, error);
}
});
}
}
/**
* @returns the current active route, by matching it against the URL
*/
function parseRoute() {
// Match a route
var params, match;
angular.forEach(routes, function(route, path) {
if (!match && (params = switchRouteMatcher($location.path(), route))) {
match = inherit(route, {
params: angular.extend({}, $location.search(), params),
pathParams: params});
match.$$route = route;
}
});
// No route matched; fallback to "otherwise" route
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
}
/**
* @returns interpolation of the redirect path with the parameters
*/
function interpolate(string, params) {
var result = [];
angular.forEach((string||'').split(':'), function(segment, i) {
if (i === 0) {
result.push(segment);
} else {
var segmentMatch = segment.match(/(\w+)(.*)/);
var key = segmentMatch[1];
result.push(params[key]);
result.push(segmentMatch[2] || '');
delete params[key];
}
});
return result.join('');
}
}];
}
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
/**
* @ngdoc object
* @name ngRoute.$routeParams
* @requires $route
*
* @description
* The `$routeParams` service allows you to retrieve the current set of route parameters.
*
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* The route parameters are a combination of {@link ng.$location `$location`}'s
* {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}.
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
*
* In case of parameter name collision, `path` params take precedence over `search` params.
*
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
* (but its properties will likely change) even when a route change occurs.
*
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
* Instead you can use `$route.current.params` to access the new route's parameters.
*
* @example
* <pre>
* // Given:
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
* // Route: /Chapter/:chapterId/Section/:sectionId
* //
* // Then
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
* </pre>
*/
function $RouteParamsProvider() {
this.$get = function() { return {}; };
}
ngRouteModule.directive('ngView', ngViewFactory);
ngRouteModule.directive('ngView', ngViewFillContentFactory);
/**
* @ngdoc directive
* @name ngRoute.directive:ngView
* @restrict ECA
*
* @description
* # Overview
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
* including the rendered template of the current route into the main layout (`index.html`) file.
* Every time the current route changes, the included view changes with it according to the
* configuration of the `$route` service.
*
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* @animations
* enter - animation is used to bring new content into the browser.
* leave - animation is used to animate existing content away.
*
* The enter and leave animation occur concurrently.
*
* @scope
* @priority 400
* @example
<example module="ngViewExample" deps="angular-route.js" animations="true">
<file name="index.html">
<div ng-controller="MainCntl as main">
Choose:
<a href="Book/Moby">Moby</a> |
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
<a href="Book/Gatsby">Gatsby</a> |
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
<a href="Book/Scarlet">Scarlet Letter</a><br/>
<div class="view-animate-container">
<div ng-view class="view-animate"></div>
</div>
<hr />
<pre>$location.path() = {{main.$location.path()}}</pre>
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
<pre>$route.current.params = {{main.$route.current.params}}</pre>
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
<pre>$routeParams = {{main.$routeParams}}</pre>
</div>
</file>
<file name="book.html">
<div>
controller: {{book.name}}<br />
Book Id: {{book.params.bookId}}<br />
</div>
</file>
<file name="chapter.html">
<div>
controller: {{chapter.name}}<br />
Book Id: {{chapter.params.bookId}}<br />
Chapter Id: {{chapter.params.chapterId}}
</div>
</file>
<file name="animations.css">
.view-animate-container {
position:relative;
height:100px!important;
position:relative;
background:white;
border:1px solid black;
height:40px;
overflow:hidden;
}
.view-animate {
padding:10px;
}
.view-animate.ng-enter, .view-animate.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
display:block;
width:100%;
border-left:1px solid black;
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
padding:10px;
}
.view-animate.ng-enter {
left:100%;
}
.view-animate.ng-enter.ng-enter-active {
left:0;
}
.view-animate.ng-leave.ng-leave-active {
left:-100%;
}
</file>
<file name="script.js">
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
function($routeProvider, $locationProvider) {
$routeProvider.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: BookCntl,
controllerAs: 'book'
});
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
templateUrl: 'chapter.html',
controller: ChapterCntl,
controllerAs: 'chapter'
});
// configure html5 to get links working on jsfiddle
$locationProvider.html5Mode(true);
});
function MainCntl($route, $routeParams, $location) {
this.$route = $route;
this.$location = $location;
this.$routeParams = $routeParams;
}
function BookCntl($routeParams) {
this.name = "BookCntl";
this.params = $routeParams;
}
function ChapterCntl($routeParams) {
this.name = "ChapterCntl";
this.params = $routeParams;
}
</file>
<file name="scenario.js">
it('should load and compile correct template', function() {
element('a:contains("Moby: Ch1")').click();
var content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: ChapterCntl/);
expect(content).toMatch(/Book Id\: Moby/);
expect(content).toMatch(/Chapter Id\: 1/);
element('a:contains("Scarlet")').click();
content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: BookCntl/);
expect(content).toMatch(/Book Id\: Scarlet/);
});
</file>
</example>
*/
/**
* @ngdoc event
* @name ngRoute.directive:ngView#$viewContentLoaded
* @eventOf ngRoute.directive:ngView
* @eventType emit on the current ngView scope
* @description
* Emitted every time the ngView content is reloaded.
*/
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
function ngViewFactory( $route, $anchorScroll, $animate) {
return {
restrict: 'ECA',
terminal: true,
priority: 400,
transclude: 'element',
link: function(scope, $element, attr, ctrl, $transclude) {
var currentScope,
currentElement,
autoScrollExp = attr.autoscroll,
onloadExp = attr.onload || '';
scope.$on('$routeChangeSuccess', update);
update();
function cleanupLastView() {
if (currentScope) {
currentScope.$destroy();
currentScope = null;
}
if(currentElement) {
$animate.leave(currentElement);
currentElement = null;
}
}
function update() {
var locals = $route.current && $route.current.locals,
template = locals && locals.$template;
if (template) {
var newScope = scope.$new();
var current = $route.current;
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-view on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
});
cleanupLastView();
});
currentElement = clone;
currentScope = current.scope = newScope;
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
} else {
cleanupLastView();
}
}
}
};
}
// This directive is called during the $transclude call of the first `ngView` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngView
// is called.
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
function ngViewFillContentFactory($compile, $controller, $route) {
return {
restrict: 'ECA',
priority: -400,
link: function(scope, $element) {
var current = $route.current,
locals = current.locals;
$element.html(locals.$template);
var link = $compile($element.contents());
if (current.controller) {
locals.$scope = scope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
scope[current.controllerAs] = controller;
}
$element.data('$ngControllerController', controller);
$element.children().data('$ngControllerController', controller);
}
link(scope);
}
};
}
})(window, window.angular);

View File

@@ -0,0 +1,31 @@
/*!
* angular-translate - v2.2.0 - 2014-06-03
* http://github.com/PascalPrecht/angular-translate
* Copyright (c) 2014 ; Licensed MIT
*/
angular.module('pascalprecht.translate').factory('$translateStaticFilesLoader', [
'$q',
'$http',
function ($q, $http) {
return function (options) {
if (!options || (!angular.isString(options.prefix) || !angular.isString(options.suffix))) {
throw new Error('Couldn\'t load static files, no prefix or suffix specified!');
}
var deferred = $q.defer();
$http({
url: [
options.prefix,
options.key,
options.suffix
].join(''),
method: 'GET',
params: ''
}).success(function (data) {
deferred.resolve(data);
}).error(function (data) {
deferred.reject(options.key);
});
return deferred.promise;
};
}
]);

View File

@@ -0,0 +1,883 @@
/*!
* angular-translate - v2.2.0 - 2014-06-03
* http://github.com/PascalPrecht/angular-translate
* Copyright (c) 2014 ; Licensed MIT
*/
angular.module('pascalprecht.translate', ['ng']).run([
'$translate',
function ($translate) {
var key = $translate.storageKey(), storage = $translate.storage();
if (storage) {
if (!storage.get(key)) {
if (angular.isString($translate.preferredLanguage())) {
$translate.use($translate.preferredLanguage());
} else {
storage.set(key, $translate.use());
}
} else {
$translate.use(storage.get(key));
}
} else if (angular.isString($translate.preferredLanguage())) {
$translate.use($translate.preferredLanguage());
}
}
]);
angular.module('pascalprecht.translate').provider('$translate', [
'$STORAGE_KEY',
function ($STORAGE_KEY) {
var $translationTable = {}, $preferredLanguage, $availableLanguageKeys = [], $languageKeyAliases, $fallbackLanguage, $fallbackWasString, $uses, $nextLang, $storageFactory, $storageKey = $STORAGE_KEY, $storagePrefix, $missingTranslationHandlerFactory, $interpolationFactory, $interpolatorFactories = [], $interpolationSanitizationStrategy = false, $loaderFactory, $cloakClassName = 'translate-cloak', $loaderOptions, $notFoundIndicatorLeft, $notFoundIndicatorRight, $postCompilingEnabled = false, NESTED_OBJECT_DELIMITER = '.';
var getLocale = function () {
var nav = window.navigator;
return (nav.language || nav.browserLanguage || nav.systemLanguage || nav.userLanguage || '').split('-').join('_');
};
var negotiateLocale = function (preferred) {
var avail = [], locale = angular.lowercase(preferred), i = 0, n = $availableLanguageKeys.length;
for (; i < n; i++) {
avail.push(angular.lowercase($availableLanguageKeys[i]));
}
if (avail.indexOf(locale) > -1) {
return preferred;
}
if ($languageKeyAliases) {
var alias;
for (var langKeyAlias in $languageKeyAliases) {
var hasWildcardKey = false;
var hasExactKey = $languageKeyAliases.hasOwnProperty(langKeyAlias) && angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
if (langKeyAlias.slice(-1) === '*') {
hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1);
}
if (hasExactKey || hasWildcardKey) {
alias = $languageKeyAliases[langKeyAlias];
if (avail.indexOf(angular.lowercase(alias)) > -1) {
return alias;
}
}
}
}
var parts = preferred.split('_');
if (parts.length > 1 && avail.indexOf(angular.lowercase(parts[0])) > -1) {
return parts[0];
}
return preferred;
};
var translations = function (langKey, translationTable) {
if (!langKey && !translationTable) {
return $translationTable;
}
if (langKey && !translationTable) {
if (angular.isString(langKey)) {
return $translationTable[langKey];
}
} else {
if (!angular.isObject($translationTable[langKey])) {
$translationTable[langKey] = {};
}
angular.extend($translationTable[langKey], flatObject(translationTable));
}
return this;
};
this.translations = translations;
this.cloakClassName = function (name) {
if (!name) {
return $cloakClassName;
}
$cloakClassName = name;
return this;
};
var flatObject = function (data, path, result, prevKey) {
var key, keyWithPath, keyWithShortPath, val;
if (!path) {
path = [];
}
if (!result) {
result = {};
}
for (key in data) {
if (!data.hasOwnProperty(key)) {
continue;
}
val = data[key];
if (angular.isObject(val)) {
flatObject(val, path.concat(key), result, key);
} else {
keyWithPath = path.length ? '' + path.join(NESTED_OBJECT_DELIMITER) + NESTED_OBJECT_DELIMITER + key : key;
if (path.length && key === prevKey) {
keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER);
result[keyWithShortPath] = '@:' + keyWithPath;
}
result[keyWithPath] = val;
}
}
return result;
};
this.addInterpolation = function (factory) {
$interpolatorFactories.push(factory);
return this;
};
this.useMessageFormatInterpolation = function () {
return this.useInterpolation('$translateMessageFormatInterpolation');
};
this.useInterpolation = function (factory) {
$interpolationFactory = factory;
return this;
};
this.useSanitizeValueStrategy = function (value) {
$interpolationSanitizationStrategy = value;
return this;
};
this.preferredLanguage = function (langKey) {
if (langKey) {
$preferredLanguage = langKey;
return this;
}
return $preferredLanguage;
};
this.translationNotFoundIndicator = function (indicator) {
this.translationNotFoundIndicatorLeft(indicator);
this.translationNotFoundIndicatorRight(indicator);
return this;
};
this.translationNotFoundIndicatorLeft = function (indicator) {
if (!indicator) {
return $notFoundIndicatorLeft;
}
$notFoundIndicatorLeft = indicator;
return this;
};
this.translationNotFoundIndicatorRight = function (indicator) {
if (!indicator) {
return $notFoundIndicatorRight;
}
$notFoundIndicatorRight = indicator;
return this;
};
this.fallbackLanguage = function (langKey) {
fallbackStack(langKey);
return this;
};
var fallbackStack = function (langKey) {
if (langKey) {
if (angular.isString(langKey)) {
$fallbackWasString = true;
$fallbackLanguage = [langKey];
} else if (angular.isArray(langKey)) {
$fallbackWasString = false;
$fallbackLanguage = langKey;
}
if (angular.isString($preferredLanguage)) {
$fallbackLanguage.push($preferredLanguage);
}
return this;
} else {
if ($fallbackWasString) {
return $fallbackLanguage[0];
} else {
return $fallbackLanguage;
}
}
};
this.use = function (langKey) {
if (langKey) {
if (!$translationTable[langKey] && !$loaderFactory) {
throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
}
$uses = langKey;
return this;
}
return $uses;
};
var storageKey = function (key) {
if (!key) {
if ($storagePrefix) {
return $storagePrefix + $storageKey;
}
return $storageKey;
}
$storageKey = key;
};
this.storageKey = storageKey;
this.useUrlLoader = function (url) {
return this.useLoader('$translateUrlLoader', { url: url });
};
this.useStaticFilesLoader = function (options) {
return this.useLoader('$translateStaticFilesLoader', options);
};
this.useLoader = function (loaderFactory, options) {
$loaderFactory = loaderFactory;
$loaderOptions = options || {};
return this;
};
this.useLocalStorage = function () {
return this.useStorage('$translateLocalStorage');
};
this.useCookieStorage = function () {
return this.useStorage('$translateCookieStorage');
};
this.useStorage = function (storageFactory) {
$storageFactory = storageFactory;
return this;
};
this.storagePrefix = function (prefix) {
if (!prefix) {
return prefix;
}
$storagePrefix = prefix;
return this;
};
this.useMissingTranslationHandlerLog = function () {
return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
};
this.useMissingTranslationHandler = function (factory) {
$missingTranslationHandlerFactory = factory;
return this;
};
this.usePostCompiling = function (value) {
$postCompilingEnabled = !!value;
return this;
};
this.determinePreferredLanguage = function (fn) {
var locale = fn && angular.isFunction(fn) ? fn() : getLocale();
if (!$availableLanguageKeys.length) {
$preferredLanguage = locale;
} else {
$preferredLanguage = negotiateLocale(locale);
}
return this;
};
this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
if (languageKeys) {
$availableLanguageKeys = languageKeys;
if (aliases) {
$languageKeyAliases = aliases;
}
return this;
}
return $availableLanguageKeys;
};
this.$get = [
'$log',
'$injector',
'$rootScope',
'$q',
function ($log, $injector, $rootScope, $q) {
var Storage, defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'), pendingLoader = false, interpolatorHashMap = {}, langPromises = {}, fallbackIndex, startFallbackIteration;
var $translate = function (translationId, interpolateParams, interpolationId) {
if (angular.isArray(translationId)) {
var translateAll = function (translationIds) {
var results = {};
var promises = [];
var translate = function (translationId) {
var deferred = $q.defer();
var regardless = function (value) {
results[translationId] = value;
deferred.resolve([
translationId,
value
]);
};
$translate(translationId, interpolateParams, interpolationId).then(regardless, regardless);
return deferred.promise;
};
for (var i = 0, c = translationIds.length; i < c; i++) {
promises.push(translate(translationIds[i]));
}
return $q.all(promises).then(function () {
return results;
});
};
return translateAll(translationId);
}
var deferred = $q.defer();
if (translationId) {
translationId = translationId.trim();
}
var promiseToWaitFor = function () {
var promise = $preferredLanguage ? langPromises[$preferredLanguage] : langPromises[$uses];
fallbackIndex = 0;
if ($storageFactory && !promise) {
var langKey = Storage.get($storageKey);
promise = langPromises[langKey];
if ($fallbackLanguage && $fallbackLanguage.length) {
var index = indexOf($fallbackLanguage, langKey);
fallbackIndex = index > -1 ? index += 1 : 0;
$fallbackLanguage.push($preferredLanguage);
}
}
return promise;
}();
if (!promiseToWaitFor) {
determineTranslation(translationId, interpolateParams, interpolationId).then(deferred.resolve, deferred.reject);
} else {
promiseToWaitFor.then(function () {
determineTranslation(translationId, interpolateParams, interpolationId).then(deferred.resolve, deferred.reject);
}, deferred.reject);
}
return deferred.promise;
};
var indexOf = function (array, searchElement) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i] === searchElement) {
return i;
}
}
return -1;
};
var applyNotFoundIndicators = function (translationId) {
if ($notFoundIndicatorLeft) {
translationId = [
$notFoundIndicatorLeft,
translationId
].join(' ');
}
if ($notFoundIndicatorRight) {
translationId = [
translationId,
$notFoundIndicatorRight
].join(' ');
}
return translationId;
};
var useLanguage = function (key) {
$uses = key;
$rootScope.$emit('$translateChangeSuccess');
if ($storageFactory) {
Storage.set($translate.storageKey(), $uses);
}
defaultInterpolator.setLocale($uses);
angular.forEach(interpolatorHashMap, function (interpolator, id) {
interpolatorHashMap[id].setLocale($uses);
});
$rootScope.$emit('$translateChangeEnd');
};
var loadAsync = function (key) {
if (!key) {
throw 'No language key specified for loading.';
}
var deferred = $q.defer();
$rootScope.$emit('$translateLoadingStart');
pendingLoader = true;
$injector.get($loaderFactory)(angular.extend($loaderOptions, { key: key })).then(function (data) {
var translationTable = {};
$rootScope.$emit('$translateLoadingSuccess');
if (angular.isArray(data)) {
angular.forEach(data, function (table) {
angular.extend(translationTable, flatObject(table));
});
} else {
angular.extend(translationTable, flatObject(data));
}
pendingLoader = false;
deferred.resolve({
key: key,
table: translationTable
});
$rootScope.$emit('$translateLoadingEnd');
}, function (key) {
$rootScope.$emit('$translateLoadingError');
deferred.reject(key);
$rootScope.$emit('$translateLoadingEnd');
});
return deferred.promise;
};
if ($storageFactory) {
Storage = $injector.get($storageFactory);
if (!Storage.get || !Storage.set) {
throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or set() method!');
}
}
if (angular.isFunction(defaultInterpolator.useSanitizeValueStrategy)) {
defaultInterpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
}
if ($interpolatorFactories.length) {
angular.forEach($interpolatorFactories, function (interpolatorFactory) {
var interpolator = $injector.get(interpolatorFactory);
interpolator.setLocale($preferredLanguage || $uses);
if (angular.isFunction(interpolator.useSanitizeValueStrategy)) {
interpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
}
interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
});
}
var getTranslationTable = function (langKey) {
var deferred = $q.defer();
if ($translationTable.hasOwnProperty(langKey)) {
deferred.resolve($translationTable[langKey]);
return deferred.promise;
} else {
langPromises[langKey].then(function (data) {
translations(data.key, data.table);
deferred.resolve(data.table);
}, deferred.reject);
}
return deferred.promise;
};
var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
var deferred = $q.defer();
getTranslationTable(langKey).then(function (translationTable) {
if (translationTable.hasOwnProperty(translationId)) {
Interpolator.setLocale(langKey);
deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams));
Interpolator.setLocale($uses);
} else {
deferred.reject();
}
}, deferred.reject);
return deferred.promise;
};
var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
var result, translationTable = $translationTable[langKey];
if (translationTable.hasOwnProperty(translationId)) {
Interpolator.setLocale(langKey);
result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
Interpolator.setLocale($uses);
}
return result;
};
var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
var deferred = $q.defer();
if (fallbackLanguageIndex < $fallbackLanguage.length) {
var langKey = $fallbackLanguage[fallbackLanguageIndex];
getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(function (translation) {
deferred.resolve(translation);
}, function () {
var nextFallbackLanguagePromise = resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
deferred.resolve(nextFallbackLanguagePromise);
});
} else {
if ($missingTranslationHandlerFactory) {
var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
if (resultString !== undefined) {
deferred.resolve(resultString);
} else {
deferred.resolve(translationId);
}
} else {
deferred.resolve(translationId);
}
}
return deferred.promise;
};
var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
var result;
if (fallbackLanguageIndex < $fallbackLanguage.length) {
var langKey = $fallbackLanguage[fallbackLanguageIndex];
result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
if (!result) {
result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
}
}
return result;
};
var fallbackTranslation = function (translationId, interpolateParams, Interpolator) {
return resolveForFallbackLanguage(startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex, translationId, interpolateParams, Interpolator);
};
var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
return resolveForFallbackLanguageInstant(startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex, translationId, interpolateParams, Interpolator);
};
var determineTranslation = function (translationId, interpolateParams, interpolationId) {
var deferred = $q.defer();
var table = $uses ? $translationTable[$uses] : $translationTable, Interpolator = interpolationId ? interpolatorHashMap[interpolationId] : defaultInterpolator;
if (table && table.hasOwnProperty(translationId)) {
var translation = table[translationId];
if (translation.substr(0, 2) === '@:') {
$translate(translation.substr(2), interpolateParams, interpolationId).then(deferred.resolve, deferred.reject);
} else {
deferred.resolve(Interpolator.interpolate(translation, interpolateParams));
}
} else {
if ($missingTranslationHandlerFactory && !pendingLoader) {
$injector.get($missingTranslationHandlerFactory)(translationId, $uses);
}
if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
fallbackTranslation(translationId, interpolateParams, Interpolator).then(function (translation) {
deferred.resolve(translation);
}, function (_translationId) {
deferred.reject(applyNotFoundIndicators(_translationId));
});
} else {
deferred.reject(applyNotFoundIndicators(translationId));
}
}
return deferred.promise;
};
var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {
var result, table = $uses ? $translationTable[$uses] : $translationTable, Interpolator = interpolationId ? interpolatorHashMap[interpolationId] : defaultInterpolator;
if (table && table.hasOwnProperty(translationId)) {
var translation = table[translationId];
if (translation.substr(0, 2) === '@:') {
result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
} else {
result = Interpolator.interpolate(translation, interpolateParams);
}
} else {
if ($missingTranslationHandlerFactory && !pendingLoader) {
$injector.get($missingTranslationHandlerFactory)(translationId, $uses);
}
if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
fallbackIndex = 0;
result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
} else {
result = applyNotFoundIndicators(translationId);
}
}
return result;
};
$translate.preferredLanguage = function () {
return $preferredLanguage;
};
$translate.cloakClassName = function () {
return $cloakClassName;
};
$translate.fallbackLanguage = function (langKey) {
if (langKey !== undefined && langKey !== null) {
fallbackStack(langKey);
if ($loaderFactory) {
if ($fallbackLanguage && $fallbackLanguage.length) {
for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
if (!langPromises[$fallbackLanguage[i]]) {
langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
}
}
}
}
$translate.use($translate.use());
}
if ($fallbackWasString) {
return $fallbackLanguage[0];
} else {
return $fallbackLanguage;
}
};
$translate.useFallbackLanguage = function (langKey) {
if (langKey !== undefined && langKey !== null) {
if (!langKey) {
startFallbackIteration = 0;
} else {
var langKeyPosition = indexOf($fallbackLanguage, langKey);
if (langKeyPosition > -1) {
startFallbackIteration = langKeyPosition;
}
}
}
};
$translate.proposedLanguage = function () {
return $nextLang;
};
$translate.storage = function () {
return Storage;
};
$translate.use = function (key) {
if (!key) {
return $uses;
}
var deferred = $q.defer();
$rootScope.$emit('$translateChangeStart');
var aliasedKey = negotiateLocale(key);
if (aliasedKey) {
key = aliasedKey;
}
if (!$translationTable[key] && $loaderFactory) {
$nextLang = key;
langPromises[key] = loadAsync(key).then(function (translation) {
translations(translation.key, translation.table);
deferred.resolve(translation.key);
if ($nextLang === key) {
useLanguage(translation.key);
$nextLang = undefined;
}
}, function (key) {
$nextLang = undefined;
$rootScope.$emit('$translateChangeError');
deferred.reject(key);
$rootScope.$emit('$translateChangeEnd');
});
} else {
deferred.resolve(key);
useLanguage(key);
}
return deferred.promise;
};
$translate.storageKey = function () {
return storageKey();
};
$translate.isPostCompilingEnabled = function () {
return $postCompilingEnabled;
};
$translate.refresh = function (langKey) {
if (!$loaderFactory) {
throw new Error('Couldn\'t refresh translation table, no loader registered!');
}
var deferred = $q.defer();
function resolve() {
deferred.resolve();
$rootScope.$emit('$translateRefreshEnd');
}
function reject() {
deferred.reject();
$rootScope.$emit('$translateRefreshEnd');
}
$rootScope.$emit('$translateRefreshStart');
if (!langKey) {
var tables = [];
if ($fallbackLanguage && $fallbackLanguage.length) {
for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
tables.push(loadAsync($fallbackLanguage[i]));
}
}
if ($uses) {
tables.push(loadAsync($uses));
}
$q.all(tables).then(function (tableData) {
angular.forEach(tableData, function (data) {
if ($translationTable[data.key]) {
delete $translationTable[data.key];
}
translations(data.key, data.table);
});
if ($uses) {
useLanguage($uses);
}
resolve();
});
} else if ($translationTable[langKey]) {
loadAsync(langKey).then(function (data) {
translations(data.key, data.table);
if (langKey === $uses) {
useLanguage($uses);
}
resolve();
}, reject);
} else {
reject();
}
return deferred.promise;
};
$translate.instant = function (translationId, interpolateParams, interpolationId) {
if (translationId === null || angular.isUndefined(translationId)) {
return translationId;
}
if (angular.isArray(translationId)) {
var results = {};
for (var i = 0, c = translationId.length; i < c; i++) {
results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId);
}
return results;
}
if (angular.isString(translationId) && translationId.length < 1) {
return translationId;
}
if (translationId) {
translationId = translationId.trim();
}
var result, possibleLangKeys = [];
if ($preferredLanguage) {
possibleLangKeys.push($preferredLanguage);
}
if ($uses) {
possibleLangKeys.push($uses);
}
if ($fallbackLanguage && $fallbackLanguage.length) {
possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
}
for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
var possibleLangKey = possibleLangKeys[j];
if ($translationTable[possibleLangKey]) {
if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
result = determineTranslationInstant(translationId, interpolateParams, interpolationId);
}
}
if (typeof result !== 'undefined') {
break;
}
}
if (!result && result !== '') {
result = translationId;
if ($missingTranslationHandlerFactory && !pendingLoader) {
$injector.get($missingTranslationHandlerFactory)(translationId, $uses);
}
}
return result;
};
if ($loaderFactory) {
if (angular.equals($translationTable, {})) {
$translate.use($translate.use());
}
if ($fallbackLanguage && $fallbackLanguage.length) {
for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
}
}
}
return $translate;
}
];
}
]);
angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', [
'$interpolate',
function ($interpolate) {
var $translateInterpolator = {}, $locale, $identifier = 'default', $sanitizeValueStrategy = null, sanitizeValueStrategies = {
escaped: function (params) {
var result = {};
for (var key in params) {
if (params.hasOwnProperty(key)) {
result[key] = angular.element('<div></div>').text(params[key]).html();
}
}
return result;
}
};
var sanitizeParams = function (params) {
var result;
if (angular.isFunction(sanitizeValueStrategies[$sanitizeValueStrategy])) {
result = sanitizeValueStrategies[$sanitizeValueStrategy](params);
} else {
result = params;
}
return result;
};
$translateInterpolator.setLocale = function (locale) {
$locale = locale;
};
$translateInterpolator.getInterpolationIdentifier = function () {
return $identifier;
};
$translateInterpolator.useSanitizeValueStrategy = function (value) {
$sanitizeValueStrategy = value;
return this;
};
$translateInterpolator.interpolate = function (string, interpolateParams) {
if ($sanitizeValueStrategy) {
interpolateParams = sanitizeParams(interpolateParams);
}
return $interpolate(string)(interpolateParams || {});
};
return $translateInterpolator;
}
]);
angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
angular.module('pascalprecht.translate').directive('translate', [
'$translate',
'$q',
'$interpolate',
'$compile',
'$parse',
'$rootScope',
function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {
return {
restrict: 'AE',
scope: true,
compile: function (tElement, tAttr) {
var translateValuesExist = tAttr.translateValues ? tAttr.translateValues : undefined;
var translateInterpolation = tAttr.translateInterpolation ? tAttr.translateInterpolation : undefined;
var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
return function linkFn(scope, iElement, iAttr) {
scope.interpolateParams = {};
iAttr.$observe('translate', function (translationId) {
if (angular.equals(translationId, '') || !angular.isDefined(translationId)) {
scope.translationId = $interpolate(iElement.text().replace(/^\s+|\s+$/g, ''))(scope.$parent);
} else {
scope.translationId = translationId;
}
});
iAttr.$observe('translateDefault', function (value) {
scope.defaultText = value;
});
if (translateValuesExist) {
iAttr.$observe('translateValues', function (interpolateParams) {
if (interpolateParams) {
scope.$parent.$watch(function () {
angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
});
}
});
}
if (translateValueExist) {
var fn = function (attrName) {
iAttr.$observe(attrName, function (value) {
scope.interpolateParams[angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15)] = value;
});
};
for (var attr in iAttr) {
if (iAttr.hasOwnProperty(attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
fn(attr);
}
}
}
var applyElementContent = function (value, scope, successful) {
if (!successful && typeof scope.defaultText !== 'undefined') {
value = scope.defaultText;
}
iElement.html(value);
var globallyEnabled = $translate.isPostCompilingEnabled();
var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
if (globallyEnabled && !locallyDefined || locallyEnabled) {
$compile(iElement.contents())(scope);
}
};
var updateTranslationFn = function () {
if (!translateValuesExist && !translateValueExist) {
return function () {
var unwatch = scope.$watch('translationId', function (value) {
if (scope.translationId && value) {
$translate(value, {}, translateInterpolation).then(function (translation) {
applyElementContent(translation, scope, true);
unwatch();
}, function (translationId) {
applyElementContent(translationId, scope, false);
unwatch();
});
}
}, true);
};
} else {
return function () {
var updateTranslations = function () {
if (scope.translationId && scope.interpolateParams) {
$translate(scope.translationId, scope.interpolateParams, translateInterpolation).then(function (translation) {
applyElementContent(translation, scope, true);
}, function (translationId) {
applyElementContent(translationId, scope, false);
});
}
};
scope.$watch('interpolateParams', updateTranslations, true);
scope.$watch('translationId', updateTranslations);
};
}
}();
var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslationFn);
updateTranslationFn();
scope.$on('$destroy', unbind);
};
}
};
}
]);
angular.module('pascalprecht.translate').directive('translateCloak', [
'$rootScope',
'$translate',
function ($rootScope, $translate) {
return {
compile: function (tElement) {
$rootScope.$on('$translateLoadingSuccess', function () {
tElement.removeClass($translate.cloakClassName());
});
tElement.addClass($translate.cloakClassName());
}
};
}
]);
angular.module('pascalprecht.translate').filter('translate', [
'$parse',
'$translate',
function ($parse, $translate) {
return function (translationId, interpolateParams, interpolation) {
if (!angular.isObject(interpolateParams)) {
interpolateParams = $parse(interpolateParams)(this);
}
return $translate.instant(translationId, interpolateParams, interpolation);
};
}
]);

View File

@@ -0,0 +1,78 @@
/*!
* angular-modal v0.0.3
* (c) 2013 Brian Ford http://briantford.com
* License: MIT
*/
'use strict';
angular.module('btford.modal', []).
factory('btfModal', function ($compile, $rootScope, $controller, $q, $http, $templateCache) {
return function modalFactory (config) {
if ((+!!config.template) + (+!!config.templateUrl) !== 1) {
throw new Error('Expected modal to have exacly one of either `template` or `templateUrl`');
}
var template = config.template,
controller = config.controller || angular.noop,
controllerAs = config.controllerAs,
container = angular.element(config.container || document.body),
element = null,
html;
if (config.template) {
var deferred = $q.defer();
deferred.resolve(config.template);
html = deferred.promise;
} else {
html = $http.get(config.templateUrl, {
cache: $templateCache
}).
then(function (response) {
return response.data;
});
}
function activate (locals) {
html.then(function (html) {
if (!element) {
attach(html, locals);
}
});
}
function attach (html, locals) {
element = angular.element(html);
/*
* Changed by James Muehlner to append to the end of the document instead
* of the beginning.
*/
container.append(element);
var scope = $rootScope.$new();
if (locals) {
for (var prop in locals) {
scope[prop] = locals[prop];
}
}
var ctrl = $controller(controller, { $scope: scope });
if (controllerAs) {
scope[controllerAs] = ctrl;
}
$compile(element)(scope);
}
function deactivate () {
if (element) {
element.remove();
element = null;
}
}
return {
activate: activate,
deactivate: deactivate
};
};
});

View File

@@ -0,0 +1,21 @@
/*!
* 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.
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,400 +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.
*/
/**
* General set of UI elements and UI-related functions regarding user login and
* connection management.
*/
var GuacamoleRootUI = {
"sections": {
"login_form" : document.getElementById("login-form"),
"recent_connections" : document.getElementById("recent-connections"),
"all_connections" : document.getElementById("all-connections")
},
"messages": {
"login_error" : document.getElementById("login-error"),
"no_recent_connections" : document.getElementById("no-recent")
},
"fields": {
"username" : document.getElementById("username"),
"password" : document.getElementById("password"),
},
"buttons": {
"login" : document.getElementById("login"),
"logout" : document.getElementById("logout"),
"manage" : document.getElementById("manage")
},
"views": {
"login" : document.getElementById("login-ui"),
"connections" : document.getElementById("connection-list-ui")
},
"parameters" : null
};
// Get parameters from query string
GuacamoleRootUI.parameters = window.location.search.substring(1) || null;
/**
* A connection UI object which can be easily added to a list of connections
* for sake of display.
*
* @param {String} id The ID of this object, including prefix.
* @param {String} name The name that should be displayed.
*/
GuacamoleRootUI.RecentConnection = function(id, name) {
/**
* The ID of this object, including prefix.
* @type String
*/
this.id = id;
/**
* The displayable name of this object.
* @type String
*/
this.name = name;
// Create connection display elements
var element = GuacUI.createElement("div", "connection");
var thumbnail = GuacUI.createChildElement(element, "div", "thumbnail");
var caption = GuacUI.createChildElement(element, "div", "caption");
var name_element = GuacUI.createChildElement(caption, "span", "name");
// Connect on click
element.addEventListener("click", function(e) {
// Prevent click from affecting parent
e.stopPropagation();
e.preventDefault();
// Open connection
GuacUI.openObject(id, GuacamoleRootUI.parameters);
}, false);
// Set name
name_element.textContent = name;
// Add screenshot if available
var thumbnail_url = GuacamoleHistory.get(id).thumbnail;
if (thumbnail_url) {
// Create thumbnail element
var thumb_img = GuacUI.createChildElement(thumbnail, "img");
thumb_img.src = thumbnail_url;
}
/**
* Returns the DOM element representing this connection.
*/
this.getElement = function() {
return element;
};
/**
* 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. Here, each connection
* is a GuacamoleRootUI.RecentConnection.
*/
GuacamoleRootUI.recentConnections = {};
/**
* Set of all connections, indexed by ID. Each connection is a
* GuacamoleService.Connection.
*/
GuacamoleRootUI.connections = {};
/**
* Adds the given RecentConnection to the recent connections list.
*/
GuacamoleRootUI.addRecentConnection = function(id, name) {
// Create recent connection object
var connection = new GuacamoleRootUI.RecentConnection(id, name);
// Add connection object to list of thumbnailed connections
GuacamoleRootUI.recentConnections[connection.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
* the connection list UI (or the client for the only available
* connection, if there is only one) is displayed if the user is
* authenticated.
*/
GuacamoleRootUI.reset = function() {
function hasEntry(object) {
for (var name in object)
return true;
return false;
}
// Read root group
var root_group;
try {
root_group = GuacamoleService.Connections.list(GuacamoleRootUI.parameters);
// Show admin elements if admin permissions available
var permissions = GuacamoleService.Permissions.list(null, GuacamoleRootUI.parameters);
if (permissions.administer
|| permissions.create_connection
|| permissions.create_user
|| hasEntry(permissions.update_user)
|| hasEntry(permissions.remove_user)
|| hasEntry(permissions.administer_user)
|| hasEntry(permissions.update_connection)
|| hasEntry(permissions.remove_connection)
|| hasEntry(permissions.administer_connection))
GuacUI.addClass(document.body, "admin");
else
GuacUI.removeClass(document.body, "admin");
}
catch (e) {
// Show login UI if unable to get connections
GuacamoleRootUI.views.login.style.display = "";
GuacamoleRootUI.views.connections.style.display = "none";
return;
}
// Create group view
var group_view = new GuacUI.GroupView(root_group, GuacUI.GroupView.SHOW_CONNECTIONS);
GuacamoleRootUI.sections.all_connections.appendChild(group_view.getElement());
// Add any connections with thumbnails
for (var connection_id in group_view.connections) {
// Get corresponding connection
var connection = group_view.connections[connection_id];
// If thumbnail exists, add to recent connections
if (GuacamoleHistory.get("c/" + connection_id).thumbnail)
GuacamoleRootUI.addRecentConnection("c/" + connection_id, connection.name);
}
// Add any groups with thumbnails
for (var group_id in group_view.groups) {
// Get corresponding group
var group = group_view.groups[group_id];
// If thumbnail exists, add to recent connections
if (GuacamoleHistory.get("g/" + group_id).thumbnail)
GuacamoleRootUI.addRecentConnection("g/" + group_id, group.name);
}
// Open connections when clicked
group_view.onconnectionclick = function(connection) {
GuacUI.openConnection(connection.id, GuacamoleRootUI.parameters);
};
// Open connection groups when clicked
group_view.ongroupclick = function(group) {
// Connect if balancing
if (group.type === GuacamoleService.ConnectionGroup.Type.BALANCING)
GuacUI.openConnectionGroup(group.id, GuacamoleRootUI.parameters);
};
// Save all connections for later reference
GuacamoleRootUI.connections = group_view.connections;
// If connections could be retrieved, display list
GuacamoleRootUI.views.login.style.display = "none";
GuacamoleRootUI.views.connections.style.display = "";
};
GuacamoleHistory.onchange = function(id, old_entry, new_entry) {
// Get existing connection, if any
var connection = GuacamoleRootUI.recentConnections[id];
// If we are adding or updating a connection
if (new_entry) {
// Ensure connection is added
if (!connection) {
// If connection not actually defined, storage must be being
// modified externally. Stop early.
if (!GuacamoleRootUI.connections[id]) return;
// Create new connection
GuacamoleRootUI.addRecentConnection(id, connection.name);
}
// Set new thumbnail
connection.setThumbnail(new_entry.thumbnail);
}
// Otherwise, delete existing connection
else {
GuacamoleRootUI.sections.recent_connections.removeChild(
connection.getElement());
delete GuacamoleRootUI.recentConnections[id];
// Display "No recent connections" message if none left
if (GuacamoleRootUI.recentConnections.length === 0)
GuacamoleRootUI.messages.no_recent_connections.style.display = "";
}
};
/*
* This window has no name. We need it to have no name. If someone navigates
* to the root UI within the same window as a previous connection, we need to
* remove the name from that window such that new attempts to use that previous
* connection do not replace the contents of this very window.
*/
window.name = "";
/*
* Set handler for logout
*/
GuacamoleRootUI.buttons.logout.onclick = function() {
window.location.href = "logout";
};
/*
* Set handler for admin
*/
GuacamoleRootUI.buttons.manage.onclick = function() {
window.location.href = "admin.xhtml";
};
/*
* Set handler for login
*/
GuacamoleRootUI.sections.login_form.onsubmit = function() {
try {
GuacUI.removeClass(GuacamoleRootUI.views.login, "error");
// Attempt login
GuacamoleService.Auth.login(
GuacamoleRootUI.fields.username.value,
GuacamoleRootUI.fields.password.value
);
// Ensure username/password fields are blurred after login attempt
GuacamoleRootUI.fields.username.blur();
GuacamoleRootUI.fields.password.blur();
// Reset UI
GuacamoleRootUI.reset();
}
catch (e) {
window.setTimeout(function() {
// Display error
GuacUI.addClass(GuacamoleRootUI.views.login, "error");
GuacamoleRootUI.messages.login_error.textContent = e.message;
// Reset and refocus password field
GuacamoleRootUI.fields.password.value = "";
GuacamoleRootUI.fields.password.focus();
}, 1);
}
// Always cancel submit
return false;
};
/*
* Turn off autocorrect and autocapitalization on usename
*/
GuacamoleRootUI.fields.username.setAttribute("autocorrect", "off");
GuacamoleRootUI.fields.username.setAttribute("autocapitalize", "off");
/*
* Initialize UI
*/
GuacamoleRootUI.reset();
/*
* Make sure body has an associated touch event handler such that CSS styles
* will work in browsers that require this.
*/
document.body.ontouchstart = function() {};

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ img {
} }
.software-cursor { .software-cursor {
cursor: url('../images/mouse/blank.gif'),url('../images/mouse/blank.cur'),default; cursor: url('images/mouse/blank.gif'),url('images/mouse/blank.cur'),default;
overflow: hidden; overflow: hidden;
cursor: none; cursor: none;
} }
@@ -114,9 +114,13 @@ div.dialog p {
margin: 0; margin: 0;
} }
div#main { div.main {
overflow: auto; overflow: auto;
position: absolute; position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
} }
div.displayOuter { div.displayOuter {
@@ -195,7 +199,7 @@ div.magnifier {
right: 0; right: 0;
height: 32px; height: 32px;
background-image: url('../images/arrows/arrows-u.png'); background-image: url('images/arrows/arrows-u.png');
} }
@@ -206,7 +210,7 @@ div.magnifier {
right: 0; right: 0;
height: 32px; height: 32px;
background-image: url('../images/arrows/arrows-d.png'); background-image: url('images/arrows/arrows-d.png');
} }
@@ -217,7 +221,7 @@ div.magnifier {
left: 0; left: 0;
width: 32px; width: 32px;
background-image: url('../images/arrows/arrows-l.png'); background-image: url('images/arrows/arrows-l.png');
} }
@@ -228,7 +232,7 @@ div.magnifier {
right: 0; right: 0;
width: 32px; width: 32px;
background-image: url('../images/arrows/arrows-r.png'); background-image: url('images/arrows/arrows-r.png');
} }
@@ -375,7 +379,7 @@ p.hint {
.notification .close { .notification .close {
background: url('../images/action-icons/guac-close.png'); background: url('images/action-icons/guac-close.png');
background-size: 10px 10px; background-size: 10px 10px;
-moz-background-size: 10px 10px; -moz-background-size: 10px 10px;
-webkit-background-size: 10px 10px; -webkit-background-size: 10px 10px;
@@ -464,7 +468,7 @@ p.hint {
.upload.notification .progress, .upload.notification .progress,
.download.notification .progress { .download.notification .progress {
background: #C2C2C2 url('../images/progress.png'); background: #C2C2C2 url('images/progress.png');
background-size: 16px 16px; background-size: 16px 16px;
-moz-background-size: 16px 16px; -moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px; -webkit-background-size: 16px 16px;

View File

@@ -33,7 +33,7 @@ input[type=checkbox], input[type=number], input[type=text], input[type=radio], l
-webkit-tap-highlight-color: rgba(128,192,128,0.5); -webkit-tap-highlight-color: rgba(128,192,128,0.5);
} }
input[type=submit], button { input[type=submit], button, a.button {
-webkit-appearance: none; -webkit-appearance: none;
} }
@@ -59,7 +59,9 @@ textarea {
overflow: auto; overflow: auto;
} }
input[type="submit"], button { input[type="submit"], button, a.button {
text-decoration: none;
background-color: #3C3C3C; background-color: #3C3C3C;
@@ -85,11 +87,11 @@ input[type="submit"], button {
} }
input[type="submit"]:hover, button:hover { input[type="submit"]:hover, button:hover, a.button:hover {
background-color: #5A5A5A; background-color: #5A5A5A;
} }
input[type="submit"]:active, button:active { input[type="submit"]:active, button:active, a.button:active {
background-color: #2C2C2C; background-color: #2C2C2C;
@@ -99,15 +101,15 @@ input[type="submit"]:active, button:active {
1px 1px 0.25em rgba(255, 255, 255, 0.25); 1px 1px 0.25em rgba(255, 255, 255, 0.25);
} }
button.danger { button.danger, a.button.danger {
background: #A43; background: #A43;
} }
button.danger:hover { button.danger:hover, a.button.danger:hover {
background: #C54; background: #C54;
} }
button.danger:active { button.danger:active, a.button.danger:active {
background: #932; background: #932;
} }
@@ -168,13 +170,16 @@ div.section {
padding: 1em; padding: 1em;
} }
.dialog.edit {
max-height: 100%;
}
.dialog { .dialog {
max-width: 100%; max-width: 100%;
width: 8in; width: 8in;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-height: 100%;
overflow: auto; overflow: auto;
border: 1px solid rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 0, 0, 0.5);
@@ -284,10 +289,6 @@ div.section {
opacity: 0.5; opacity: 0.5;
} }
.choice .list-item .usage {
display: none;
}
.choice .list-item.in-use { .choice .list-item.in-use {
opacity: 1; opacity: 1;
} }
@@ -393,19 +394,19 @@ div.section {
*/ */
.icon.user { .icon.user {
background-image: url('../images/user-icons/guac-user.png'); background-image: url('images/user-icons/guac-user.png');
} }
.icon.user.add { .icon.user.add {
background-image: url('../images/action-icons/guac-user-add.png'); background-image: url('images/action-icons/guac-user-add.png');
} }
.icon.connection { .icon.connection {
background-image: url('../images/protocol-icons/guac-plug.png'); background-image: url('images/protocol-icons/guac-plug.png');
} }
.icon.connection.add { .icon.connection.add {
background-image: url('../images/action-icons/guac-monitor-add.png'); background-image: url('images/action-icons/guac-monitor-add.png');
} }
.protocol { .protocol {
@@ -415,7 +416,7 @@ div.section {
.protocol .icon { .protocol .icon {
width: 24px; width: 24px;
height: 24px; height: 24px;
background-image: url('../images/protocol-icons/guac-plug.png'); background-image: url('images/protocol-icons/guac-plug.png');
background-size: 16px 16px; background-size: 16px 16px;
-moz-background-size: 16px 16px; -moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px; -webkit-background-size: 16px 16px;
@@ -427,18 +428,13 @@ div.section {
.protocol .icon.ssh, .protocol .icon.ssh,
.protocol .icon.telnet { .protocol .icon.telnet {
background-image: url('../images/protocol-icons/guac-text.png'); background-image: url('images/protocol-icons/guac-text.png');
} }
.protocol .icon.vnc, .protocol .icon.vnc,
.protocol .icon.rdp { .protocol .icon.rdp {
background-image: url('../images/protocol-icons/guac-monitor.png'); background-image: url('images/protocol-icons/guac-monitor.png');
} }
.connection .thumbnail {
display: none;
}
/* /*
* Groups * Groups
*/ */
@@ -446,7 +442,6 @@ div.section {
.group > .children { .group > .children {
margin-left: 13px; margin-left: 13px;
padding-left: 6px; padding-left: 6px;
display: none;
} }
.group.expanded > .children { .group.expanded > .children {
@@ -454,31 +449,23 @@ div.section {
border-left: 1px dotted rgba(0, 0, 0, 0.25); border-left: 1px dotted rgba(0, 0, 0, 0.25);
} }
.group > .caption .icon.type {
display: none;
}
.group.balancer > .caption .icon.type { .group.balancer > .caption .icon.type {
display: inline-block; display: inline-block;
background-image: url('../images/protocol-icons/guac-monitor.png'); background-image: url('images/protocol-icons/guac-monitor.png');
} }
.group > .caption .icon.group { .group > .caption .icon.group {
opacity: 0.75; opacity: 0.75;
background-image: url('../images/group-icons/guac-closed.png'); background-image: url('images/group-icons/guac-closed.png');
} }
.group.expanded > .caption .icon.group { .group .icon.type.group.expanded {
background-image: url('../images/group-icons/guac-open.png'); background-image: url('images/group-icons/guac-open.png');
} }
.group.empty > .caption .icon.group { .group .icon.type.group.empty {
opacity: 0.25; opacity: 0.25;
background-image: url('../images/group-icons/guac-open.png'); background-image: url('images/group-icons/guac-open.png');
}
.group.empty.balancer > .caption .icon.group {
display: none;
} }
/* /*
@@ -498,14 +485,14 @@ div.section {
font-size: 0.75em; font-size: 0.75em;
} }
#connections input.name, .connections input.name,
#users input.name { .users input.name {
max-width: 80%; max-width: 80%;
width: 20em; width: 20em;
} }
#connection-list, .connection-list,
#user-list { .user-list {
border: 1px solid rgba(0, 0, 0, 0.25); border: 1px solid rgba(0, 0, 0, 0.25);
min-height: 20em; min-height: 20em;
-moz-border-radius: 0.2em; -moz-border-radius: 0.2em;
@@ -514,30 +501,18 @@ div.section {
border-radius: 0.2em; border-radius: 0.2em;
} }
#connections #add-connection, .connections .add-connection,
#connections #add-connection-group, .connections .add-connection-group,
#users #add-user { .users .add-user {
font-size: 0.8em; font-size: 0.8em;
} }
#connection-add-form, .connection-add-form,
#user-add-form { .user-add-form {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
body:not(.manage-connections) .require-manage-connections, div.logout-panel {
body:not(.manage-users) .require-manage-users {
display: none;
}
body:not(.add-connections) #add-connection,
body:not(.add-connection-groups) #add-connection-group,
body:not(.add-users) #user-add-form {
display: none;
display: none;
}
div#logout-panel {
padding: 0.45em; padding: 0.45em;
text-align: right; text-align: right;
float: right; float: right;
@@ -592,19 +567,19 @@ div#logout-panel {
} }
.icon.first-page { .icon.first-page {
background-image: url('../images/action-icons/guac-first-page.png'); background-image: url('images/action-icons/guac-first-page.png');
} }
.icon.prev-page { .icon.prev-page {
background-image: url('../images/action-icons/guac-prev-page.png'); background-image: url('images/action-icons/guac-prev-page.png');
} }
.icon.next-page { .icon.next-page {
background-image: url('../images/action-icons/guac-next-page.png'); background-image: url('images/action-icons/guac-next-page.png');
} }
.icon.last-page { .icon.last-page {
background-image: url('../images/action-icons/guac-last-page.png'); background-image: url('images/action-icons/guac-last-page.png');
} }
.buttons, .buttons,
@@ -613,9 +588,9 @@ div#logout-panel {
margin: 1em; margin: 1em;
} }
button#logout { button.logout, a.button {
background-image: url('../images/action-icons/guac-logout.png'); background-image: url('images/action-icons/guac-logout.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1em; background-size: 1em;
background-position: 0.5em 0.45em; background-position: 0.5em 0.45em;

View File

@@ -0,0 +1,245 @@
{
"index": {
"title" : "Guacamole 0.9.1"
},
"login": {
"loginError" : "Invalid Login",
"username" : "Username",
"password" : "Password",
"login" : "Login"
},
"home": {
"manage" : "Manage",
"logout" : "Logout",
"recentConnections" : "Recent Connections",
"noRecentConnections" : "No recent Connections.",
"allConnections" : "All Connections",
"clipboard" : "Clipboard",
"clipboardDescription" : "Text copied/cut within Guacamole will appear here. Changes to the text will affect the remote clipboard, and will be pastable within the remote desktop. Use the textbox below as an interface between the client and server clipboards.",
"settings" : "Settings",
"autoFitDisplay" : "Auto-fit display to browser window",
"autoFitDescription" : "If checked, remote displays are automatically scaled to exactly fit within the browser window. If unchecked, remote displays are always shown at their natural resolution, even if doing so causes the display to extend beyond the bounds of the window.",
"disableSound" : "Disable Sound",
"disableSoundDescription" : "If on a device or network where bandwidth usage must be kept to a minimum, you may wish to check this box and disable sound. This can also be necessary if a device doesn't actually support sound, but claims to, resulting in wasted bandwidth.",
"disableSoundNote" : "Changing this setting will only affect future connections."
},
"manage": {
"back" : "Back",
"logout" : "Logout",
"administration" : "Administration",
"users" : "Users",
"usersDescription" : "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.",
"addUser" : "Add User",
"connections" : "Connections",
"connectionsDescription" : "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.",
"newConnection" : "New Connection",
"newGroup" : "New Group",
"edit": {
"connection": {
"cancel" : "Cancel",
"save" : "Save",
"delete" : "Delete",
"protocol" : "Protocol:",
"root" : "ROOT",
"location" : "Location:",
"name" : "Name:",
"history" : {
"connectionNotUsed" : "This connection has not yet been used.",
"usageHistory" : "Usage History:",
"username" : "Username",
"startTime" : "Start Time",
"duration" : "Duration",
"activeNow" : "Active Now"
}
},
"connectionGroup": {
"cancel" : "Cancel",
"save" : "Save",
"delete" : "Delete",
"usageHistory" : "Usage History:",
"type" : {
"label" : "Type",
"organizational" : "Organizational",
"balancing" : "Balancing"
},
"root" : "ROOT",
"location" : "Location:",
"name" : "Name:"
},
"user": {
"cancel" : "Cancel",
"save" : "Save",
"delete" : "Delete",
"properties" : "Properties:",
"password" : "Password:",
"passwordMatch" : "Re-enter Password:",
"permissions" : "Permissions:",
"administerSystem" : "Administer system:",
"createUser" : "Create new users:",
"createConnection" : "Create new connections:",
"createConnectionGroup" : "Create new connection groups:",
"connections" : "Connections:"
}
}
},
"protocol": {
"vnc": {
"label": "VNC",
"parameters": {
"hostname" : {"label": "Hostname"},
"port" : {"label": "Port"},
"password" : {"label": "Password"},
"read-only" : {"label": "Read-only"},
"swap-red-blue" : {"label": "Swap red/blue components"},
"cursor" : {
"label": "Cursor",
"options": {
"local" : "Local",
"remote" : "Remote"
}
},
"color-depth" : {
"label": "Color Depth",
"options": {
"8" : "256 color",
"16" : "Low color (16-bit)",
"24" : "True color (24-bit)",
"32" : "True color (32-bit)"
}
},
"dest-host" : {"label": "Repeater destination host"},
"dest-port" : {"label": "Repeater destination port"},
"enable-audio" : {"label": "Enable audio"},
"audio-servername" : {"label": "Audio server name"}
}
},
"rdp": {
"label": "RDP",
"parameters": {
"hostname" : {"label": "Hostname"},
"port" : {"label": "Port"},
"username" : {"label": "Username"},
"password" : {"label": "Password"},
"domain" : {"label": "Domain"},
"initial-program" : {"label": "Initial Program"},
"width" : {"label": "Width"},
"height" : {"label": "Height"},
"color-depth" : {
"label": "Color Depth",
"options": {
"8" : "256 color",
"16" : "Low color (16-bit)",
"24" : "True color (24-bit)",
"32" : "True color (32-bit)"
}
},
"server-layout" : {
"label": "Keyboard layout",
"options": {
"empty" : "(default)",
"en-us-qwerty" : "US English (Qwerty)",
"fr-fr-azerty" : "French (Azerty)",
"de-de-qwertz" : "German (Qwertz)",
"failsafe" : "Unicode"
}
},
"console" : {"label": "Administrator console"},
"console-audio" : {"label": "Support audio in console"},
"disable-audio" : {"label": "Disable audio"},
"enable-printing" : {"label": "Enable printing"},
"enable-drive" : {"label": "Enable drive"},
"drive-path" : {"label": "Drive path"},
"security" : {
"label": "Security mode",
"options": {
"empty" : "(default)",
"rdp" : "RDP encryption",
"tls" : "TLS encryption",
"nla" : "NLA (Network Level Authentication)",
"any" : "Any"
}
},
"disable-auth" : {"label": "Disable authentication"},
"ignore-cert" : {"label": "Ignore server certificate"},
"remote-app" : {"label": "RemoteApp program"},
"remote-app-dir" : {"label": "RemoteApp working directory"},
"remote-app-args" : {"label": "RemoteApp parameters"}
}
},
"ssh": {
"label": "SSH",
"parameters": {
"hostname" : {"label": "Hostname"},
"port" : {"label": "Port"},
"username" : {"label": "Username"},
"password" : {"label": "Password"},
"font-name" : {"label": "Font name"},
"font-size" : {
"label": "Font size",
"options": {
"empty" : "",
"8" : "8",
"9" : "9",
"10" : "10",
"11" : "11",
"12" : "12",
"14" : "14",
"18" : "18",
"24" : "24",
"30" : "30",
"36" : "36",
"48" : "48",
"60" : "60",
"72" : "72",
"96" : "96"
}
},
"enable-sftp" : {"label": "Enable SFTP"},
"private-key" : {"label": "Private key"},
"passphrase" : {"label": "Passphrase"}
}
}
},
"client": {
"ctrl" : "Ctrl",
"alt" : "Alt",
"esc" : "Esc",
"tab" : "Tab",
"clipboard" : "Clipboard",
"copiedText" : "Text copied/cut within Guacamole will appear here. Changes to the text below will affect the remote clipboard.",
"inputMethod" : "Input method",
"none" : "None",
"noneDesc" : "No input method is used. Keyboard input is accepted from a connected, physical keyboard.",
"textInput" : "Text input",
"textInputDesc" : "Allow typing of text, and emulate keyboard events based on the typed text. This is necessary for devices such as mobile phones that lack a physical keyboard.",
"osk" : "On-screen keyboard",
"oskDesc" : "Display and accept input from the built-in Guacamole on-screen keyboard. The on-screen keyboard allows typing of key combinations that may otherwise be impossible (such as Ctrl-Alt-Del).",
"mouseMode" : "Mouse emulation mode",
"mouseModeDesc" : "Determines how the remote mouse behaves with respect to touches.",
"touchscreen" : "Touchscreen",
"touchscreenDesc" : "Tap to click. The click occurs at the location of the touch.",
"touchpad" : "Touchpad",
"touchpadDesc" : "Drag to move the mouse pointer and tap to click. The click occurs at the location of the pointer.",
"display" : "Display",
"oneHundredPercent" : "100%",
"autoFit" : "Automatically fit to browser window",
"error" : {
"reconnect" : "Reconnect",
"connectionErrorTitle" : "Connection Error",
"clientErrors" : {
"0x0201" : "This connection has been closed because the server is busy. Please wait a few minutes and try again.",
"0x0202" : "The Guacamole server has closed the connection because the remote desktop is taking too long to respond. Please try again or contact your system administrator.",
"0x0203" : "The remote desktop server encountered an error and has closed the connection. Please try again or contact your system administrator.",
"0x0205" : "This connection has been closed because it conflicts with another connection. Please try again later.",
"0x0301" : "Log in failed. Please reconnect and try again.",
"0x0303" : "You do not have permission to access this connection. If you require access, please ask your system administrator to add you the list of allowed users, or check your system settings.",
"0x0308" : "The Guacamole server has closed the connection because there has been no response from your browser for long enough that it appeared to be disconnected. This is commonly caused by network problems, such as spotty wireless signal, or simply very slow network speeds. Please check your network and try again.",
"0x031D" : "The Guacamole server is denying access to this connection because you have exhausted the limit for simultaneous connection use by an individual user. Please close one or more connections and try again.",
"DEFAULT" : "An internal error has occurred within the Guacamole server, and the connection has been terminated. If the problem persists, please notify your system administrator, or check your system logs."
}
}
}
}