AC-1197 callback workflow -dynamic help text. Fixed js lint issues.

This commit is contained in:
Chris Houseknecht
2014-04-16 13:47:20 -04:00
parent 9901658f02
commit 98c56827db
11 changed files with 140 additions and 55 deletions

View File

@@ -48,6 +48,7 @@ angular.module('Tower', [
'LookUpHelper', 'LookUpHelper',
'JobTemplatesListDefinition', 'JobTemplatesListDefinition',
'JobTemplateFormDefinition', 'JobTemplateFormDefinition',
'JobTemplatesHelper',
'JobSubmissionHelper', 'JobSubmissionHelper',
'ProjectsListDefinition', 'ProjectsListDefinition',
'ProjectFormDefinition', 'ProjectFormDefinition',

View File

@@ -103,7 +103,7 @@ function Authenticate($cookieStore, $compile, $window, $scope, $rootScope, $loca
Wait('stop'); Wait('stop');
Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger', setLoginFocus); Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger', setLoginFocus);
}); });
}); });
if (scope.removeAuthorizationGetUser) { if (scope.removeAuthorizationGetUser) {
scope.removeAuthorizationGetUser(); scope.removeAuthorizationGetUser();
@@ -131,7 +131,7 @@ function Authenticate($cookieStore, $compile, $window, $scope, $rootScope, $loca
} else { } else {
Wait('start'); Wait('start');
Authorization.retrieveToken(username, password) Authorization.retrieveToken(username, password)
.success(function (data, status) { .success(function (data) {
$('#login-modal').modal('hide'); $('#login-modal').modal('hide');
token = data.token; token = data.token;
Authorization.setToken(data.token, data.expires); Authorization.setToken(data.token, data.expires);

View File

@@ -108,7 +108,8 @@ JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$route
function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, 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(); ClearScope();
@@ -121,6 +122,8 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa
selectPlaybook, checkSCMStatus, selectPlaybook, checkSCMStatus,
callback; callback;
CallbackHelpInit({ scope: $scope });
generator.inject(form, { mode: 'add', related: false, scope: $scope }); generator.inject(form, { mode: 'add', related: false, scope: $scope });
callback = function() { callback = function() {
@@ -274,8 +277,9 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
Wait('stop'); Wait('stop');
if (data.related && data.related.callback) { if (data.related && data.related.callback) {
Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is: <strong>' + data.related.callback + Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is:</p>'+
'</strong></p><p>The host configuration key is: <strong>' + data.host_config_key + '</strong></p>', 'alert-info', saveCompleted); '<p style="padding: 10px 0;"><strong>' + $scope.callback_server_path + data.related.callback + '</strong></p>'+
'<p>The host configuration key is: <strong>' + data.host_config_key + '</strong></p>', 'alert-info', saveCompleted);
} }
else { else {
saveCompleted(); saveCompleted();
@@ -330,7 +334,7 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa
JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope',
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', '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, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList,
CredentialList, ProjectList, LookUpInit, GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, CredentialList, ProjectList, LookUpInit, GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate,
Wait, Stream, Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit, JobsControllerInit, JobsListUpdate, Wait, Stream, Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit, JobsControllerInit, JobsListUpdate,
GetChoices, SchedulesListInit, SchedulesList) { GetChoices, SchedulesListInit, SchedulesList, CallbackHelpInit) {
ClearScope(); ClearScope();
@@ -353,6 +357,8 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP
checkSCMStatus, getPlaybooks, callback, checkSCMStatus, getPlaybooks, callback,
choicesCount = 0; choicesCount = 0;
CallbackHelpInit({ scope: $scope });
generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.inject(form, { mode: 'edit', related: true, scope: $scope });
$scope.parseType = 'yaml'; $scope.parseType = 'yaml';
@@ -610,7 +616,14 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP
relatedSets = form.relatedSets(data.related); 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; master.callback_url = $scope.callback_url;
LookUpInit({ LookUpInit({
@@ -695,9 +708,10 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP
} }
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
Wait('stop'); Wait('stop');
if (Empty(master.callback_url) && data.related && data.related.callback) { if (data.related && data.related.callback) {
Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is: <strong>' + data.related.callback + Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is:</p>'+
'</strong></p><p>The host configuration key is: <strong>' + data.host_config_key + '</strong></p>', 'alert-info', saveCompleted); '<p style="padding: 10px 0;"><strong>' + $scope.callback_server_path + data.related.callback + '</strong></p>'+
'<p>The host configuration key is: <strong>' + data.host_config_key + '</strong></p>', 'alert-info', saveCompleted);
} }
else { else {
saveCompleted(); saveCompleted();
@@ -802,5 +816,5 @@ JobTemplatesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$l
'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit',
'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt',
'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices', 'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices',
'SchedulesListInit', 'SchedulesList' 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit'
]; ];

View File

@@ -1,26 +1,26 @@
/************************************ /************************************
* Copyright (c) 2014 AnsibleWorks, Inc. * Copyright (c) 2014 AnsibleWorks, Inc.
* *
* Sockets.js * Sockets.js
* SocketsController- simple test of socket connection * 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 }); var socket = Socket({ scope: $scope });
socket.init(); //make the connection 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); $scope.messages.push(data);
}); });
} }
SocketsController.$inject = [ '$scope', 'ClearScope', 'Socket']; SocketsController.$inject = [ '$scope', 'ClearScope', 'Socket'];

View File

@@ -226,17 +226,10 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet
falseValue: 'false', falseValue: 'false',
ngChange: "toggleCallback('host_config_key')", ngChange: "toggleCallback('host_config_key')",
column: 2, column: 2,
awPopOver: "<p>Create a callback URL a host can use to contact Tower and request a configuration update " + awPopOver: "<p>Enable creation of a callback URL. With a callback URL a host can contact Tower and request a configuration update " +
"using the job template. The URL will look like the following:</p>\n" + "using this job template.</p>",
"<pre>http://your.server.com:999/api/v1/job_templates/1/callback/</pre>" +
"<p>The request from the host must be a POST. Here is an example using curl:</p>\n" +
"<pre>curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
"http://your.server.com:999/api/v1/job_templates/1/callback/</pre>\n" +
"<p>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.</p>" +
"<p>Successful requests will result in an entry on the Jobs tab, where the results and history can be viewed.</p>",
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: 'Callback URL', dataTitle: 'Allow Callbacks',
dataContainer: "body" dataContainer: "body"
}, },
callback_url: { callback_url: {
@@ -247,14 +240,8 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet
readonly: true, readonly: true,
ngShow: "allow_callbacks", ngShow: "allow_callbacks",
column: 2, column: 2,
required: false, awPopOver: "callback_help",
awPopOver: "<p>Using this URL a host can contact Tower and request a configuration update using the job " + awPopOverWatch: "callback_help",
"template. The request from the host must be a POST. Here is an example using curl:</p>\n" +
"<pre>curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
"http://your.server.com:999/api/v1/job_templates/1/callback/</pre>\n" +
"<p>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.</p>" +
"<p>Successful requests will result in an entry on the Jobs tab, where the results and history can be viewed.</p>",
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: 'Callback URL', dataTitle: 'Callback URL',
dataContainer: "body" dataContainer: "body"
@@ -263,12 +250,11 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet
label: 'Host Config Key', label: 'Host Config Key',
type: 'text', type: 'text',
ngShow: "allow_callbacks", ngShow: "allow_callbacks",
ngChange: "configKeyChange()",
genMD5: true, genMD5: true,
column: 2, column: 2,
awPopOver: "<p>When contacting the Tower server using the callback URL, the calling host must authenticate by including " + awPopOver: "callback_help",
"this key in the POST data of the request. Here's an example using curl:</p>\n" + awPopOverWatch: "callback_help",
"<pre>curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
"http://your.server.com:999/api/v1/job_templates/1/callback/</pre>\n",
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: "Host Config Key", dataTitle: "Host Config Key",
dataContainer: "body" dataContainer: "body"

View File

@@ -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 = "<p>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:</p>\n" +
"<pre>curl --data \"host_config_key=\"" + scope.example_config_key + "\" " +
scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/</pre>\n" +
"<p>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.</p>" +
"<p>Successful requests create an entry on the Jobs page, where results and history can be viewed.</p>";
};
// 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();
};
}]);

View File

@@ -25,6 +25,7 @@ angular.module('md5Helper', ['RestServices', 'Utilities', 'angular-md5'])
scope.genMD5 = function (fld) { scope.genMD5 = function (fld) {
var now = new Date(); var now = new Date();
scope[fld] = md5.createHash('AnsibleWorks' + now.getTime()); scope[fld] = md5.createHash('AnsibleWorks' + now.getTime());
scope.$emit('NewMD5Generated');
}; };
scope.toggleCallback = function (fld) { scope.toggleCallback = function (fld) {

View File

@@ -6,6 +6,8 @@
* Wrapper for lib/socket.io-client/dist/socket.io.js. * Wrapper for lib/socket.io-client/dist/socket.io.js.
*/ */
/* global io */
'use strict'; 'use strict';
angular.module('SocketIO', ['AuthService', 'Utilities']) 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) { .factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', 'Alert', function ($rootScope, $location, $log, Authorization, Alert) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
debug = params.debug,
host = $location.host(), host = $location.host(),
protocol = $location.protocol(), protocol = $location.protocol(),
url = protocol + '://' + host + ':8080'; url = protocol + '://' + host + ':8080';
@@ -102,7 +103,7 @@ angular.module('SocketIO', ['AuthService', 'Utilities'])
} }
}, },
on: function (eventName, callback) { on: function (eventName, callback) {
self = this; var self = this;
self.socket.on(eventName, function () { self.socket.on(eventName, function () {
var args = arguments; var args = arguments;
self.scope.$apply(function () { self.scope.$apply(function () {
@@ -119,7 +120,7 @@ angular.module('SocketIO', ['AuthService', 'Utilities'])
callback.apply(self.socket, args); callback.apply(self.socket, args);
} }
}); });
}) });
} }
}; };
}; };

View File

@@ -288,8 +288,30 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
title = (attrs.title !== undefined && attrs.title !== null) ? attrs.title : 'Help', title = (attrs.title !== undefined && attrs.title !== null) ? attrs.title : 'Help',
container = (attrs.container !== undefined) ? attrs.container : false, container = (attrs.container !== undefined) ? attrs.container : false,
trigger = (attrs.trigger !== undefined) ? attrs.trigger : 'manual'; trigger = (attrs.trigger !== undefined) ? attrs.trigger : 'manual';
$(element).popover({ placement: placement, delay: 0, title: title, if (attrs.awPopOverWatch) {
content: attrs.awPopOver, trigger: trigger, html: true, container: container }); $(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() { $(element).click(function() {
var self = $(this); var self = $(this);
try { try {

View File

@@ -43,6 +43,7 @@ angular.module('GeneratorHelpers', [])
result += (obj.dataContainer) ? "data-container=\"" + obj.dataContainer + "\" " : ""; result += (obj.dataContainer) ? "data-container=\"" + obj.dataContainer + "\" " : "";
result += (obj.dataTitle) ? "data-title=\"" + obj.dataTitle + "\" " : ""; result += (obj.dataTitle) ? "data-title=\"" + obj.dataTitle + "\" " : "";
result += (obj.dataTrigger) ? "data-trigger=\"" + obj.dataTrigger + "\" " : ""; result += (obj.dataTrigger) ? "data-trigger=\"" + obj.dataTrigger + "\" " : "";
result += (obj.awPopOverWatch) ? "aw-pop-over-watch=\"" + obj.awPopOverWatch + "\" " : "";
result += "class=\"help-link\" "; result += "class=\"help-link\" ";
result += "><i class=\"fa fa-question-circle\"></i></a> "; result += "><i class=\"fa fa-question-circle\"></i></a> ";
break; break;

View File

@@ -154,6 +154,7 @@
<script src="{{ STATIC_URL }}js/helpers/Schedules.js"></script> <script src="{{ STATIC_URL }}js/helpers/Schedules.js"></script>
<script src="{{ STATIC_URL }}js/helpers/LogViewer.js"></script> <script src="{{ STATIC_URL }}js/helpers/LogViewer.js"></script>
<script src="{{ STATIC_URL }}js/helpers/JobDetail.js"></script> <script src="{{ STATIC_URL }}js/helpers/JobDetail.js"></script>
<script src="{{ STATIC_URL }}js/helpers/JobTemplates.js"></script>
<script src="{{ STATIC_URL }}js/widgets/JobStatus.js"></script> <script src="{{ STATIC_URL }}js/widgets/JobStatus.js"></script>
<script src="{{ STATIC_URL }}js/widgets/InventorySyncStatus.js"></script> <script src="{{ STATIC_URL }}js/widgets/InventorySyncStatus.js"></script>
<script src="{{ STATIC_URL }}js/widgets/SCMSyncStatus.js"></script> <script src="{{ STATIC_URL }}js/widgets/SCMSyncStatus.js"></script>