| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /** |
| * The controller for the root of the application. |
| */ |
| angular.module('index').controller('indexController', ['$scope', '$injector', |
| function indexController($scope, $injector) { |
| |
| // Required types |
| const Error = $injector.get('Error'); |
| |
| // Required services |
| const $document = $injector.get('$document'); |
| const $location = $injector.get('$location'); |
| const $route = $injector.get('$route'); |
| const $window = $injector.get('$window'); |
| const clipboardService = $injector.get('clipboardService'); |
| const guacNotification = $injector.get('guacNotification'); |
| const guacClientManager = $injector.get('guacClientManager'); |
| |
| /** |
| * The error that prevents the current page from rendering at all. If no |
| * such error has occurred, this will be null. |
| * |
| * @type Error |
| */ |
| $scope.fatalError = null; |
| |
| /** |
| * The notification service. |
| */ |
| $scope.guacNotification = guacNotification; |
| |
| /** |
| * All currently-active connections, grouped into their corresponding |
| * tiled views. |
| * |
| * @type ManagedClientGroup[] |
| */ |
| $scope.getManagedClientGroups = guacClientManager.getManagedClientGroups; |
| |
| /** |
| * The message to display to the user as instructions for the login |
| * process. |
| * |
| * @type TranslatableMessage |
| */ |
| $scope.loginHelpText = null; |
| |
| /** |
| * Whether the user has selected to log back in after having logged out. |
| * |
| * @type boolean |
| */ |
| $scope.reAuthenticating = false; |
| |
| /** |
| * The credentials that the authentication service is has already accepted, |
| * pending additional credentials, if any. If the user is logged in, or no |
| * credentials have been accepted, this will be null. If credentials have |
| * been accepted, this will be a map of name/value pairs corresponding to |
| * the parameters submitted in a previous authentication attempt. |
| * |
| * @type Object.<String, String> |
| */ |
| $scope.acceptedCredentials = null; |
| |
| /** |
| * The credentials that the authentication service is currently expecting, |
| * if any. If the user is logged in, this will be null. |
| * |
| * @type Field[] |
| */ |
| $scope.expectedCredentials = null; |
| |
| /** |
| * Possible overall states of the client side of the web application. |
| * |
| * @enum {string} |
| */ |
| var ApplicationState = { |
| |
| /** |
| * A non-interactive authentication attempt failed. |
| */ |
| AUTOMATIC_LOGIN_REJECTED : 'automaticLoginRejected', |
| |
| /** |
| * The application has fully loaded but is awaiting credentials from |
| * the user before proceeding. |
| */ |
| AWAITING_CREDENTIALS : 'awaitingCredentials', |
| |
| /** |
| * A fatal error has occurred that will prevent the client side of the |
| * application from functioning properly. |
| */ |
| FATAL_ERROR : 'fatalError', |
| |
| /** |
| * The application has just started within the user's browser and has |
| * not yet settled into any specific state. |
| */ |
| LOADING : 'loading', |
| |
| /** |
| * The user has manually logged out. |
| */ |
| LOGGED_OUT : 'loggedOut', |
| |
| /** |
| * The application has fully loaded and the user has logged in |
| */ |
| READY : 'ready' |
| |
| }; |
| |
| /** |
| * The current overall state of the client side of the application. |
| * Possible values are defined by {@link ApplicationState}. |
| * |
| * @type string |
| */ |
| $scope.applicationState = ApplicationState.LOADING; |
| |
| /** |
| * Basic page-level information. |
| */ |
| $scope.page = { |
| |
| /** |
| * The title of the page. |
| * |
| * @type String |
| */ |
| title: '', |
| |
| /** |
| * The name of the CSS class to apply to the page body, if any. |
| * |
| * @type String |
| */ |
| bodyClassName: '' |
| |
| }; |
| |
| // Add default destination for input events |
| var sink = new Guacamole.InputSink(); |
| $document[0].body.appendChild(sink.getElement()); |
| |
| // Create event listeners at the global level |
| var keyboard = new Guacamole.Keyboard($document[0]); |
| keyboard.listenTo(sink.getElement()); |
| |
| // Broadcast keydown events |
| keyboard.onkeydown = function onkeydown(keysym) { |
| |
| // Do not handle key events if not logged in |
| if ($scope.applicationState !== ApplicationState.READY) |
| return true; |
| |
| // Warn of pending keydown |
| var guacBeforeKeydownEvent = $scope.$broadcast('guacBeforeKeydown', keysym, keyboard); |
| if (guacBeforeKeydownEvent.defaultPrevented) |
| return true; |
| |
| // If not prevented via guacBeforeKeydown, fire corresponding keydown event |
| var guacKeydownEvent = $scope.$broadcast('guacKeydown', keysym, keyboard); |
| return !guacKeydownEvent.defaultPrevented; |
| |
| }; |
| |
| // Broadcast keyup events |
| keyboard.onkeyup = function onkeyup(keysym) { |
| |
| // Do not handle key events if not logged in or if a notification is |
| // shown |
| if ($scope.applicationState !== ApplicationState.READY) |
| return; |
| |
| // Warn of pending keyup |
| var guacBeforeKeydownEvent = $scope.$broadcast('guacBeforeKeyup', keysym, keyboard); |
| if (guacBeforeKeydownEvent.defaultPrevented) |
| return; |
| |
| // If not prevented via guacBeforeKeyup, fire corresponding keydown event |
| $scope.$broadcast('guacKeyup', keysym, keyboard); |
| |
| }; |
| |
| // Release all keys when window loses focus |
| $window.onblur = function () { |
| keyboard.reset(); |
| }; |
| |
| // Release all keys upon form submission (there may not be corresponding |
| // keyup events for key presses involved in submitting a form) |
| $document.on('submit', function formSubmitted() { |
| keyboard.reset(); |
| }); |
| |
| // Attempt to read the clipboard if it may have changed |
| $window.addEventListener('load', clipboardService.resyncClipboard, true); |
| $window.addEventListener('copy', clipboardService.resyncClipboard); |
| $window.addEventListener('cut', clipboardService.resyncClipboard); |
| $window.addEventListener('focus', function focusGained(e) { |
| |
| // Only recheck clipboard if it's the window itself that gained focus |
| if (e.target === $window) |
| clipboardService.resyncClipboard(); |
| |
| }, true); |
| |
| /** |
| * Sets the current overall state of the client side of the |
| * application to the given value. Possible values are defined by |
| * {@link ApplicationState}. The title and class associated with the |
| * current page are automatically reset to the standard values applicable |
| * to the application as a whole (rather than any specific page). |
| * |
| * @param {!string} state |
| * The state to assign, as defined by {@link ApplicationState}. |
| */ |
| const setApplicationState = function setApplicationState(state) { |
| $scope.applicationState = state; |
| $scope.page.title = 'APP.NAME'; |
| $scope.page.bodyClassName = ''; |
| }; |
| |
| /** |
| * Navigates the user back to the root of the application (or reloads the |
| * current route and controller if the user is already there), effectively |
| * forcing reauthentication. If the user is not logged in, this will result |
| * in the login screen appearing. |
| */ |
| $scope.reAuthenticate = function reAuthenticate() { |
| |
| $scope.reAuthenticating = true; |
| |
| // Clear out URL state to conveniently bring user back to home screen |
| // upon relogin |
| if ($location.path() !== '/') |
| $location.url('/'); |
| else |
| $route.reload(); |
| |
| }; |
| |
| // Display login screen if a whole new set of credentials is needed |
| $scope.$on('guacInvalidCredentials', function loginInvalid(event, parameters, error) { |
| |
| setApplicationState(ApplicationState.AWAITING_CREDENTIALS); |
| |
| $scope.loginHelpText = null; |
| $scope.acceptedCredentials = {}; |
| $scope.expectedCredentials = error.expected; |
| |
| }); |
| |
| // Prompt for remaining credentials if provided credentials were not enough |
| $scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, error) { |
| |
| setApplicationState(ApplicationState.AWAITING_CREDENTIALS); |
| |
| $scope.loginHelpText = error.translatableMessage; |
| $scope.acceptedCredentials = parameters; |
| $scope.expectedCredentials = error.expected; |
| |
| }); |
| |
| // Alert user to authentication errors that occur in the absence of an |
| // interactive login form |
| $scope.$on('guacLoginFailed', function loginFailed(event, parameters, error) { |
| |
| // All errors related to an interactive login form are handled elsewhere |
| if ($scope.applicationState === ApplicationState.AWAITING_CREDENTIALS |
| || error.type === Error.Type.INSUFFICIENT_CREDENTIALS |
| || error.type === Error.Type.INVALID_CREDENTIALS) |
| return; |
| |
| setApplicationState(ApplicationState.AUTOMATIC_LOGIN_REJECTED); |
| $scope.reAuthenticating = false; |
| $scope.fatalError = error; |
| |
| }); |
| |
| // Replace absolutely all content with an error message if the page itself |
| // cannot be displayed due to an error |
| $scope.$on('guacFatalPageError', function fatalPageError(error) { |
| setApplicationState(ApplicationState.FATAL_ERROR); |
| $scope.fatalError = error; |
| }); |
| |
| // Replace the overall user interface with an informational message if the |
| // user has manually logged out |
| $scope.$on('guacLogout', function loggedOut() { |
| $scope.applicationState = ApplicationState.LOGGED_OUT; |
| $scope.reAuthenticating = false; |
| }); |
| |
| // Ensure new pages always start with clear keyboard state |
| $scope.$on('$routeChangeStart', function routeChanging() { |
| keyboard.reset(); |
| }); |
| |
| // Update title and CSS class upon navigation |
| $scope.$on('$routeChangeSuccess', function(event, current, previous) { |
| |
| // If the current route is available |
| if (current.$$route) { |
| |
| // Clear login screen if route change was successful (and thus |
| // login was either successful or not required) |
| $scope.applicationState = ApplicationState.READY; |
| |
| // Set title |
| var title = current.$$route.title; |
| if (title) |
| $scope.page.title = title; |
| |
| // Set body CSS class |
| $scope.page.bodyClassName = current.$$route.bodyClassName || ''; |
| } |
| |
| }); |
| |
| }]); |