diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 5048c69572..f1441c9216 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -75,7 +75,7 @@ import './shared/Modal'; import './shared/prompt-dialog'; import './shared/directives'; import './shared/filters'; -import './shared/Socket'; +import socket from './shared/socket/main'; import './shared/features/main'; import config from './shared/config/main'; import './login/authenticationServices/pendo/ng-pendo'; @@ -183,7 +183,6 @@ var tower = angular.module('Tower', [ 'HostGroupsFormDefinition', 'StreamWidget', 'JobsHelper', - 'InventoryGroupsHelpDefinition', 'CredentialsHelper', 'StreamListDefinition', 'ActivityDetailDefinition', @@ -197,10 +196,9 @@ var tower = angular.module('Tower', [ 'StandardOutHelper', 'LogViewerOptionsDefinition', 'JobDetailHelper', - 'SocketIO', + 'socket', 'lrInfiniteScroll', 'LoadConfigHelper', - 'SocketHelper', 'PortalJobsListDefinition', 'features', 'longDateFilter', @@ -528,15 +526,15 @@ var tower = angular.module('Tower', [ .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', - 'ClearScope', 'Socket', 'LoadConfig', 'Store', - 'ShowSocketHelp', 'pendoService', 'Prompt', 'Rest', 'Wait', + 'ClearScope', 'LoadConfig', 'Store', + 'pendoService', 'Prompt', 'Rest', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', - 'FeaturesService', '$filter', + 'FeaturesService', '$filter', 'SocketService', function($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, - $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket, - LoadConfig, Store, ShowSocketHelp, pendoService, Prompt, Rest, Wait, + $location, Authorization, LoadBasePaths, Timer, ClearScope, + LoadConfig, Store, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, - $filter) { + $filter, SocketService) { var sock; $rootScope.addPermission = function(scope) { $compile("")(scope); @@ -792,35 +790,35 @@ var tower = angular.module('Tower', [ // } $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { if(toState.name === 'dashboard'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'jobDetail'){ - $rootScope.socket.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); + SocketService.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); console.log(toState.name); } else if(toState.name === 'jobStdout'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'jobs'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); console.log(toState.name); } else if(toState.name === 'portalMode'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'projects'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'inventory'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'adHocJobStdout'){ - $rootScope.socket.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); + SocketService.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); console.log(toState.name); } }); @@ -882,7 +880,8 @@ var tower = angular.module('Tower', [ ConfigService.getConfig().then(function() { Timer.init().then(function(timer) { $rootScope.sessionTimer = timer; - $rootScope.$emit('OpenSocket'); + // $rootScope.$emit('OpenSocket'); + SocketService.init(); pendoService.issuePendoIdentity(); CheckLicense.test(); FeaturesService.get(); @@ -910,10 +909,6 @@ var tower = angular.module('Tower', [ $('#' + tabs + ' #' + tab).tab('show'); }; - $rootScope.socketHelp = function() { - ShowSocketHelp(); - }; - $rootScope.leavePortal = function() { $rootScope.portalMode = false; $location.path('/home/'); diff --git a/awx/ui/client/src/help.js b/awx/ui/client/src/help.js index 0c526d541f..563ce5d460 100644 --- a/awx/ui/client/src/help.js +++ b/awx/ui/client/src/help.js @@ -4,7 +4,4 @@ * All Rights Reserved *************************************************/ -import "./help/ChromeSocketHelp"; -import "./help/FirefoxSocketHelp"; import "./help/InventoryGroups"; -import "./help/SafariSocketHelp"; diff --git a/awx/ui/client/src/help/ChromeSocketHelp.js b/awx/ui/client/src/help/ChromeSocketHelp.js deleted file mode 100644 index d2ea6ef44a..0000000000 --- a/awx/ui/client/src/help/ChromeSocketHelp.js +++ /dev/null @@ -1,47 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc overview - * @name help - * @description These are the modal windows that are shown to the user to give additional guidance on certain tasks that might not be straightforward. -*/ - - /** - * @ngdoc function - * @name help.function:ChromeSocketHelp - * @description This help modal gives instructions on what the user should do if not connected to the web sockets while using Chrome. -*/ - - -angular.module('ChromeSocketHelpDefinition', []) - .value('ChromeSocketHelp', { - story: { - hdr: 'Live Events', - width: 510, - height: 560, - steps: [{ - intro: 'Connection status indicator:', - img: { - src: 'socket_indicator.png', - maxWidth: 360 - }, - box: "

indicates live events are streaming and the browser is connected to the live events server.

If the indicator continually shows " + - "or , then live events are not streaming, and the browser is having difficulty connecting to the live events server. In this case click Next for troubleshooting help.

" - }, { - intro: 'Live events connection:', - icon: { - "class": "fa fa-5x fa-rss {{ socketStatus }}-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

{{ browserName }} is connecting to the live events server on port {{ socketPort }}. The current connection status is " + - " {{ socketStatus }}.

If the connection status indicator is not green, have the " + - "system administrator verify this is the correct port and that access to the port is not blocked by a firewall." - }] - } - }); diff --git a/awx/ui/client/src/help/FirefoxSocketHelp.js b/awx/ui/client/src/help/FirefoxSocketHelp.js deleted file mode 100644 index 989bb2fb87..0000000000 --- a/awx/ui/client/src/help/FirefoxSocketHelp.js +++ /dev/null @@ -1,78 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name help.function:FirefoxSocketHelp - * @description This help modal gives instructions on what the user should do if not connected to the web sockets while using Firefox. -*/ - - -angular.module('FFSocketHelpDefinition', []) - .value('FFSocketHelp', { - story: { - hdr: 'Live Events', - width: 510, - height: 560, - steps: [{ - intro: 'Connection status indicator:', - img: { - src: 'socket_indicator.png', - maxWidth: 360 - }, - box: "

indicates live events are streaming and the browser is connected to the live events server.

If the indicator continually shows " + - "or , then live events are not streaming, and the browser is having difficulty connecting to the live events server. In this case click Next for troubleshooting help.

" - }, { - intro: 'Live events connection:', - icon: { - "class": "fa fa-5x fa-rss {{ socketStatus }}-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

{{ browserName }} is connecting to the live events server on port {{ socketPort }}. The current connection status is " + - " {{ socketStatus }}.

If the connection status indicator is not green, have the " + - "system administrator verify this is the correct port and that access to the port is not blocked by a firewall.

" - }, { - intro: 'Self signed certificate:', - icon: { - "class": "fa fa-5x fa-check ok-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

If the Tower web server is using a self signed security certificate, Firefox needs to accept the certificate and allow the " + - "connection.

Click Next for help accepting a self signed certificate.

" - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'understand_the_risk.png', - maxWidth: 440 - }, - box: "

Navigate to {{ socketURL }} The above warning will appear.

Click I Understand the Risks

" - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'add_exception.png', - maxWidth: 440 - }, - box: "

Click the Add Exception button." - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'confirm_exception.png', - maxWidth: 340 - }, - box: "

Click the Confirm the Security Exception button. This will add the self signed certificate from the Tower server to Firefox's list of trusted certificates.

" - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'refresh_firefox.png', - maxWidth: 480 - }, - box: "

Now that Firefox has accepted the security certificate the live event connection status indicator should turn green. If it does not, reload Tower by clicking the " + - "Firefox refresh button." - }] - } - }); diff --git a/awx/ui/client/src/help/InventoryGroups.js b/awx/ui/client/src/help/InventoryGroups.js deleted file mode 100644 index c9ee3b7432..0000000000 --- a/awx/ui/client/src/help/InventoryGroups.js +++ /dev/null @@ -1,86 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name help.function:InventoryGroups - * @description This help modal walks the user how to add groups to an inventory or a subgroup to an existing group. -*/ - - -angular.module('InventoryGroupsHelpDefinition', []) - .value('InventoryGroupsHelp', { - story: { - hdr: 'Inventory Setup', - width: 510, - height: 560, - steps: [{ - intro: 'Start by creating a group:', - img: { - src: 'groups001.png', - maxWidth: 257, - maxHeight: 114 - }, - box: "Click on the groups list (the left side of the page) to add a new group.", - autoOffNotice: true - }, { - intro: 'Enter group properties:', - img: { - src: 'groups002.png', - maxWidth: 443, - maxHeight: 251 - }, - box: 'Enter the group name, a description and any inventory variables. Variables can be entered using either JSON or YAML syntax. ' + - 'For more on inventory variables, see ' + - 'docs.ansible.com/intro_inventory.html' - }, { - intro: 'Cloud inventory: select cloud source', - img: { - src: 'groups003.png', - maxWidth: 412, - maxHeight: 215 - }, - box: "For a cloud inventory, choose the cloud provider from the list and select your credentials. If you have not already setup " + - "credentials for the provider, you will need to do that first on the Credentials tab." - }, { - intro: 'Cloud inventory: synchronize Tower with the cloud', - img: { - src: 'groups004.png', - maxWidth: 187, - maxHeight: 175 - }, - box: "To import a cloud inventory into Tower, initiate an inventory sync by clicking ." - }, { - intro: "Add subgroups:", - img: { - src: 'groups008.png', - maxWidth: 469, - maxHeight: 243 - }, - box: "

First, select an existing group.
" - }, { - intro: "Add subgroups:", - img: { - src: 'groups009.png', - maxWidth: 475, - maxHeight: 198 - }, - box: "
Then click to create a new group. The new group " + - "will be added to the selected group.
" - }, { - intro: 'Add hosts:', - img: { - src: 'groups010.png', - maxWidth: 475, - maxHeight: 122 - }, - box: "

First, select a Group. " + - "Then click on the hosts list (the right side of the page) to create a host. " + - "The new host will be part of the selected group.

" - }] - } - }); diff --git a/awx/ui/client/src/help/SafariSocketHelp.js b/awx/ui/client/src/help/SafariSocketHelp.js deleted file mode 100644 index 46b9cf48b0..0000000000 --- a/awx/ui/client/src/help/SafariSocketHelp.js +++ /dev/null @@ -1,50 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name help.function:SafariSocketHelp - * @description This help modal gives instructions on what the user should do if not connected to the web sockets while using Safari. Safari does not support websockets. -*/ - - -angular.module('SafariSocketHelpDefinition', []) - .value('SafariSocketHelp', { - story: { - hdr: 'Live Events', - width: 510, - height: 560, - steps: [{ - intro: 'Connection status indicator:', - img: { - src: 'socket_indicator.png', - maxWidth: 360 - }, - box: "

indicates live events are streaming and the browser is connected to the live events server.

If the indicator continually shows " + - "or , then live events are not streaming, and the browser is having difficulty connecting to the live events server. In this case click Next for troubleshooting help.

" - }, { - intro: 'Live events connection:', - icon: { - "class": "fa fa-5x fa-rss {{ socketStatus }}-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

{{ browserName }} is connecting to the live events server on port {{ socketPort }}. The current connection status is " + - " {{ socketStatus }}.

If the connection status indicator is not green, have the " + - "system administrator verify this is the correct port and that access to the port is not blocked by a firewall.

" - }, { - intro: 'Self signed certificate:', - icon: { - "class": "fa fa-5x fa-check ok-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

Safari will not connect to the live event port when the Tower web server is configured with a self signed certificate. Check with a system administrator to " + - "determine if Tower is using a self signed certificate. Installing a signed certificate will fix the problem.

" + - "

Switching browsers to either Chrome or Firefox will work as well.

" - }] - } - }); diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index aae8a17225..cf5f65e601 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -23,7 +23,6 @@ import ProjectPath from "./helpers/ProjectPath"; import Projects from "./helpers/Projects"; import Schedules from "./helpers/Schedules"; import Selection from "./helpers/Selection"; -import SocketHelper from "./helpers/SocketHelper"; import Users from "./helpers/Users"; import Variables from "./helpers/Variables"; import ApiDefaults from "./helpers/api-defaults"; @@ -55,7 +54,6 @@ export Projects, Schedules, Selection, - SocketHelper, Users, Variables, ApiDefaults, diff --git a/awx/ui/client/src/helpers/SocketHelper.js b/awx/ui/client/src/helpers/SocketHelper.js deleted file mode 100644 index ea0110cf01..0000000000 --- a/awx/ui/client/src/helpers/SocketHelper.js +++ /dev/null @@ -1,39 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:SocketHelper - * @description - * SocketHelper.js - * - * Show web socket troubleshooting help - * - */ - - -export default - angular.module('SocketHelper', ['Utilities', 'FFSocketHelpDefinition', 'SafariSocketHelpDefinition' , 'ChromeSocketHelpDefinition']) - - .factory('ShowSocketHelp', ['$location', '$rootScope', 'FFSocketHelp', 'SafariSocketHelp', 'ChromeSocketHelp', 'HelpDialog', 'browserData', - function($location, $rootScope, FFSocketHelp, SafariSocketHelp, ChromeSocketHelp, HelpDialog, browserData) { - return function() { - var scope = $rootScope.$new(); - scope.socketPort = $AnsibleConfig.websocket_port; - scope.socketURL = 'https://' + $location.host() + ':' + scope.socketPort + '/'; - scope.browserName = browserData.name; - - if (browserData.name === 'Firefox') { - HelpDialog({ defn: FFSocketHelp, scope: scope }); - } - else if (browserData.name === 'Safari') { - HelpDialog({ defn: SafariSocketHelp, scope: scope }); - } - else { - HelpDialog({ defn: ChromeSocketHelp, scope: scope }); - } - }; - }]); diff --git a/awx/ui/client/src/shared/socket/main.js b/awx/ui/client/src/shared/socket/main.js new file mode 100644 index 0000000000..3b38053762 --- /dev/null +++ b/awx/ui/client/src/shared/socket/main.js @@ -0,0 +1,13 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +// import awFeatureDirective from './features.directive'; +import socketService from './socket.service'; + +export default + angular.module('socket', []) + // .directive('awFeature', awFeatureDirective) + .service('SocketService', socketService); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js new file mode 100644 index 0000000000..ca7ddb2aee --- /dev/null +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -0,0 +1,173 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ +import ReconnectingWebSocket from 'reconnectingwebsocket' +export default +['$rootScope', '$location', '$log', 'Authorization','$state', + function ($rootScope, $location, $log, Authorization, $state) { + return { + init: function() { + var self = this, + token = Authorization.getToken(), + host = window.location.host, + url = "ws://" + host + "/websocket/"; + if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) { + // We have a valid session token, so attempt socket connection + $log.debug('Socket connecting to: ' + url); + self.socket = new ReconnectingWebSocket(url, null, { + debug: true, + timeoutInterval: 3000, + maxReconnectAttempts: 10 + }); + self.socket.onopen = function () { + console.log('websocket connected'); //log errors + }; + + self.socket.onerror = function (error) { + console.log('Error Logged: ' + error); //log errors + }; + self.socket.onmessage = function (e) { + console.log('Received From Server: ' + e.data); + var data = JSON.parse(e.data); + // {'groups': + // {'jobs': ['status_changed', 'summary'], + // 'schedules': ['changed'], + // 'ad_hoc_command_events': [ids,], + // 'job_events': [ids,], + // 'control': ['limit_reached'], + // } + // } + if(data.group_name==="jobs"){ + + if (!('status' in data)){ + // we know that this must have been a + // summary complete message + $log.debug('Job summary_complete ' + data.unified_job_id); + $rootScope.$emit('JobSummaryComplete', data); + } + if ($state.is('jobs')) { + $rootScope.$emit('JobStatusChange-jobs', data); + } else if ($state.includes('jobDetail') || + $state.is('adHocJobStdout') || + $state.is('inventorySyncStdout') || + $state.is('managementJobStdout') || + $state.is('scmUpdateStdout')) { + + $log.debug("sending status to standard out"); + $rootScope.$emit('JobStatusChange-jobStdout', data); + } + if ($state.includes('jobDetail')) { + $rootScope.$emit('JobStatusChange-jobDetails', data); + } else if ($state.is('dashboard')) { + $rootScope.$emit('JobStatusChange-home', data); + } else if ($state.is('portalMode')) { + $rootScope.$emit('JobStatusChange-portal', data); + } else if ($state.is('projects')) { + $rootScope.$emit('JobStatusChange-projects', data); + } else if ($state.is('inventoryManage')) { + $rootScope.$emit('JobStatusChange-inventory', data); + } + } + if(data.group_name==="job_events"){ + $rootScope.$emit('job_events-'+data.job, data); + } + if(data.group_name==="schedules"){ + $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); + $rootScope.$emit('ScheduleStatusChange', data); + } + if(data.group_name==="ad_hoc_command_events"){ + + } + if(data.group_name==="control"){ + $log.debug(data.reason); + $rootScope.sessionTimer.expireSession('session_limit'); + $state.go('signOut'); + } + + return self.socket; + + }; + + } + else { + // encountered expired token, redirect to login page + $rootScope.sessionTimer.expireSession('idle'); + $location.url('/login'); + } + }, + checkStatus: function() { + + function getSocketTip(status) { + var result = ''; + switch(status) { + case 'error': + result = "Live events: error connecting to the Tower server."; + break; + case 'connecting': + result = "Live events: attempting to connect to the Tower server."; + break; + case "ok": + result = "Live events: connected. Pages containing job status information will automatically update in real-time."; + } + return result; + } + // Check connection status + var self = this; + if(self){ + if(self.socket){ + if (self.socket.readyState === 0 ) { + self.scope.socketStatus = 'connecting'; + } + else if (self.socket.readyState === 1){ + self.scope.socketStatus = 'ok'; + } + else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ + self.scope.socketStatus = 'error'; + } + self.scope.socketTip = getSocketTip(self.scope.socketStatus); + return self.scope.socketStatus; + } + } + + }, + on: function (eventName, callback) { + var self = this; + if(self){ + if(self.socket){ + // self.socket.onmessage(function (e) { + // var args = arguments; + // self.scope.$apply(function () { + // callback.apply(self.socket, args); + // }); + // }); + } + } + + }, + emit: function (eventName, data, callback) { + var self = this; + // console.log(eventName) + self.socket.send(eventName, data, function () { + var args = arguments; + self.scope.$apply(function () { + if (callback) { + callback.apply(self.socket, args); + } + }); + }); + }, + getUrl: function() { + return url; + }, + removeAllListeners: function (eventName) { + var self = this; + if(self){ + if(self.socket){ + self.socket.removeEventListener(eventName); + } + } + }, + }; + }]; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 51d1a8ea7b..5f05f9e4fc 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.socket.on("job_events-" + job_id, function() { + $rootScope.$on("job_events-" + job_id, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++;