From 98c56827dbac2d042829bdc9953a318dfb6351cc Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Wed, 16 Apr 2014 13:47:20 -0400 Subject: [PATCH] AC-1197 callback workflow -dynamic help text. Fixed js lint issues. --- awx/ui/static/js/app.js | 1 + .../static/js/controllers/Authentication.js | 4 +- awx/ui/static/js/controllers/JobTemplates.js | 34 +++++++---- awx/ui/static/js/controllers/Sockets.js | 32 +++++----- awx/ui/static/js/forms/JobTemplates.js | 30 +++------- awx/ui/static/js/helpers/JobTemplates.js | 58 +++++++++++++++++++ awx/ui/static/js/helpers/md5.js | 1 + awx/ui/static/lib/ansible/Socket.js | 7 ++- awx/ui/static/lib/ansible/directives.js | 26 ++++++++- .../static/lib/ansible/generator-helpers.js | 1 + awx/ui/templates/ui/index.html | 1 + 11 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 awx/ui/static/js/helpers/JobTemplates.js diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 61e8866022..75f1f1ac42 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -48,6 +48,7 @@ angular.module('Tower', [ 'LookUpHelper', 'JobTemplatesListDefinition', 'JobTemplateFormDefinition', + 'JobTemplatesHelper', 'JobSubmissionHelper', 'ProjectsListDefinition', 'ProjectFormDefinition', diff --git a/awx/ui/static/js/controllers/Authentication.js b/awx/ui/static/js/controllers/Authentication.js index e3c56e667a..856494b9f7 100644 --- a/awx/ui/static/js/controllers/Authentication.js +++ b/awx/ui/static/js/controllers/Authentication.js @@ -103,7 +103,7 @@ function Authenticate($cookieStore, $compile, $window, $scope, $rootScope, $loca Wait('stop'); Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger', setLoginFocus); }); - }); + }); if (scope.removeAuthorizationGetUser) { scope.removeAuthorizationGetUser(); @@ -131,7 +131,7 @@ function Authenticate($cookieStore, $compile, $window, $scope, $rootScope, $loca } else { Wait('start'); Authorization.retrieveToken(username, password) - .success(function (data, status) { + .success(function (data) { $('#login-modal').modal('hide'); token = data.token; Authorization.setToken(data.token, data.expires); diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 20fc7069bd..4ad7b29934 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -108,7 +108,8 @@ JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$route function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, - InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait, Empty, ToJSON) { + InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait, Empty, ToJSON, + CallbackHelpInit) { ClearScope(); @@ -121,6 +122,8 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa selectPlaybook, checkSCMStatus, callback; + CallbackHelpInit({ scope: $scope }); + generator.inject(form, { mode: 'add', related: false, scope: $scope }); callback = function() { @@ -274,8 +277,9 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { Wait('stop'); if (data.related && data.related.callback) { - Alert('Callback URL', '

Host callbacks are enabled for this template. The callback URL is: ' + data.related.callback + - '

The host configuration key is: ' + data.host_config_key + '

', 'alert-info', saveCompleted); + Alert('Callback URL', '

Host callbacks are enabled for this template. The callback URL is:

'+ + '

' + $scope.callback_server_path + data.related.callback + '

'+ + '

The host configuration key is: ' + data.host_config_key + '

', 'alert-info', saveCompleted); } else { saveCompleted(); @@ -330,7 +334,7 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', - 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON' + 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON', 'CallbackHelpInit' ]; @@ -338,7 +342,7 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList, ProjectList, LookUpInit, GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, Wait, Stream, Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit, JobsControllerInit, JobsListUpdate, - GetChoices, SchedulesListInit, SchedulesList) { + GetChoices, SchedulesListInit, SchedulesList, CallbackHelpInit) { ClearScope(); @@ -353,6 +357,8 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP checkSCMStatus, getPlaybooks, callback, choicesCount = 0; + CallbackHelpInit({ scope: $scope }); + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); $scope.parseType = 'yaml'; @@ -610,7 +616,14 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP relatedSets = form.relatedSets(data.related); - $scope.callback_url = data.related.callback; + if (data.host_config_key) { + $scope.example_config_key = data.host_config_key; + } + $scope.example_template_id = id; + $scope.setCallbackHelp(); + + $scope.callback_url = $scope.callback_server_path + ((data.related.callback) ? data.related.callback : + GetBasePath('job_templates') + id + '/callback/'); master.callback_url = $scope.callback_url; LookUpInit({ @@ -695,9 +708,10 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP } $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { Wait('stop'); - if (Empty(master.callback_url) && data.related && data.related.callback) { - Alert('Callback URL', '

Host callbacks are enabled for this template. The callback URL is: ' + data.related.callback + - '

The host configuration key is: ' + data.host_config_key + '

', 'alert-info', saveCompleted); + if (data.related && data.related.callback) { + Alert('Callback URL', '

Host callbacks are enabled for this template. The callback URL is:

'+ + '

' + $scope.callback_server_path + data.related.callback + '

'+ + '

The host configuration key is: ' + data.host_config_key + '

', 'alert-info', saveCompleted); } else { saveCompleted(); @@ -802,5 +816,5 @@ JobTemplatesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$l 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt', 'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices', - 'SchedulesListInit', 'SchedulesList' + 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit' ]; \ No newline at end of file diff --git a/awx/ui/static/js/controllers/Sockets.js b/awx/ui/static/js/controllers/Sockets.js index 74084a7769..ab63de1dca 100644 --- a/awx/ui/static/js/controllers/Sockets.js +++ b/awx/ui/static/js/controllers/Sockets.js @@ -1,26 +1,26 @@ /************************************ - * Copyright (c) 2014 AnsibleWorks, Inc. - * - * Sockets.js - * SocketsController- simple test of socket connection - * - */ +* Copyright (c) 2014 AnsibleWorks, Inc. +* +* Sockets.js +* SocketsController- simple test of socket connection +* +*/ - 'use strict'; +'use strict'; - function SocketsController ($scope, ClearScope, Socket) { +function SocketsController ($scope, ClearScope, Socket) { - ClearScope(); + ClearScope(); - var socket = Socket({ scope: $scope }); - socket.init(); //make the connection + var socket = Socket({ scope: $scope }); + socket.init(); //make the connection - $scope.messages = ['Stuff happened', 'message received', 'blah blah bob blah']; + $scope.messages = ['Stuff happened', 'message received', 'blah blah bob blah']; - socket.on('anything', function(data) { + socket.on('anything', function(data) { $scope.messages.push(data); - }); + }); - } +} - SocketsController.$inject = [ '$scope', 'ClearScope', 'Socket']; +SocketsController.$inject = [ '$scope', 'ClearScope', 'Socket']; diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index b4b13070d2..85fbcdf1d8 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -226,17 +226,10 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet falseValue: 'false', ngChange: "toggleCallback('host_config_key')", column: 2, - awPopOver: "

Create a callback URL a host can use to contact Tower and request a configuration update " + - "using the job template. The URL will look like the following:

\n" + - "
http://your.server.com:999/api/v1/job_templates/1/callback/
" + - "

The request from the host must be a POST. Here is an example using curl:

\n" + - "
curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
-                    "http://your.server.com:999/api/v1/job_templates/1/callback/
\n" + - "

Note the requesting host must be defined in your inventory. If ansible fails to locate the host either by name or IP address " + - "in one of your defined inventories, the request will be denied.

" + - "

Successful requests will result in an entry on the Jobs tab, where the results and history can be viewed.

", + awPopOver: "

Enable creation of a callback URL. With a callback URL a host can contact Tower and request a configuration update " + + "using this job template.

", dataPlacement: 'right', - dataTitle: 'Callback URL', + dataTitle: 'Allow Callbacks', dataContainer: "body" }, callback_url: { @@ -247,14 +240,8 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet readonly: true, ngShow: "allow_callbacks", column: 2, - required: false, - awPopOver: "

Using this URL a host can contact Tower and request a configuration update using the job " + - "template. The request from the host must be a POST. Here is an example using curl:

\n" + - "
curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
-                    "http://your.server.com:999/api/v1/job_templates/1/callback/
\n" + - "

Note the requesting host must be defined in your inventory. If ansible fails to locate the host either by name or IP address " + - "in one of your defined inventories, the request will be denied.

" + - "

Successful requests will result in an entry on the Jobs tab, where the results and history can be viewed.

", + awPopOver: "callback_help", + awPopOverWatch: "callback_help", dataPlacement: 'right', dataTitle: 'Callback URL', dataContainer: "body" @@ -263,12 +250,11 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet label: 'Host Config Key', type: 'text', ngShow: "allow_callbacks", + ngChange: "configKeyChange()", genMD5: true, column: 2, - awPopOver: "

When contacting the Tower server using the callback URL, the calling host must authenticate by including " + - "this key in the POST data of the request. Here's an example using curl:

\n" + - "
curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
-                    "http://your.server.com:999/api/v1/job_templates/1/callback/
\n", + awPopOver: "callback_help", + awPopOverWatch: "callback_help", dataPlacement: 'right', dataTitle: "Host Config Key", dataContainer: "body" diff --git a/awx/ui/static/js/helpers/JobTemplates.js b/awx/ui/static/js/helpers/JobTemplates.js new file mode 100644 index 0000000000..6565bc6b49 --- /dev/null +++ b/awx/ui/static/js/helpers/JobTemplates.js @@ -0,0 +1,58 @@ +/********************************************* + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * JobTemplatesHelper + * + * Routines shared by job related controllers + * + */ + +'use strict'; + +angular.module('JobTemplatesHelper', ['Utilities']) + +/* + * Add bits to $scope for handling callback url help + * + */ +.factory('CallbackHelpInit', ['$location', 'GetBasePath', function($location, GetBasePath) { + return function(params) { + + var scope = params.scope; + + // The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the + // popover is activated, a function checks the value of scope.callback_help before constructing the content. + scope.setCallbackHelp = function() { + scope.callback_help = "

With a callback URL and a host config key a host can contact Tower and request a configuration update using this job " + + "template. The request from the host must be a POST. Here is an example using curl:

\n" + + "
curl --data \"host_config_key=\"" + scope.example_config_key + "\" " +
+                scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/
\n" + + "

Note the requesting host must be defined in the inventory associated with the job template. If Tower fails to " + + "locate the host, the request will be denied.

" + + "

Successful requests create an entry on the Jobs page, where results and history can be viewed.

"; + }; + + // The md5 helper emits NewMD5Generated whenever a new key is available + if (scope.removeNewMD5Generated) { + scope.removeNewMD5Generated(); + } + scope.removeNewMD5Generated = scope.$on('NewMD5Generated', function() { + scope.configKeyChange(); + }); + + // Fired when user enters a key value + scope.configKeyChange = function() { + scope.example_config_key = scope.host_config_key; + scope.setCallbackHelp(); + }; + + // Set initial values and construct help text + scope.callback_server_path = $location.protocol() + '://' + $location.host() + (($location.port()) ? ':' + $location.port() : ''); + scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a'; + scope.example_template_id = 'N'; + scope.setCallbackHelp(); + }; + +}]); + + diff --git a/awx/ui/static/js/helpers/md5.js b/awx/ui/static/js/helpers/md5.js index b200449f32..ce08b7e121 100644 --- a/awx/ui/static/js/helpers/md5.js +++ b/awx/ui/static/js/helpers/md5.js @@ -25,6 +25,7 @@ angular.module('md5Helper', ['RestServices', 'Utilities', 'angular-md5']) scope.genMD5 = function (fld) { var now = new Date(); scope[fld] = md5.createHash('AnsibleWorks' + now.getTime()); + scope.$emit('NewMD5Generated'); }; scope.toggleCallback = function (fld) { diff --git a/awx/ui/static/lib/ansible/Socket.js b/awx/ui/static/lib/ansible/Socket.js index 67436c5bc2..d7f18bd688 100644 --- a/awx/ui/static/lib/ansible/Socket.js +++ b/awx/ui/static/lib/ansible/Socket.js @@ -6,6 +6,8 @@ * Wrapper for lib/socket.io-client/dist/socket.io.js. */ +/* global io */ + 'use strict'; angular.module('SocketIO', ['AuthService', 'Utilities']) @@ -13,7 +15,6 @@ angular.module('SocketIO', ['AuthService', 'Utilities']) .factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', 'Alert', function ($rootScope, $location, $log, Authorization, Alert) { return function(params) { var scope = params.scope, - debug = params.debug, host = $location.host(), protocol = $location.protocol(), url = protocol + '://' + host + ':8080'; @@ -102,7 +103,7 @@ angular.module('SocketIO', ['AuthService', 'Utilities']) } }, on: function (eventName, callback) { - self = this; + var self = this; self.socket.on(eventName, function () { var args = arguments; self.scope.$apply(function () { @@ -119,7 +120,7 @@ angular.module('SocketIO', ['AuthService', 'Utilities']) callback.apply(self.socket, args); } }); - }) + }); } }; }; diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index f9df896cd3..da121dc44e 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -288,8 +288,30 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job title = (attrs.title !== undefined && attrs.title !== null) ? attrs.title : 'Help', container = (attrs.container !== undefined) ? attrs.container : false, trigger = (attrs.trigger !== undefined) ? attrs.trigger : 'manual'; - $(element).popover({ placement: placement, delay: 0, title: title, - content: attrs.awPopOver, trigger: trigger, html: true, container: container }); + if (attrs.awPopOverWatch) { + $(element).popover({ + placement: placement, + delay: 0, + title: title, + content: function() { + return scope[attrs.awPopOverWatch]; + }, + trigger: trigger, + html: true, + container: container + }); + } + else { + $(element).popover({ + placement: placement, + delay: 0, + title: title, + content: attrs.awPopOver, + trigger: trigger, + html: true, + container: container + }); + } $(element).click(function() { var self = $(this); try { diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js index 5a77b61435..e9f4fe7245 100644 --- a/awx/ui/static/lib/ansible/generator-helpers.js +++ b/awx/ui/static/lib/ansible/generator-helpers.js @@ -43,6 +43,7 @@ angular.module('GeneratorHelpers', []) result += (obj.dataContainer) ? "data-container=\"" + obj.dataContainer + "\" " : ""; result += (obj.dataTitle) ? "data-title=\"" + obj.dataTitle + "\" " : ""; result += (obj.dataTrigger) ? "data-trigger=\"" + obj.dataTrigger + "\" " : ""; + result += (obj.awPopOverWatch) ? "aw-pop-over-watch=\"" + obj.awPopOverWatch + "\" " : ""; result += "class=\"help-link\" "; result += "> "; break; diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 4912a3d155..fbe3a1cbf1 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -154,6 +154,7 @@ +