Merge branch 'modularize-helpers-utilities' of https://github.com/mabashian/ansible-tower into mabashian-modularize-helpers-utilities

# Conflicts:
#	awx/ui/client/src/activity-stream/main.js
#	awx/ui/client/src/app.js
#	awx/ui/client/src/helpers.js
#	awx/ui/client/src/helpers/Credentials.js
#	awx/ui/client/src/helpers/Groups.js
#	awx/ui/client/src/helpers/Hosts.js
#	awx/ui/client/src/helpers/teams.js
This commit is contained in:
Michael Abashian
2017-03-02 10:20:32 -05:00
92 changed files with 2953 additions and 4962 deletions

View File

@@ -6,9 +6,9 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList', export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList',
'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit', 'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath',
function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList, function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList,
InventoryList, CredentialList, $compile, generateList, GetBasePath, SelectionInit) { InventoryList, CredentialList, $compile, generateList, GetBasePath) {
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {

View File

@@ -0,0 +1,51 @@
export default
function GetTargetTitle(i18n) {
return function (target) {
var rtnTitle = i18n._('ALL ACTIVITY');
switch(target) {
case 'project':
rtnTitle = i18n._('PROJECTS');
break;
case 'inventory':
rtnTitle = i18n._('INVENTORIES');
break;
case 'credential':
rtnTitle = i18n._('CREDENTIALS');
break;
case 'user':
rtnTitle = i18n._('USERS');
break;
case 'team':
rtnTitle = i18n._('TEAMS');
break;
case 'notification_template':
rtnTitle = i18n._('NOTIFICATION TEMPLATES');
break;
case 'organization':
rtnTitle = i18n._('ORGANIZATIONS');
break;
case 'job':
rtnTitle = i18n._('JOBS');
break;
case 'custom_inventory_script':
rtnTitle = i18n._('INVENTORY SCRIPTS');
break;
case 'schedule':
rtnTitle = i18n._('SCHEDULES');
break;
case 'host':
rtnTitle = i18n._('HOSTS');
break;
case 'template':
rtnTitle = i18n._('TEMPLATES');
break;
}
return rtnTitle;
};
}
GetTargetTitle.$inject = ['i18n'];

View File

@@ -12,6 +12,8 @@ import BuildAnchor from './factories/build-anchor.factory';
import BuildDescription from './factories/build-description.factory'; import BuildDescription from './factories/build-description.factory';
import ShowDetail from './factories/show-detail.factory'; import ShowDetail from './factories/show-detail.factory';
import Stream from './factories/stream.factory'; import Stream from './factories/stream.factory';
import GetTargetTitle from './get-target-title.factory';
import ModelToBasePathKey from './model-to-base-path-key.factory';
export default angular.module('activityStream', [streamDetailModal.name]) export default angular.module('activityStream', [streamDetailModal.name])
.controller('activityStreamController', activityStreamController) .controller('activityStreamController', activityStreamController)
@@ -20,6 +22,8 @@ export default angular.module('activityStream', [streamDetailModal.name])
.factory('BuildDescription', BuildDescription) .factory('BuildDescription', BuildDescription)
.factory('ShowDetail', ShowDetail) .factory('ShowDetail', ShowDetail)
.factory('Stream', Stream) .factory('Stream', Stream)
.factory('GetTargetTitle', GetTargetTitle)
.factory('ModelToBasePathKey', ModelToBasePathKey)
.run(['$stateExtender', function($stateExtender) { .run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(activityStreamRoute); $stateExtender.addState(activityStreamRoute);
}]); }]);

View File

@@ -0,0 +1,59 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ApiModel
* @description Helper functions to convert singular/plural versions of our models to the opposite
*/
export default
function ModelToBasePathKey() {
return function(model) {
// This function takes in the singular model string and returns the key needed
// to get the base path from $rootScope/local storage.
var basePathKey;
switch(model) {
case 'project':
basePathKey = 'projects';
break;
case 'inventory':
basePathKey = 'inventory';
break;
case 'job_template':
basePathKey = 'job_templates';
break;
case 'credential':
basePathKey = 'credentials';
break;
case 'user':
basePathKey = 'users';
break;
case 'team':
basePathKey = 'teams';
break;
case 'notification_template':
basePathKey = 'notification_templates';
break;
case 'organization':
basePathKey = 'organizations';
break;
case 'management_job':
basePathKey = 'management_jobs';
break;
case 'custom_inventory_script':
basePathKey = 'inventory_scripts';
break;
case 'workflow_job_template':
basePathKey = 'workflow_job_templates';
break;
}
return basePathKey;
};
}

View File

@@ -37,7 +37,7 @@ if ($basePath) {
} }
// Modules // Modules
import './helpers'; import './forms';
import './lists'; import './lists';
import './filters'; import './filters';
import portalMode from './portal-mode/main'; import portalMode from './portal-mode/main';
@@ -71,7 +71,6 @@ import projects from './projects/main';
import RestServices from './rest/main'; import RestServices from './rest/main';
import access from './access/main'; import access from './access/main';
import './shared/Modal';
import './shared/prompt-dialog'; import './shared/prompt-dialog';
import './shared/directives'; import './shared/directives';
import './shared/filters'; import './shared/filters';
@@ -91,6 +90,7 @@ var tower = angular.module('Tower', [
require('angular-sanitize'), require('angular-sanitize'),
require('angular-scheduler').name, require('angular-scheduler').name,
require('angular-tz-extensions'), require('angular-tz-extensions'),
require('angular-md5'),
require('lr-infinite-scroll'), require('lr-infinite-scroll'),
require('ng-toast'), require('ng-toast'),
'gettext', 'gettext',
@@ -138,66 +138,46 @@ var tower = angular.module('Tower', [
'OrganizationListDefinition', 'OrganizationListDefinition',
'templates', 'templates',
'UserListDefinition', 'UserListDefinition',
'UserHelper',
'PromptDialog', 'PromptDialog',
'AWDirectives', 'AWDirectives',
'InventoriesListDefinition', 'InventoriesListDefinition',
'InventoryFormDefinition', 'InventoryFormDefinition',
'InventoryHelper',
'InventoryGroupsDefinition', 'InventoryGroupsDefinition',
'InventoryHostsDefinition', 'InventoryHostsDefinition',
'HostsHelper',
'AWFilters', 'AWFilters',
'HostFormDefinition', 'HostFormDefinition',
'HostListDefinition', 'HostListDefinition',
'GroupFormDefinition', 'GroupFormDefinition',
'GroupListDefinition', 'GroupListDefinition',
'GroupsHelper',
'TeamsListDefinition', 'TeamsListDefinition',
'TeamFormDefinition', 'TeamFormDefinition',
'TeamHelper',
'CredentialsListDefinition', 'CredentialsListDefinition',
'CredentialFormDefinition', 'CredentialFormDefinition',
'TemplatesListDefinition', 'TemplatesListDefinition',
'PortalJobTemplatesListDefinition', 'PortalJobTemplatesListDefinition',
'JobTemplateFormDefinition', 'JobTemplateFormDefinition',
'JobTemplatesHelper',
'JobSubmissionHelper',
'ProjectsListDefinition', 'ProjectsListDefinition',
'ProjectFormDefinition', 'ProjectFormDefinition',
'ProjectStatusDefinition', 'ProjectStatusDefinition',
'ProjectsHelper',
'CompletedJobsDefinition', 'CompletedJobsDefinition',
'AllJobsDefinition', 'AllJobsDefinition',
'JobSummaryDefinition', 'JobSummaryDefinition',
'ParseHelper',
'ChildrenHelper',
'ProjectPathHelper',
'md5Helper',
'SelectionHelper',
'HostGroupsFormDefinition', 'HostGroupsFormDefinition',
'JobsHelper',
'CredentialsHelper',
'StreamListDefinition', 'StreamListDefinition',
'ActivityDetailDefinition', 'ActivityDetailDefinition',
'VariablesHelper',
'SchedulesListDefinition', 'SchedulesListDefinition',
'ScheduledJobsDefinition', 'ScheduledJobsDefinition',
//'Timezones', //'Timezones',
'SchedulesHelper',
'JobsListDefinition', 'JobsListDefinition',
'LogViewerStatusDefinition', 'LogViewerStatusDefinition',
'StandardOutHelper', 'StandardOutHelper',
'LogViewerOptionsDefinition', 'LogViewerOptionsDefinition',
'lrInfiniteScroll', 'lrInfiniteScroll',
'LoadConfigHelper',
'PortalJobsListDefinition', 'PortalJobsListDefinition',
'features', 'features',
'longDateFilter', 'longDateFilter',
'pendolytics', 'pendolytics',
scheduler.name, scheduler.name,
'ApiModelHelper',
'ActivityStreamHelper',
'WorkflowFormDefinition', 'WorkflowFormDefinition',
'InventorySourcesListDefinition', 'InventorySourcesListDefinition',
'WorkflowMakerFormDefinition' 'WorkflowMakerFormDefinition'

View File

@@ -7,11 +7,11 @@
export default ['$scope', '$rootScope', '$compile', '$location', export default ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert', '$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange',
'OwnerChange', 'FormSave', '$state', 'CreateSelect2', 'i18n', 'OwnerChange', 'CredentialFormSave', '$state', 'CreateSelect2', 'i18n',
function($scope, $rootScope, $compile, $location, $log, function($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, $stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange, ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange,
OwnerChange, FormSave, $state, CreateSelect2, i18n) { OwnerChange, CredentialFormSave, $state, CreateSelect2, i18n) {
ClearScope(); ClearScope();
@@ -126,7 +126,7 @@ export default ['$scope', '$rootScope', '$compile', '$location',
// Save // Save
$scope.formSave = function() { $scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) { if ($scope[form.name + '_form'].$valid) {
FormSave({ scope: $scope, mode: 'add' }); CredentialFormSave({ scope: $scope, mode: 'add' });
} }
}; };

View File

@@ -8,10 +8,10 @@ export default ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert', '$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices', 'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices',
'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange', 'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange',
'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n', 'CredentialFormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n',
function($scope, $rootScope, $compile, $location, $log, function($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt, $stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt,
GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, FormSave, Wait, GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, CredentialFormSave, Wait,
$state, CreateSelect2, Authorization, i18n) { $state, CreateSelect2, Authorization, i18n) {
ClearScope(); ClearScope();
@@ -236,7 +236,7 @@ export default ['$scope', '$rootScope', '$compile', '$location',
// Save changes to the parent // Save changes to the parent
$scope.formSave = function() { $scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) { if ($scope[form.name + '_form'].$valid) {
FormSave({ scope: $scope, mode: 'edit' }); CredentialFormSave({ scope: $scope, mode: 'edit' });
} }
}; };

View File

@@ -0,0 +1,105 @@
export default
function BecomeMethodChange(Empty, i18n) {
return function(params) {
var scope = params.scope;
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
};
}
BecomeMethodChange.$inject =
[ 'Empty', 'i18n' ];

View File

@@ -0,0 +1,105 @@
export default
function CredentialFormSave($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) {
return function(params) {
var scope = params.scope,
mode = params.mode,
form = CredentialForm,
data = {}, fld, url;
for (fld in form.fields) {
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
fld !== 'ssh_password') {
if (fld === "organization" && !scope[fld]) {
data.user = $rootScope.current_user.id;
} else if (scope[fld] === null) {
data[fld] = "";
} else {
data[fld] = scope[fld];
}
}
}
data.kind = scope.kind.value;
if (scope.become_method === null || typeof scope.become_method === 'undefined') {
data.become_method = "";
data.become_username = "";
data.become_password = "";
} else {
data.become_method = (scope.become_method.value) ? scope.become_method.value : "";
}
switch (data.kind) {
case 'ssh':
data.password = scope.ssh_password;
break;
case 'aws':
data.username = scope.access_key;
data.password = scope.secret_key;
break;
case 'rax':
data.password = scope.api_key;
break;
case 'gce':
data.username = scope.email_address;
data.project = scope.project;
break;
case 'azure':
data.username = scope.subscription;
}
Wait('start');
if (mode === 'add') {
url = GetBasePath("credentials");
Rest.setUrl(url);
Rest.post(data)
.success(function (data) {
scope.addedItem = data.id;
Wait('stop');
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'credentials') {
$state.go('credentials.edit', {credential_id: data.id}, {reload: true});
}
else {
ReturnToCaller(1);
}
})
.error(function (data, status) {
Wait('stop');
// TODO: hopefully this conditional error handling will to away in a future version of tower. The reason why we cannot
// simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show
// the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the
// error is never shown. In the future, the API will hopefully either behave or respond differently.
if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) {
scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported.");
}
else {
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to create new Credential. POST status: ') + status
});
}
});
} else {
url = GetBasePath('credentials') + scope.id + '/';
Rest.setUrl(url);
Rest.put(data)
.success(function () {
Wait('stop');
$state.go($state.current, {}, {reload: true});
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to update Credential. PUT status: ') + status
});
});
}
};
}
CredentialFormSave.$inject =
[ '$rootScope', '$location', 'Alert', 'Rest',
'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm',
'ReturnToCaller', 'Wait', '$state', 'i18n'
];

View File

@@ -0,0 +1,192 @@
export default
function KindChange(Empty, i18n) {
return function(params) {
var scope = params.scope,
reset = params.reset,
collapse, id;
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.subscription_required = false;
scope.key_description = i18n.sprintf(i18n._("Paste the contents of the SSH private key file.%s or click to close%s"), "<div class=\"popover-footer\"><span class=\"key\">Esc</span>", "</div>");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.passwordLabel = i18n._('Password');
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.domain_required = false;
scope.subscription_required = false;
scope.key_description = i18n._("Paste the contents of the SSH private key file.");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.projectLabel = '';
scope.domainLabel = '';
scope.project_required = false;
scope.passwordLabel = i18n._('Password (API Key)');
scope.projectPopOver = "<p>" + i18n._("The project value") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host value") + "</p>";
scope.ssh_key_data_api_error = '';
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
// Reset all the field values related to Kind.
if (reset) {
scope.access_key = null;
scope.secret_key = null;
scope.api_key = null;
scope.username = null;
scope.password = null;
scope.password_confirm = null;
scope.ssh_key_data = null;
scope.ssh_key_unlock = null;
scope.ssh_key_unlock_confirm = null;
scope.become_username = null;
scope.become_password = null;
scope.authorize = false;
scope.authorize_password = null;
}
// Collapse or open help widget based on whether scm value is selected
collapse = $('#credential_kind').parent().find('.panel-collapse').first();
id = collapse.attr('id');
if (!Empty(scope.kind) && scope.kind.value !== '') {
if ($('#' + id + '-icon').hasClass('icon-minus')) {
scope.accordionToggle('#' + id);
}
} else {
if ($('#' + id + '-icon').hasClass('icon-plus')) {
scope.accordionToggle('#' + id);
}
}
};
}
KindChange.$inject =
[ 'Empty', 'i18n' ];

View File

@@ -0,0 +1,18 @@
export default
function OwnerChange() {
return function(params) {
var scope = params.scope,
owner = scope.owner;
if (owner === 'team') {
scope.team_required = true;
scope.user_required = false;
scope.user = null;
scope.user_username = null;
} else {
scope.team_required = false;
scope.user_required = true;
scope.team = null;
scope.team_name = null;
}
};
}

View File

@@ -8,11 +8,19 @@ import ownerList from './ownerList.directive';
import CredentialsList from './list/credentials-list.controller'; import CredentialsList from './list/credentials-list.controller';
import CredentialsAdd from './add/credentials-add.controller'; import CredentialsAdd from './add/credentials-add.controller';
import CredentialsEdit from './edit/credentials-edit.controller'; import CredentialsEdit from './edit/credentials-edit.controller';
import BecomeMethodChange from './factories/become-method-change.factory';
import CredentialFormSave from './factories/credential-form-save.factory';
import KindChange from './factories/kind-change.factory';
import OwnerChange from './factories/owner-change.factory';
import { N_ } from '../i18n'; import { N_ } from '../i18n';
export default export default
angular.module('credentials', []) angular.module('credentials', [])
.directive('ownerList', ownerList) .directive('ownerList', ownerList)
.factory('BecomeMethodChange', BecomeMethodChange)
.factory('CredentialFormSave', CredentialFormSave)
.factory('KindChange', KindChange)
.factory('OwnerChange', OwnerChange)
.controller('CredentialsList', CredentialsList) .controller('CredentialsList', CredentialsList)
.controller('CredentialsAdd', CredentialsAdd) .controller('CredentialsAdd', CredentialsAdd)
.controller('CredentialsEdit', CredentialsEdit) .controller('CredentialsEdit', CredentialsEdit)

View File

@@ -1,58 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import './forms';
import './lists';
import Children from "./helpers/Children";
import Credentials from "./helpers/Credentials";
import Events from "./helpers/Events";
import Groups from "./helpers/Groups";
import Hosts from "./helpers/Hosts";
import JobSubmission from "./helpers/JobSubmission";
import JobTemplates from "./helpers/JobTemplates";
import Jobs from "./helpers/Jobs";
import LoadConfig from "./helpers/LoadConfig";
import Parse from "./helpers/Parse";
import ProjectPath from "./helpers/ProjectPath";
import Projects from "./helpers/Projects";
import Schedules from "./helpers/Schedules";
import Selection from "./helpers/Selection";
import Users from "./helpers/Users";
import Variables from "./helpers/Variables";
import ApiDefaults from "./helpers/api-defaults";
import inventory from "./helpers/inventory";
import MD5 from "./helpers/md5";
import Teams from "./helpers/teams";
import AdhocHelper from "./helpers/Adhoc";
import ApiModelHelper from "./helpers/ApiModel";
import ActivityStreamHelper from "./helpers/ActivityStream";
export
{ Children,
Credentials,
Events,
Groups,
Hosts,
JobSubmission,
JobTemplates,
Jobs,
LoadConfig,
Parse,
ProjectPath,
Projects,
Schedules,
Selection,
Users,
Variables,
ApiDefaults,
inventory,
MD5,
Teams,
AdhocHelper,
ApiModelHelper,
ActivityStreamHelper
};

View File

@@ -1,64 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ActivityStream
* @description Helper functions for the activity stream
*/
export default
angular.module('ActivityStreamHelper', ['Utilities'])
.factory('GetTargetTitle', ['i18n',
function (i18n) {
return function (target) {
var rtnTitle = i18n._('ALL ACTIVITY');
switch(target) {
case 'project':
rtnTitle = i18n._('PROJECTS');
break;
case 'inventory':
rtnTitle = i18n._('INVENTORIES');
break;
case 'credential':
rtnTitle = i18n._('CREDENTIALS');
break;
case 'user':
rtnTitle = i18n._('USERS');
break;
case 'team':
rtnTitle = i18n._('TEAMS');
break;
case 'notification_template':
rtnTitle = i18n._('NOTIFICATION TEMPLATES');
break;
case 'organization':
rtnTitle = i18n._('ORGANIZATIONS');
break;
case 'job':
rtnTitle = i18n._('JOBS');
break;
case 'custom_inventory_script':
rtnTitle = i18n._('INVENTORY SCRIPTS');
break;
case 'schedule':
rtnTitle = i18n._('SCHEDULES');
break;
case 'host':
rtnTitle = i18n._('HOSTS');
break;
case 'template':
rtnTitle = i18n._('TEMPLATES');
break;
}
return rtnTitle;
};
}
]);

View File

@@ -1,172 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Adhoc
* @description These routines are shared by adhoc command related controllers.
* The content here is very similar to the JobSubmission helper, and in fact,
* certain services are pulled from that helper. This leads to an important
* point: if you need to create functionality that is shared between the command
* and playbook run process, put that code in the JobSubmission helper and make
* it into a reusable step (by specifying a callback parameter in the factory).
* For a good example of this, please see how the AdhocLaunch factory in this
* file utilizes the CheckPasswords factory from the JobSubmission helper.
*
* #AdhocRelaunch Step 1: preparing the GET to ad_hoc_commands/n/relaunch
* The adhoc relaunch process is called from the JobSubmission helper. It is a
* separate process from the initial adhoc run becuase of the way the API
* endpoints work. For AdhocRelaunch, we have access to the original run and
* we can pull the related relaunch URL by knowing the original Adhoc runs ID.
*
* #AdhocRelaunch Step 2: If we got passwords back, add them
* The relaunch URL gives us back the passwords we need to prompt for (if any).
* We'll go to step 3 if there are passwords, and step 4 if not.
*
* #AdhocRelaunch Step 3: PromptForPasswords and the CreateLaunchDialog
*
* #AdhocRelaunch Step 5: StartAdhocRun
*
* #AdhocRelaunch Step 6: LaunchJob and navigate to the standard out page.
* **If you are
* TODO: once the API endpoint is figured out for running an adhoc command
* from the form is figured out, the rest work should probably be excised from
* the controller and moved into here. See the todo statements in the
* controller for more information about this.
*/
export default
angular.module('AdhocHelper', ['RestServices', 'Utilities',
'CredentialFormDefinition', 'CredentialsListDefinition',
'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog',
'FormGenerator', 'JobVarsPromptFormDefinition'])
/**
* @ngdoc method
* @name helpers.function:JobSubmission#AdhocRun
* @methodOf helpers.function:JobSubmission
* @description The adhoc Run function is run when the user clicks the relaunch button
*
*/
// Submit request to run an adhoc comamand
.factory('AdhocRun', ['$location','$stateParams', 'LaunchJob',
'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors',
'Wait', 'Empty', 'CreateLaunchDialog', '$state',
function ($location, $stateParams, LaunchJob, PromptForPasswords,
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) {
return function (params) {
var id = params.project_id,
scope = params.scope.$new(),
new_job_id,
html,
url;
// this is used to cancel a running adhoc command from
// the jobs page
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
// Delete the job
Wait('start');
Rest.setUrl(GetBasePath('ad_hoc_commands') + new_job_id + '/');
Rest.destroy()
.success(function() {
Wait('stop');
})
.error(function (data, status) {
ProcessErrors(scope, data, status,
null, { hdr: 'Error!',
msg: 'Call to ' + url +
' failed. DELETE returned status: ' +
status });
});
});
if (scope.removeStartAdhocRun) {
scope.removeStartAdhocRun();
}
scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() {
var password,
postData={};
for (password in scope.passwords) {
postData[scope.passwords[password]] = scope[
scope.passwords[password]
];
}
// Re-launch the adhoc job
Rest.setUrl(url);
Rest.post(postData)
.success(function (data) {
Wait('stop');
if($location.path().replace(/^\//, '').split('/')[0] !== 'jobs') {
$state.go('adHocJobStdout', {id: data.id});
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, {
hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status });
});
});
// start routine only if passwords need to be prompted
if (scope.removeCreateLaunchDialog) {
scope.removeCreateLaunchDialog();
}
scope.removeCreateLaunchDialog = scope.$on('CreateLaunchDialog',
function(e, html, url) {
CreateLaunchDialog({
scope: scope,
html: html,
url: url,
callback: 'StartAdhocRun'
});
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords',
function(e, passwords_needed_to_start,html, url) {
PromptForPasswords({
scope: scope,
passwords: passwords_needed_to_start,
callback: 'CreateLaunchDialog',
html: html,
url: url
});
}); // end password prompting routine
// start the adhoc relaunch routine
Wait('start');
url = GetBasePath('ad_hoc_commands') + id + '/relaunch/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
new_job_id = data.id;
scope.passwords_needed_to_start = data.passwords_needed_to_start;
if (!Empty(data.passwords_needed_to_start) &&
data.passwords_needed_to_start.length > 0) {
// go through the password prompt routine before
// starting the adhoc run
scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url);
}
else {
// no prompting of passwords needed
scope.$emit('StartAdhocRun');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job template details. GET returned status: ' + status });
});
};
}]);

View File

@@ -1,63 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ApiModel
* @description Helper functions to convert singular/plural versions of our models to the opposite
*/
export default
angular.module('ApiModelHelper', ['Utilities'])
.factory('ModelToBasePathKey', [
function () {
return function (model) {
// This function takes in the singular model string and returns the key needed
// to get the base path from $rootScope/local storage.
var basePathKey;
switch(model) {
case 'project':
basePathKey = 'projects';
break;
case 'inventory':
basePathKey = 'inventory';
break;
case 'job_template':
basePathKey = 'job_templates';
break;
case 'credential':
basePathKey = 'credentials';
break;
case 'user':
basePathKey = 'users';
break;
case 'team':
basePathKey = 'teams';
break;
case 'notification_template':
basePathKey = 'notification_templates';
break;
case 'organization':
basePathKey = 'organizations';
break;
case 'management_job':
basePathKey = 'management_jobs';
break;
case 'custom_inventory_script':
basePathKey = 'inventory_scripts';
break;
case 'workflow_job_template':
basePathKey = 'workflow_job_templates';
break;
}
return basePathKey;
};
}
]);

View File

@@ -1,111 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Children
* @descriptionUsed in job_events to expand/collapse children by setting the
* 'show' attribute of each job_event in the set of job_events.
* See the filter in job_events.js list.
*/
export default
angular.module('ChildrenHelper', ['RestServices', 'Utilities'])
.factory('ToggleChildren', ['$location', 'Store', function ($location, Store) {
return function (params) {
var scope = params.scope,
list = params.list,
id = params.id,
set = scope[list.name],
clicked,
//base = $location.path().replace(/^\//, '').split('/')[0],
path = $location.path(),
local_child_store;
function updateExpand(key, expand) {
var found = false;
local_child_store.every(function(child, i) {
if (child.key === key) {
local_child_store[i].expand = expand;
found = true;
return false;
}
return true;
});
if (!found) {
local_child_store.push({ key: key, expand: expand });
}
}
function updateShow(key, show) {
var found = false;
local_child_store.every(function(child, i) {
if (child.key === key) {
local_child_store[i].show = show;
found = true;
return false;
}
return true;
});
if (!found) {
local_child_store.push({ key: key, show: show });
}
}
function expand(node) {
var i, has_children = false;
for (i = node + 1; i < set.length; i++) {
if (set[i].parent === set[node].id) {
updateShow(set[i].key, true);
set[i].show = true;
}
}
set[node].ngicon = (has_children) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-minus-square-o node-toggle';
}
function collapse(node) {
var i, has_children = false;
for (i = node + 1; i < set.length; i++) {
if (set[i].parent === set[node].id) {
set[i].show = false;
has_children = true;
updateShow(set[i].key, false);
if (set[i].related.children) {
collapse(i);
}
}
}
set[node].ngicon = (has_children) ? 'fa fa-plus-square-o node-toggle' : 'fa fa-square-o node-toggle';
}
local_child_store = Store(path + '_children');
if (!local_child_store) {
local_child_store = [];
}
// Scan the array list and find the clicked element
set.every(function(row, i) {
if (row.id === id) {
clicked = i;
return false;
}
return true;
});
// Expand or collapse children based on clicked element's icon
if (/plus-square-o/.test(set[clicked].ngicon)) {
// Expand: lookup and display children
expand(clicked);
updateExpand(set[clicked].key, true);
} else if (/minus-square-o/.test(set[clicked].ngicon)) {
collapse(clicked);
updateExpand(set[clicked].key, false);
}
Store(path + '_children', local_child_store);
};
}
]);

View File

@@ -1,432 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Credentials
* @description Functions shared amongst Credential related controllers
*/
export default
angular.module('CredentialsHelper', ['Utilities'])
.factory('KindChange', ['Empty', 'i18n',
function (Empty, i18n) {
return function (params) {
var scope = params.scope,
reset = params.reset,
collapse, id;
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.subscription_required = false;
scope.key_description = i18n.sprintf(i18n._("Paste the contents of the SSH private key file.%s or click to close%s"), "<div class=\"popover-footer\"><span class=\"key\">Esc</span>", "</div>");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.passwordLabel = i18n._('Password');
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.domain_required = false;
scope.subscription_required = false;
scope.key_description = i18n._("Paste the contents of the SSH private key file.");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.projectLabel = '';
scope.domainLabel = '';
scope.project_required = false;
scope.passwordLabel = i18n._('Password (API Key)');
scope.projectPopOver = "<p>" + i18n._("The project value") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host value") + "</p>";
scope.ssh_key_data_api_error = '';
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
// Reset all the field values related to Kind.
if (reset) {
scope.access_key = null;
scope.secret_key = null;
scope.api_key = null;
scope.username = null;
scope.password = null;
scope.password_confirm = null;
scope.ssh_key_data = null;
scope.ssh_key_unlock = null;
scope.ssh_key_unlock_confirm = null;
scope.become_username = null;
scope.become_password = null;
scope.authorize = false;
scope.authorize_password = null;
}
// Collapse or open help widget based on whether scm value is selected
collapse = $('#credential_kind').parent().find('.panel-collapse').first();
id = collapse.attr('id');
if (!Empty(scope.kind) && scope.kind.value !== '') {
if ($('#' + id + '-icon').hasClass('icon-minus')) {
scope.accordionToggle('#' + id);
}
} else {
if ($('#' + id + '-icon').hasClass('icon-plus')) {
scope.accordionToggle('#' + id);
}
}
};
}
])
.factory('BecomeMethodChange', ['Empty', 'i18n',
function (Empty, i18n) {
return function (params) {
console.log('become method has changed');
var scope = params.scope;
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
};
}
])
.factory('OwnerChange', [
function () {
return function (params) {
var scope = params.scope,
owner = scope.owner;
if (owner === 'team') {
scope.team_required = true;
scope.user_required = false;
scope.user = null;
scope.user_username = null;
} else {
scope.team_required = false;
scope.user_required = true;
scope.team = null;
scope.team_name = null;
}
};
}
])
.factory('FormSave', ['$rootScope', '$location', 'Alert', 'Rest', 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', 'ReturnToCaller', 'Wait', '$state', 'i18n',
function ($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) {
return function (params) {
var scope = params.scope,
mode = params.mode,
form = CredentialForm,
data = {}, fld, url;
for (fld in form.fields) {
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
fld !== 'ssh_password') {
if (fld === "organization" && !scope[fld]) {
data.user = $rootScope.current_user.id;
} else if (scope[fld] === null) {
data[fld] = "";
} else {
data[fld] = scope[fld];
}
}
}
data.kind = scope.kind.value;
if (scope.become_method === null || typeof scope.become_method === 'undefined') {
data.become_method = "";
data.become_username = "";
data.become_password = "";
} else {
data.become_method = (scope.become_method.value) ? scope.become_method.value : "";
}
switch (data.kind) {
case 'ssh':
data.password = scope.ssh_password;
break;
case 'aws':
data.username = scope.access_key;
data.password = scope.secret_key;
break;
case 'rax':
data.password = scope.api_key;
break;
case 'gce':
data.username = scope.email_address;
data.project = scope.project;
break;
case 'azure':
data.username = scope.subscription;
}
Wait('start');
if (mode === 'add') {
url = GetBasePath("credentials");
Rest.setUrl(url);
Rest.post(data)
.success(function (data) {
scope.addedItem = data.id;
Wait('stop');
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'credentials') {
$state.go('credentials.edit', {credential_id: data.id}, {reload: true});
}
else {
ReturnToCaller(1);
}
})
.error(function (data, status) {
Wait('stop');
// TODO: hopefully this conditional error handling will to away in a future version of tower. The reason why we cannot
// simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show
// the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the
// error is never shown. In the future, the API will hopefully either behave or respond differently.
if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) {
scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported.");
}
else {
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to create new Credential. POST status: ') + status
});
}
});
} else {
url = GetBasePath('credentials') + scope.id + '/';
Rest.setUrl(url);
Rest.put(data)
.success(function () {
Wait('stop');
$state.go($state.current, {}, {reload: true});
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to update Credential. PUT status: ') + status
});
});
}
};
}
]);

View File

@@ -1,237 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Events
* @description EventView - show the job_events form in a modal dialog
*/
export default
angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefinition', 'JobEventsFormDefinition'])
.factory('EventView', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'JobEventDataForm', 'Empty', 'JobEventsForm',
function ($rootScope, $location, $log, $stateParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath,
FormatDate, JobEventDataForm, Empty, JobEventsForm) {
return function (params) {
var event_id = params.event_id,
generator = GenerateForm,
form = angular.copy(JobEventsForm),
scope,
defaultUrl = GetBasePath('base') + 'job_events/' + event_id + '/';
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl);
Rest.get()
.success(function (data) {
var i, n, fld, rows, txt, cDate;
// If event_data is not available, remove fields that depend on it
if ($.isEmptyObject(data.event_data) || !data.event_data.res || typeof data.event_data.res === 'string') {
for (fld in form.fields) {
switch (fld) {
case 'start':
case 'end':
case 'delta':
case 'msg':
case 'stdout':
case 'stderr':
case 'msg':
case 'results':
case 'module_name':
case 'module_args':
case 'rc':
delete form.fields[fld];
break;
}
}
}
if ($.isEmptyObject(data.event_data) || !data.event_data.res || typeof data.event_data.res !== 'string') {
delete form.fields.traceback;
}
// Remove remaining form fields that do not have a corresponding data value
for (fld in form.fields) {
switch (fld) {
case 'start':
case 'end':
case 'delta':
case 'msg':
case 'stdout':
case 'stderr':
case 'msg':
case 'rc':
if (data.event_data && data.event_data.res && Empty(data.event_data.res[fld])) {
delete form.fields[fld];
} else {
if (form.fields[fld].type === 'textarea') {
n = data.event_data.res[fld].match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
rows = (rows < 3) ? 3 : rows;
form.fields[fld].rows = rows;
}
}
break;
case 'results':
if (data.event_data && data.event_data.res && data.event_data.res[fld] === undefined) {
// not defined
delete form.fields[fld];
} else if (!Array.isArray(data.event_data.res[fld]) || data.event_data.res[fld].length === 0) {
// defined, but empty
delete form.fields[fld];
} else {
// defined and not empty, so attempt to size the textarea field
txt = '';
for (i = 0; i < data.event_data.res[fld].length; i++) {
txt += data.event_data.res[fld][i];
}
if (txt === '') {
// there's an array, but the actual text is empty
delete form.fields[fld];
} else {
n = txt.match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
rows = (rows < 3) ? 3 : rows;
form.fields[fld].rows = rows;
}
}
break;
case 'module_name':
case 'module_args':
if (data.event_data && data.event_data.res) {
if (data.event_data.res.invocation === undefined ||
data.event_data.res.invocation[fld] === undefined) {
delete form.fields[fld];
}
}
break;
}
}
// load the form
scope = generator.inject(form, {
mode: 'edit',
modal: true,
related: false
});
generator.reset();
scope.formModalAction = function () {
$('#form-modal').modal("hide");
};
scope.formModalActionLabel = 'OK';
scope.formModalCancelShow = false;
scope.formModalInfo = 'View JSON';
$('#form-modal .btn-success').removeClass('btn-success').addClass('btn-none');
$('#form-modal').addClass('skinny-modal');
scope.formModalHeader = data.event_display.replace(/^\u00a0*/g, '');
// Respond to View JSON button
scope.formModalInfoAction = function () {
var generator = GenerateForm,
scope = generator.inject(JobEventDataForm, {
mode: 'edit',
modal: true,
related: false,
modal_selector: '#form-modal2',
modal_body_id: 'form-modal2-body',
modal_title_id: 'formModal2Header'
});
generator.reset();
scope.formModal2Header = data.event_display.replace(/^\u00a0*/g, '');
scope.event_data = JSON.stringify(data.event_data, null, '\t');
scope.formModal2ActionLabel = 'OK';
scope.formModal2CancelShow = false;
scope.formModal2Info = false;
scope.formModalInfo = 'View JSON';
scope.formModal2Action = function () {
$('#form-modal2').modal("hide");
};
$('#form-modal2 .btn-success').removeClass('btn-success').addClass('btn-none');
};
if (typeof data.event_data.res === 'string') {
scope.traceback = data.event_data.res;
}
for (fld in form.fields) {
switch (fld) {
case 'status':
if (data.failed) {
scope.status = 'error';
} else if (data.changed) {
scope.status = 'changed';
} else {
scope.status = 'success';
}
break;
case 'created':
cDate = new Date(data.created);
scope.created = FormatDate(cDate);
break;
case 'host':
if (data.summary_fields && data.summary_fields.host) {
scope.host = data.summary_fields.host.name;
}
break;
case 'id':
case 'task':
case 'play':
scope[fld] = data[fld];
break;
case 'start':
case 'end':
if (data.event_data && data.event_data.res && !Empty(data.event_data.res[fld])) {
scope[fld] = data.event_data.res[fld];
}
break;
case 'results':
if (Array.isArray(data.event_data.res[fld]) && data.event_data.res[fld].length > 0) {
txt = '';
for (i = 0; i < data.event_data.res[fld].length; i++) {
txt += data.event_data.res[fld][i];
}
if (txt !== '') {
scope[fld] = txt;
}
}
break;
case 'msg':
case 'stdout':
case 'stderr':
case 'delta':
case 'rc':
if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) {
scope[fld] = data.event_data.res[fld];
}
break;
case 'module_name':
case 'module_args':
if (data.event_data.res && data.event_data.res.invocation) {
scope[fld] = data.event_data.res.invocation[fld];
}
break;
}
}
if (!scope.$$phase) {
scope.$digest();
}
})
.error(function (data, status) {
$('#form-modal').modal("hide");
ProcessErrors(scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to retrieve event: ' + event_id + '. GET status: ' + status });
});
};
}
]);

File diff suppressed because it is too large Load Diff

View File

@@ -1,406 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/* jshint loopfunc: true */
/**
* @ngdoc function
* @name helpers.function:Hosts
* @description Routines that handle host add/edit/delete on the Inventory detail page.
*/
'use strict';
import listGenerator from '../shared/list-generator/main';
export default
angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'HostListDefinition',
listGenerator.name, 'HostsHelper',
'InventoryHelper', 'InventoryFormDefinition', 'SelectionHelper',
'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'StandardOutHelper',
'GroupListDefinition'
])
.factory('SetEnabledMsg', [ function() {
return function(host) {
if (host.has_inventory_sources) {
// Inventory sync managed, so not clickable
host.enabledToolTip = (host.enabled) ? 'Host is available' : 'Host is not available';
}
else {
// Clickable
host.enabledToolTip = (host.enabled) ? 'Host is available. Click to toggle.' : 'Host is not available. Click to toggle.';
}
};
}])
.factory('SetHostStatus', ['SetEnabledMsg', function(SetEnabledMsg) {
return function(host) {
// Set status related fields on a host object
host.activeFailuresLink = '/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + host.inventory +
'&host_name=' + encodeURI(host.name);
if (host.has_active_failures === true) {
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
host.active_failures = 'failed';
}
else if (host.has_active_failures === false && host.last_job === null) {
host.has_active_failures = 'none';
host.badgeToolTip = "No job data available.";
host.active_failures = 'n/a';
}
else if (host.has_active_failures === false && host.last_job !== null) {
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
host.active_failures = 'success';
}
host.enabled_flag = host.enabled;
SetEnabledMsg(host);
};
}])
.factory('SetStatus', ['$filter', 'SetEnabledMsg', 'Empty', function($filter, SetEnabledMsg, Empty) {
return function(params) {
var scope = params.scope,
host = params.host,
i, html, title;
function ellipsis(a) {
if (a.length > 25) {
return a.substr(0,25) + '...';
}
return a;
}
function noRecentJobs() {
title = 'No job data';
html = "<p>No recent job data available for this host.</p>\n";
}
function setMsg(host) {
var j, job, jobs;
if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) {
if (host.has_active_failures === true) {
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
host.active_failures = 'error';
}
else {
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
host.active_failures = 'successful';
}
if (host.summary_fields.recent_jobs.length > 0) {
// build html table of job status info
jobs = host.summary_fields.recent_jobs.sort(
function(a,b) {
// reverse numerical order
return -1 * (a - b);
});
title = "Recent Jobs";
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>\n";
html += "<th>Status</th>\n";
html += "<th>Finished</th>\n";
html += "<th>Name</th>\n";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
for (j=0; j < jobs.length; j++) {
job = jobs[j];
html += "<tr>\n";
// SmartStatus-tooltips are named --success whereas icon-job uses successful
var iconStatus = (job.status === 'successful') ? 'success' : 'failed';
html += "<td><a href=\"#/jobs/" + job.id + "\"><i class=\"fa DashboardList-status SmartStatus-tooltip--" + iconStatus + " icon-job-" +
job.status + "\"></i></a></td>\n";
html += "<td>" + ($filter('longDate')(job.finished)).replace(/ /,'<br />') + "</td>\n";
html += "<td class=\"break\"><a href=\"#/jobs/" + job.id + "\" " +
"aw-tool-tip=\"" + job.status.charAt(0).toUpperCase() + job.status.slice(1) +
". Click for details\" data-placement=\"top\">" + ellipsis(job.name) + "</a></td>\n";
html += "</tr>\n";
}
html += "</tbody>\n";
html += "</table>\n";
}
else {
noRecentJobs();
}
}
else if (host.has_active_failures === false && host.last_job === null) {
host.badgeToolTip = "No job data available.";
host.active_failures = 'none';
noRecentJobs();
}
host.job_status_html = html;
host.job_status_title = title;
}
if (!Empty(host)) {
// update single host
setMsg(host);
SetEnabledMsg(host);
}
else {
// update all hosts
for (i=0; i < scope.hosts.length; i++) {
setMsg(scope.hosts[i]);
SetEnabledMsg(scope.hosts[i]);
}
}
};
}])
.factory('HostsReload', [ '$stateParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'Wait',
'SetHostStatus', 'SetStatus', 'ApplyEllipsis',
function($stateParams, Empty, InventoryHosts, GetBasePath, Wait, SetHostStatus, SetStatus,
ApplyEllipsis) {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope;
if (scope.removeHostsReloadPostRefresh) {
scope.removeHostsReloadPostRefresh();
}
scope.removeHostsReloadPostRefresh = scope.$on('PostRefresh', function(e, set) {
if (set === 'hosts') {
for (var i=0; i < scope.hosts.length; i++) {
//Set tooltip for host enabled flag
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
}
SetStatus({ scope: scope });
setTimeout(function() { ApplyEllipsis('#hosts_table .host-name a'); }, 2500);
Wait('stop');
if (parent_scope) {
parent_scope.$emit('HostReloadComplete');
}
}
});
};
}])
.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList',
function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList) {
return function(params) {
var host_id = params.host_id,
group_scope = params.group_scope,
parent_scope = params.host_scope,
parent_group = group_scope.selected_group_id,
scope = parent_scope.$new(),
buttonSet, url, host;
buttonSet = [{
label: "Cancel",
onClick: function() {
scope.cancel();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "host-copy-cancel-button"
},{
label: "OK",
onClick: function() {
scope.performCopy();
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "host-copy-ok-button"
}];
if (scope.removeHostCopyPostRefresh) {
scope.removeHostCopyPostRefresh();
}
scope.removeHostCopyPostRefresh = scope.$on('PostRefresh', function() {
scope.copy_groups.forEach(function(row, i) {
scope.copy_groups[i].checked = '0';
});
Wait('stop');
$('#host-copy-dialog').dialog('open');
$('#host-copy-ok-button').attr('disabled','disabled');
// prevent backspace from navigation when not in input or textarea field
$(document).on("keydown", function (e) {
if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) {
e.preventDefault();
}
});
});
if (scope.removeHostCopyDialogReady) {
scope.removeHostCopyDialogReady();
}
scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() {
GenerateList.inject(GroupList, {
mode: 'lookup',
id: 'copy-host-select-container',
scope: scope
});
});
if (scope.removeShowDialog) {
scope.removeShowDialog();
}
scope.removeShowDialog = scope.$on('ShowDialog', function() {
var d;
scope.name = host.name;
scope.copy_choice = "copy";
d = angular.element(document.getElementById('host-copy-dialog'));
$compile(d)(scope);
CreateDialog({
id: 'host-copy-dialog',
scope: scope,
buttons: buttonSet,
width: 650,
height: 650,
minWidth: 600,
title: 'Copy or Move Host',
callback: 'HostCopyDialogReady',
onClose: function() {
scope.cancel();
}
});
});
Wait('start');
url = GetBasePath('hosts') + host_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
host = data;
scope.$emit('ShowDialog');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status });
});
scope.cancel = function() {
$(document).off("keydown");
try {
$('#host-copy-dialog').dialog('close');
}
catch(e) {
// ignore
}
scope.$destroy();
};
scope['toggle_' + GroupList.iterator] = function (id) {
var count = 0,
list = GroupList;
scope[list.name].forEach( function(row, i) {
if (row.id === id) {
if (row.checked) {
scope[list.name][i].success_class = 'success';
}
else {
scope[list.name][i].success_class = '';
}
} else {
scope[list.name][i].checked = 0;
scope[list.name][i].success_class = '';
}
});
// Check if any rows are checked
scope[list.name].forEach(function(row) {
if (row.checked) {
count++;
}
});
if (count === 0) {
$('#host-copy-ok-button').attr('disabled','disabled');
}
else {
$('#host-copy-ok-button').removeAttr('disabled');
}
};
scope.performCopy = function() {
var list = GroupList,
target,
url;
Wait('start');
if (scope.use_root_group) {
target = null;
}
else {
scope[list.name].every(function(row) {
if (row.checked === 1) {
target = row;
return false;
}
return true;
});
}
if (scope.copy_choice === 'move') {
// Respond to move
// disassociate the host from the original parent
if (scope.removeHostRemove) {
scope.removeHostRemove();
}
scope.removeHostRemove = scope.$on('RemoveHost', function () {
if (parent_group > 0) {
// Only remove a host from a parent when the parent is a group and not the inventory root
url = GetBasePath('groups') + parent_group + '/hosts/';
Rest.setUrl(url);
Rest.post({ id: host.id, disassociate: 1 })
.success(function () {
scope.cancel();
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to remove ' + host.name + ' from group ' + parent_group + '. POST returned: ' + status });
});
} else {
scope.cancel();
}
});
// add the new host to the target
url = GetBasePath('groups') + target.id + '/hosts/';
Rest.setUrl(url);
Rest.post(host)
.success(function () {
scope.$emit('RemoveHost');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status });
});
}
else {
// Respond to copy by adding the new host to the target
url = GetBasePath('groups') + target.id + '/hosts/';
Rest.setUrl(url);
Rest.post(host)
.success(function () {
scope.cancel();
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status
});
});
}
};
};
}]);

View File

@@ -1,337 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
'use strict';
export default
angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition',
'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition'])
.factory('CreateLaunchDialog', ['$compile', 'CreateDialog', 'Wait', 'ParseTypeChange',
function($compile, CreateDialog, Wait, ParseTypeChange) {
return function(params) {
var buttons,
scope = params.scope,
html = params.html,
// job_launch_data = {},
callback = params.callback || 'PlaybookLaunchFinished',
// url = params.url,
e;
// html+='<br>job_launch_form.$valid = {{job_launch_form.$valid}}<br>';
html+='</form>';
$('#password-modal').empty().html(html);
$('#password-modal').find('#job_extra_vars').before(scope.helpContainer);
e = angular.element(document.getElementById('password-modal'));
$compile(e)(scope);
if(scope.prompt_for_vars===true){
ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"});
}
buttons = [{
label: "Cancel",
onClick: function() {
$('#password-modal').dialog('close');
// scope.$emit('CancelJob');
// scope.$destroy();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "password-cancel-button"
},{
label: "Launch",
onClick: function() {
scope.$emit(callback);
$('#password-modal').dialog('close');
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "password-accept-button"
}];
CreateDialog({
id: 'password-modal',
scope: scope,
buttons: buttons,
width: 620,
height: 700, //(scope.passwords.length > 1) ? 700 : 500,
minWidth: 500,
title: 'Launch Configuration',
callback: 'DialogReady',
onOpen: function(){
Wait('stop');
}
});
if (scope.removeDialogReady) {
scope.removeDialogReady();
}
scope.removeDialogReady = scope.$on('DialogReady', function() {
$('#password-modal').dialog('open');
$('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' );
e = angular.element(document.getElementById('password-accept-button'));
$compile(e)(scope);
});
};
}])
.factory('PromptForPasswords', ['CredentialForm',
function(CredentialForm) {
return function(params) {
var scope = params.scope,
callback = params.callback || 'PasswordsAccepted',
url = params.url,
form = CredentialForm,
fld, field,
html=params.html || "";
scope.passwords = params.passwords;
html += "<div class=\"alert alert-info\">Launching this job requires the passwords listed below. Enter and confirm each password before continuing.</div>\n";
scope.passwords.forEach(function(password) {
// Prompt for password
field = form.fields[password];
fld = password;
scope[fld] = '';
html += "<div class=\"form-group prepend-asterisk\">\n";
html += "<label for=\"" + fld + "\">" + field.label + "</label>\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"password-field form-control input-sm\" ";
html += (field.associated) ? "ng-change=\"clearPWConfirm('" + field.associated + "')\" " : "";
html += "required ";
html += " >";
// Add error messages
html += "<div class=\"error\" ng-show=\"job_launch_form." + fld + ".$dirty && " +
"job_launch_form." + fld + ".$error.required\">Please enter a password.</div>\n";
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
// Add the related confirm field
if (field.associated) {
fld = field.associated;
field = form.fields[field.associated];
scope[fld] = '';
html += "<div class=\"form-group prepend-asterisk\">\n";
html += "<label for=\"" + fld + "\"> " + field.label + "</label>\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"form-control input-sm\" ";
html += "ng-change=\"checkStatus()\" ";
html += "required ";
html += (field.awPassMatch) ? "awpassmatch=\"" + field.associated + "\" " : "";
html += "/>";
// Add error messages
html += "<div class=\"error\" ng-show=\"job_launch_form." + fld + ".$dirty && " +
"job_launch_form." + fld + ".$error.required\">Please confirm the password.</span>\n";
html += (field.awPassMatch) ? "<span class=\"error\" ng-show=\"job_launch_form." + fld +
".$error.awpassmatch\">This value does not match the password you entered previously. Please confirm that password.</div>\n" : "";
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
}
});
scope.$emit(callback, html, url);
// Password change
scope.clearPWConfirm = function (fld) {
// If password value changes, make sure password_confirm must be re-entered
scope[fld] = '';
scope.job_launch_form[fld].$setValidity('awpassmatch', false);
scope.checkStatus();
};
scope.checkStatus = function() {
if (!scope.job_launch_form.$invalid) {
$('#password-accept-button').removeAttr('disabled');
}
else {
$('#password-accept-button').attr({ "disabled": "disabled" });
}
};
};
}])
.factory('CheckPasswords', ['Rest', 'GetBasePath', 'ProcessErrors', 'Empty',
function(Rest, GetBasePath, ProcessErrors, Empty) {
return function(params) {
var scope = params.scope,
callback = params.callback,
credential = params.credential;
var passwords = [];
if (!Empty(credential)) {
Rest.setUrl(GetBasePath('credentials')+credential);
Rest.get()
.success(function (data) {
if(data.kind === "ssh"){
if(data.password === "ASK" ){
passwords.push("ssh_password");
}
if(data.ssh_key_unlock === "ASK"){
passwords.push("ssh_key_unlock");
}
if(data.become_password === "ASK"){
passwords.push("become_password");
}
if(data.vault_password === "ASK"){
passwords.push("vault_password");
}
}
scope.$emit(callback, passwords);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job template details. GET returned status: ' + status });
});
}
};
}])
// Submit SCM Update request
.factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) {
return function (params) {
var scope = params.scope,
project_id = params.project_id,
url = GetBasePath('projects') + project_id + '/update/',
project;
if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted();
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() {
// Refresh the project list after update request submitted
Wait('stop');
if (/\d$/.test($location.path())) {
//Request submitted from projects/N page. Navigate back to the list so user can see status
$location.path('/projects');
}
if (scope.socketStatus === 'error') {
Alert('Update Started', '<div>The request to start the SCM update process was submitted. ' +
'To monitor the update status, refresh the page by clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
if (scope.refresh) {
scope.refresh();
}
}
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate();
}
scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
});
// Check to see if we have permission to perform the update and if any passwords are needed
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
project = data;
if (project.can_update) {
if (project.passwords_needed_to_updated) {
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
}
else {
Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.',
'alert-danger');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to lookup project ' + url + ' GET returned: ' + status });
});
};
}
])
// Submit Inventory Update request
.factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
function (PromptForPasswords, LaunchJob, Rest, GetBasePath, ProcessErrors, Alert, Wait) {
return function (params) {
var scope = params.scope,
url = params.url,
inventory_source;
if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted();
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () {
Wait('stop');
if (scope.socketStatus === 'error') {
Alert('Sync Started', '<div>The request to start the inventory sync process was submitted. ' +
'To monitor the status refresh the page by clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
if (scope.refreshGroups) {
// inventory detail page
scope.refreshGroups();
}
else if (scope.refresh) {
scope.refresh();
}
}
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate();
}
scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
});
// Check to see if we have permission to perform the update and if any passwords are needed
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
inventory_source = data;
if (data.can_update) {
if (data.passwords_needed_to_update) {
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
} else {
Wait('stop');
Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.',
'alert-danger');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status });
});
};
}
]);

View File

@@ -1,173 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:JobTemplatesHelper
* @description Routines shared by job related controllers
*/
export default
angular.module('JobTemplatesHelper', ['Utilities'])
/*
* Add bits to $scope for handling callback url help
*
*/
.factory('CallbackHelpInit', ['$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm', '$stateParams', 'ProcessErrors', 'ParseTypeChange',
'ParseVariableString', 'Empty', 'InventoryList', 'CredentialList','ProjectList', 'Wait',
function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, ParseTypeChange,
ParseVariableString, Empty, InventoryList, CredentialList, ProjectList, Wait) {
return function(params) {
var scope = params.scope,
defaultUrl = GetBasePath('job_templates'),
// generator = GenerateForm,
form = JobTemplateForm(),
// loadingFinishedCount = 0,
// base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.job_template_id;
// checkSCMStatus, getPlaybooks, callback,
// choicesCount = 0;
CredentialList = _.cloneDeep(CredentialList);
// 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 provisioning 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();
// this fills the job template form both on copy of the job template
// and on edit
scope.fillJobTemplate = function(){
// id = id || $rootScope.copy.id;
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl + id);
Rest.get()
.success(function (data) {
scope.job_template_obj = data;
scope.name = data.name;
var fld, i;
for (fld in form.fields) {
if (fld !== 'variables' && fld !== 'survey' && data[fld] !== null && data[fld] !== undefined) {
if (form.fields[fld].type === 'select') {
if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) {
for (i = 0; i < scope[fld + '_options'].length; i++) {
if (data[fld] === scope[fld + '_options'][i].value) {
scope[fld] = scope[fld + '_options'][i];
}
}
} else {
scope[fld] = data[fld];
}
} else {
scope[fld] = data[fld];
if(!Empty(data.summary_fields.survey)) {
scope.survey_exists = true;
}
}
master[fld] = scope[fld];
}
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
scope.variables = ParseVariableString(data.extra_vars);
master.variables = scope.variables;
}
if (form.fields[fld].type === 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
if (form.fields[fld].type === 'checkbox_group') {
for(var j=0; j<form.fields[fld].fields.length; j++) {
scope[form.fields[fld].fields[j].name] = data[form.fields[fld].fields[j].name];
}
}
}
Wait('stop');
scope.url = data.url;
scope.survey_enabled = data.survey_enabled;
scope.ask_variables_on_launch = (data.ask_variables_on_launch) ? true : false;
master.ask_variables_on_launch = scope.ask_variables_on_launch;
scope.ask_limit_on_launch = (data.ask_limit_on_launch) ? true : false;
master.ask_limit_on_launch = scope.ask_limit_on_launch;
scope.ask_tags_on_launch = (data.ask_tags_on_launch) ? true : false;
master.ask_tags_on_launch = scope.ask_tags_on_launch;
scope.ask_skip_tags_on_launch = (data.ask_skip_tags_on_launch) ? true : false;
master.ask_skip_tags_on_launch = scope.ask_skip_tags_on_launch;
scope.ask_job_type_on_launch = (data.ask_job_type_on_launch) ? true : false;
master.ask_job_type_on_launch = scope.ask_job_type_on_launch;
scope.ask_inventory_on_launch = (data.ask_inventory_on_launch) ? true : false;
master.ask_inventory_on_launch = scope.ask_inventory_on_launch;
scope.ask_credential_on_launch = (data.ask_credential_on_launch) ? true : false;
master.ask_credential_on_launch = scope.ask_credential_on_launch;
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;
scope.can_edit = data.summary_fields.user_capabilities.edit;
if (scope.job_type.value === "scan" && (!scope.project || scope.project === "") && (!scope.playbook || scope.playbook === "")) {
scope.resetProjectToDefault();
}
scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to retrieve job template: ' + $stateParams.id + '. GET status: ' + status
});
});
};
};
}]);

View File

@@ -1,308 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Jobs
* @description routines shared by job related controllers
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers',
'JobSubmissionHelper', 'StandardOutHelper', 'AdhocHelper', listGenerator.name])
.factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', 'RelaunchAdhoc',
function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) {
return function(params) {
var scope = params.scope,
id = params.id,
type = params.type,
name = params.name;
if (type === 'inventory_update') {
RelaunchInventory({ scope: scope, id: id});
}
else if (type === 'ad_hoc_command') {
RelaunchAdhoc({ scope: scope, id: id, name: name });
}
else if (type === 'job' || type === 'system_job' || type === 'workflow_job') {
RelaunchPlaybook({ scope: scope, id: id, name: name, job_type: type });
}
else if (type === 'project_update') {
RelaunchSCM({ scope: scope, id: id });
}
};
}
])
.factory('JobStatusToolTip', [
function () {
return function (status) {
var toolTip;
switch (status) {
case 'successful':
case 'success':
toolTip = 'There were no failed tasks.';
break;
case 'failed':
toolTip = 'Some tasks encountered errors.';
break;
case 'canceled':
toolTip = 'Stopped by user request.';
break;
case 'new':
toolTip = 'In queue, waiting on task manager.';
break;
case 'waiting':
toolTip = 'SCM Update or Inventory Update is executing.';
break;
case 'pending':
toolTip = 'Not in queue, waiting on task manager.';
break;
case 'running':
toolTip = 'Playbook tasks executing.';
break;
}
return toolTip;
};
}
])
.factory('JobsListUpdate', [function() {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope,
list = params.list;
scope[list.name].forEach(function(item, item_idx) {
var fld, field,
itm = scope[list.name][item_idx];
//if (item.type === 'inventory_update') {
// itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,'');
//}
// Set the item type label
if (list.fields.type) {
parent_scope.type_choices.forEach(function(choice) {
if (choice.value === item.type) {
itm.type_label = choice.label;
}
});
}
// Set the job status label
parent_scope.status_choices.forEach(function(status) {
if (status.value === item.status) {
itm.status_label = status.label;
}
});
if (list.name === 'completed_jobs' || list.name === 'running_jobs') {
itm.status_tip = itm.status_label + '. Click for details.';
}
else if (list.name === 'queued_jobs') {
itm.status_tip = 'Pending';
}
// Copy summary_field values
for (field in list.fields) {
fld = list.fields[field];
if (fld.sourceModel) {
if (itm.summary_fields[fld.sourceModel]) {
itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField];
}
}
}
});
};
}])
.factory('DeleteJob', ['$state', 'Find', 'GetBasePath', 'Rest', 'Wait',
'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n',
function($state, Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert,
$filter, i18n){
return function(params) {
var scope = params.scope,
id = params.id,
job = params.job,
callback = params.callback,
action, jobs, url, action_label, hdr;
if (!job) {
if (scope.completed_jobs) {
jobs = scope.completed_jobs;
}
else if (scope.running_jobs) {
jobs = scope.running_jobs;
}
else if (scope.queued_jobs) {
jobs = scope.queued_jobs;
}
else if (scope.all_jobs) {
jobs = scope.all_jobs;
}
else if (scope.jobs) {
jobs = scope.jobs;
}
job = Find({list: jobs, key: 'id', val: id });
}
if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') {
url = job.related.cancel;
action_label = 'cancel';
hdr = i18n._('Cancel');
} else {
url = job.url;
action_label = 'delete';
hdr = i18n._('Delete');
}
action = function () {
Wait('start');
Rest.setUrl(url);
if (action_label === 'cancel') {
Rest.post()
.success(function () {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.error(function(obj, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
// ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. POST returned status: ' + status });
});
} else {
Rest.destroy()
.success(function () {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.error(function (obj, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
//ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. DELETE returned status: ' + status });
});
}
};
if (scope.removeCancelNotAllowed) {
scope.removeCancelNotAllowed();
}
scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() {
Wait('stop');
Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info');
});
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
var cancelBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Submit the request to cancel?") + "</div>";
var deleteBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to delete the job below?") + "</div><div class=\"Prompt-bodyTarget\">#" + id + " " + $filter('sanitize')(job.name) + "</div>";
Prompt({
hdr: hdr,
body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody,
action: action,
actionText: (action_label === 'cancel' || job.status === 'new') ? "OK" : "DELETE"
});
});
if (action_label === 'cancel') {
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.can_cancel) {
scope.$emit('CancelJob');
}
else {
scope.$emit('CancelNotAllowed');
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. GET returned: ' + status });
});
}
else {
scope.$emit('CancelJob');
}
};
}])
.factory('RelaunchInventory', ['Find', 'Wait', 'Rest', 'InventoryUpdate', 'ProcessErrors', 'GetBasePath',
function(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('inventory_sources') + id + '/';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
InventoryUpdate({
scope: scope,
url: data.related.update,
group_name: data.summary_fields.group.name,
group_source: data.source,
tree_id: null,
group_id: data.group
});
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
url + ' GET returned: ' + status });
});
};
}])
.factory('RelaunchPlaybook', ['InitiatePlaybookRun', function(InitiatePlaybookRun) {
return function(params) {
var scope = params.scope,
id = params.id,
job_type = params.job_type;
InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type });
};
}])
.factory('RelaunchSCM', ['ProjectUpdate', function(ProjectUpdate) {
return function(params) {
var scope = params.scope,
id = params.id;
ProjectUpdate({ scope: scope, project_id: id });
};
}])
.factory('RelaunchAdhoc', ['AdhocRun', function(AdhocRun) {
return function(params) {
var scope = params.scope,
id = params.id;
AdhocRun({ scope: scope, project_id: id, relaunch: true });
};
}]);

View File

@@ -1,108 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Parse
* @description
* Show the CodeMirror variable editor and allow
* toggle between JSON and YAML
*
*/
import 'codemirror/lib/codemirror.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/yaml/yaml.js';
import 'codemirror/addon/lint/lint.js';
import 'angular-codemirror/lib/yaml-lint.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/selection/active-line.js';
export default
angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule'])
.factory('ParseTypeChange', ['Alert', 'AngularCodeMirror',
function (Alert, AngularCodeMirror) {
return function (params) {
var scope = params.scope,
field_id = params.field_id,
fld = (params.variable) ? params.variable : 'variables',
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
onReady = params.onReady,
onChange = params.onChange,
readOnly = params.readOnly;
function removeField(fld) {
//set our model to the last change in CodeMirror and then destroy CodeMirror
scope[fld] = scope[fld + 'codeMirror'].getValue();
$('#cm-' + fld + '-container > .CodeMirror').empty().remove();
}
function createField(onChange, onReady, fld) {
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes);
scope[fld + 'codeMirror'].showTextArea({
scope: scope,
model: fld,
element: field_id,
lineNumbers: true,
mode: scope[pfld],
onReady: onReady,
onChange: onChange
});
}
// Hide the textarea and show a CodeMirror editor
createField(onChange, onReady, fld);
// Toggle displayed variable string between JSON and YAML
scope.parseTypeChange = function(model, fld) {
var json_obj;
if (scope[model] === 'json') {
// converting yaml to json
try {
removeField(fld);
json_obj = jsyaml.load(scope[fld]);
if ($.isEmptyObject(json_obj)) {
scope[fld] = "{}";
}
else {
scope[fld] = JSON.stringify(json_obj, null, " ");
}
createField(onReady, onChange, fld);
}
catch (e) {
Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message);
setTimeout( function() { scope.$apply( function() { scope[model] = 'yaml'; createField(onReady, onChange, fld); }); }, 500);
}
}
else {
// convert json to yaml
try {
removeField(fld);
json_obj = JSON.parse(scope[fld]);
if ($.isEmptyObject(json_obj)) {
scope[fld] = '---';
}
else {
scope[fld] = jsyaml.safeDump(json_obj);
}
createField(onReady, onChange, fld);
}
catch (e) {
Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message);
setTimeout( function() { scope.$apply( function() { scope[model] = 'json'; createField(onReady, onChange, fld); }); }, 500 );
}
}
};
};
}
]);

View File

@@ -1,91 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ProjectPath
* @description
* Use GetProjectPath({ scope: <scope>, master: <master obj> }) to
* load scope.project_local_paths (array of options for drop-down) and
* scope.base_dir (readonly field).
*
*/
export default
angular.module('ProjectPathHelper', ['RestServices', 'Utilities'])
.factory('GetProjectPath', ['Alert', 'Rest', 'GetBasePath', 'ProcessErrors',
function (Alert, Rest, GetBasePath, ProcessErrors) {
return function (params) {
var scope = params.scope,
master = params.master;
function arraySort(data) {
//Sort nodes by name
var i, j, names = [],
newData = [];
for (i = 0; i < data.length; i++) {
names.push(data[i].value);
}
names.sort();
for (j = 0; j < names.length; j++) {
for (i = 0; i < data.length; i++) {
if (data[i].value === names[j]) {
newData.push(data[i]);
}
}
}
return newData;
}
scope.showMissingPlaybooksAlert = false;
Rest.setUrl(GetBasePath('config'));
Rest.get()
.success(function (data) {
var opts = [], i;
if (data.project_local_paths) {
for (i = 0; i < data.project_local_paths.length; i++) {
opts.push({
label: data.project_local_paths[i],
value: data.project_local_paths[i]
});
}
}
if (scope.local_path) {
// List only includes paths not assigned to projects, so add the
// path assigned to the current project.
opts.push({
label: scope.local_path,
value: scope.local_path
});
}
scope.project_local_paths = arraySort(opts);
if (scope.local_path) {
for (i = 0; scope.project_local_paths.length; i++) {
if (scope.project_local_paths[i].value === scope.local_path) {
scope.local_path = scope.project_local_paths[i];
break;
}
}
}
scope.base_dir = data.project_base_dir;
master.local_path = scope.local_path;
master.base_dir = scope.base_dir; // Keep in master object so that it doesn't get
// wiped out on form reset.
if (opts.length === 0) {
// trigger display of alert block when scm_type == manual
scope.showMissingPlaybooksAlert = true;
}
scope.$emit('pathsReady');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to access API config. GET status: ' + status });
});
};
}
]);

View File

@@ -1,84 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Projects
* @description
* Use GetProjectPath({ scope: <scope>, master: <master obj> }) to
* load scope.project_local_paths (array of options for drop-down) and
* scope.base_dir (readonly field).
*
*/
export default
angular.module('ProjectsHelper', ['RestServices', 'Utilities', 'ProjectStatusDefinition', 'ProjectFormDefinition'])
.factory('GetProjectIcon', [ function() {
return function(status) {
var result = '';
switch (status) {
case 'n/a':
case 'ok':
case 'never updated':
result = 'none';
break;
case 'pending':
case 'waiting':
case 'new':
result = 'none';
break;
case 'updating':
case 'running':
result = 'running';
break;
case 'successful':
result = 'success';
break;
case 'failed':
case 'missing':
case 'canceled':
result = 'error';
}
return result;
};
}])
.factory('GetProjectToolTip', ['i18n', function(i18n) {
return function(status) {
var result = '';
switch (status) {
case 'n/a':
case 'ok':
case 'never updated':
result = i18n._('No SCM updates have run for this project');
break;
case 'pending':
case 'waiting':
case 'new':
result = i18n._('Queued. Click for details');
break;
case 'updating':
case 'running':
result = i18n._('Running! Click for details');
break;
case 'successful':
result = i18n._('Success! Click for details');
break;
case 'failed':
result = i18n._('Failed. Click for details');
break;
case 'missing':
result = i18n._('Missing. Click for details');
break;
case 'canceled':
result = i18n._('Canceled. Click for details');
break;
}
return result;
};
}]);

View File

@@ -1,506 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Schedules
* @description
* Schedules Helper
*
* Display the scheduler widget in a dialog
*
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', listGenerator.name, 'ModalDialog',
'GeneratorHelpers'])
.factory('EditSchedule', ['SchedulerInit', '$rootScope', 'Wait', 'Rest',
'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state',
function(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors,
GetBasePath, SchedulePost, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
schedule, scheduler,
url = GetBasePath('schedules') + id + '/';
delete scope.isFactCleanup;
delete scope.cleanupJob;
function setGranularity(){
var a,b, prompt_for_days,
keep_unit,
granularity,
granularity_keep_unit;
if(scope.cleanupJob){
scope.schedulerPurgeDays = Number(schedule.extra_data.days);
// scope.scheduler_form.schedulerPurgeDays.$setViewValue( Number(schedule.extra_data.days));
}
else if(scope.isFactCleanup){
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
// the API returns something like 20w or 1y
a = schedule.extra_data.older_than; // "20y"
b = schedule.extra_data.granularity; // "1w"
prompt_for_days = Number(_.initial(a,1).join('')); // 20
keep_unit = _.last(a); // "y"
granularity = Number(_.initial(b,1).join('')); // 1
granularity_keep_unit = _.last(b); // "w"
scope.keep_amount = prompt_for_days;
scope.granularity_keep_amount = granularity;
scope.keep_unit = _.find(scope.keep_unit_choices, function(i){
return i.value === keep_unit;
});
scope.granularity_keep_unit =_.find(scope.granularity_keep_unit_choices, function(i){
return i.value === granularity_keep_unit;
});
}
}
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function() {
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
if (!/DTSTART/.test(schedule.rrule)) {
schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z');
}
schedule.rrule = schedule.rrule.replace(/ RRULE:/,';');
schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART=');
scope.$on("htmlDetailReady", function() {
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
if(scope.isFactCleanup || scope.cleanupJob){
setGranularity();
}
});
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^");
});
scope.saveSchedule = function() {
schedule.extra_data = scope.extraVars;
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'edit',
schedule: schedule
});
};
Wait('start');
// Get the existing record
Rest.setUrl(url);
Rest.get()
.success(function(data) {
schedule = data;
try {
schedule.extra_data = JSON.parse(schedule.extra_data);
} catch(e) {
// do nothing
}
scope.extraVars = data.extra_data === '' ? '---' : '---\n' + jsyaml.safeDump(data.extra_data);
if(schedule.extra_data.hasOwnProperty('granularity')){
scope.isFactCleanup = true;
}
if (schedule.extra_data.hasOwnProperty('days')){
scope.cleanupJob = true;
}
scope.schedule_obj = data;
scope.$emit('ScheduleFound');
})
.error(function(data,status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}])
.factory('AddSchedule', ['$location', '$rootScope', '$stateParams',
'SchedulerInit', 'Wait', 'GetBasePath', 'Empty', 'SchedulePost', '$state', 'Rest', 'ProcessErrors',
function($location, $rootScope, $stateParams, SchedulerInit,
Wait, GetBasePath, Empty, SchedulePost, $state, Rest,
ProcessErrors) {
return function(params) {
var scope = params.scope,
callback= params.callback,
base = params.base || $location.path().replace(/^\//, '').split('/')[0],
url = params.url || null,
scheduler,
job_type;
job_type = scope.parentObject.job_type;
if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !url) {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
}
else if(base === "inventories"){
if (!params.url){
url = GetBasePath('groups') + $stateParams.id + '/';
Rest.setUrl(url);
Rest.get().
then(function (data) {
url = data.data.related.inventory_source + 'schedules/';
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory group info. GET returned status: ' +
response.status
});
});
}
else {
url = params.url;
}
}
else if (base === 'system_job_templates') {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
if(job_type === "cleanup_facts"){
scope.isFactCleanup = true;
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30);
scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1);
scope.keep_unit = scope.keep_unit_choices[0];
scope.granularity_keep_unit = scope.granularity_keep_unit_choices[1];
}
else {
scope.cleanupJob = true;
}
}
Wait('start');
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
if(scope.schedulerUTCTime) {
// The UTC time is already set
scope.processSchedulerEndDt();
}
else {
// We need to wait for it to be set by angular-scheduler because the following function depends
// on it
var schedulerUTCTimeWatcher = scope.$watch('schedulerUTCTime', function(newVal) {
if(newVal) {
// Remove the watcher
schedulerUTCTimeWatcher();
scope.processSchedulerEndDt();
}
});
}
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
scheduler.clear();
scope.$on("htmlDetailReady", function() {
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^", null, {reload: true});
});
scope.saveSchedule = function() {
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'add'
});
};
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
if ($(e.target).text() === 'Details') {
if (!scheduler.isValid()) {
$('#scheduler-tabs a:first').tab('show');
}
}
});
};
}])
.factory('SchedulePost', ['Rest', 'ProcessErrors', 'RRuleToAPI', 'Wait',
function(Rest, ProcessErrors, RRuleToAPI, Wait) {
return function(params) {
var scope = params.scope,
url = params.url,
scheduler = params.scheduler,
mode = params.mode,
schedule = (params.schedule) ? params.schedule : {},
callback = params.callback,
newSchedule, rrule, extra_vars;
if (scheduler.isValid()) {
Wait('start');
newSchedule = scheduler.getValue();
rrule = scheduler.getRRule();
schedule.name = newSchedule.name;
schedule.rrule = RRuleToAPI(rrule.toString());
schedule.description = (/error/.test(rrule.toText())) ? '' : rrule.toText();
if (scope.isFactCleanup) {
extra_vars = {
"older_than": scope.scheduler_form.keep_amount.$viewValue + scope.scheduler_form.keep_unit.$viewValue.value,
"granularity": scope.scheduler_form.granularity_keep_amount.$viewValue + scope.scheduler_form.granularity_keep_unit.$viewValue.value
};
schedule.extra_data = JSON.stringify(extra_vars);
} else if (scope.cleanupJob) {
extra_vars = {
"days" : scope.scheduler_form.schedulerPurgeDays.$viewValue
};
schedule.extra_data = JSON.stringify(extra_vars);
}
else if(scope.extraVars){
schedule.extra_data = scope.parseType === 'yaml' ?
(scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars;
}
Rest.setUrl(url);
if (mode === 'add') {
Rest.post(schedule)
.success(function(){
if (callback) {
scope.$emit(callback);
}
else {
Wait('stop');
}
})
.error(function(data, status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status });
});
}
else {
Rest.put(schedule)
.success(function(){
if (callback) {
scope.$emit(callback, schedule);
}
else {
Wait('stop');
}
})
.error(function(data, status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status });
});
}
}
else {
return false;
}
};
}])
/**
* Flip a schedule's enable flag
*
* ToggleSchedule({
* scope: scope,
* id: schedule.id to update
* callback: scope.$emit label to call when update completes
* });
*
*/
.factory('ToggleSchedule', ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', '$state',
function(Wait, GetBasePath, ProcessErrors, Rest, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('schedules') + id +'/';
// Perform the update
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function(e, data) {
data.enabled = (data.enabled) ? false : true;
Rest.put(data)
.success( function() {
Wait('stop');
$state.go('.', null, {reload: true});
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status });
});
});
Wait('start');
// Get the schedule
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.$emit('ScheduleFound', data);
})
.error(function(data,status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}])
/**
* Delete a schedule. Prompts user to confirm delete
*
* DeleteSchedule({
* scope: $scope containing list of schedules
* id: id of schedule to delete
* callback: $scope.$emit label to call when delete is completed
* })
*
*/
.factory('DeleteSchedule', ['GetBasePath','Rest', 'Wait', '$state',
'ProcessErrors', 'Prompt', 'Find', '$location', '$filter',
function(GetBasePath, Rest, Wait, $state, ProcessErrors, Prompt, Find,
$location, $filter) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
action, schedule, list, url, hdr;
if (scope.schedules) {
list = scope.schedules;
}
else if (scope.scheduled_jobs) {
list = scope.scheduled_jobs;
}
url = GetBasePath('schedules') + id + '/';
schedule = Find({list: list, key: 'id', val: id });
hdr = 'Delete Schedule';
action = function () {
Wait('start');
Rest.setUrl(url);
Rest.destroy()
.success(function () {
$('#prompt-modal').modal('hide');
scope.$emit(callback, id);
if (new RegExp('/' + id + '$').test($location.$$url)) {
$location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view
}
else{
$state.go('.', null, {reload: true});
}
})
.error(function (data, status) {
try {
$('#prompt-modal').modal('hide');
}
catch(e) {
// ignore
}
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. DELETE returned: ' + status });
});
};
Prompt({
hdr: hdr,
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the schedule below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(schedule.name) + '</div>',
action: action,
actionText: 'DELETE',
backdrop: false
});
};
}])
/**
* Convert rrule string to an API agreeable format
*
*/
.factory('RRuleToAPI', [ function() {
return function(rrule) {
var response;
response = rrule.replace(/(^.*(?=DTSTART))(DTSTART=.*?;)(.*$)/, function(str, p1, p2, p3) {
return p2.replace(/\;/,'').replace(/=/,':') + ' ' + 'RRULE:' + p1 + p3;
});
return response;
};
}]);

View File

@@ -1,177 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Selection
* @description
* SelectionHelper
* Used in list controllers where the list might also be used as a selection list.
*
* SelectionInit( {
* scope: <list scope>,
* list: <list object>
* })
*/
export default
angular.module('SelectionHelper', ['Utilities', 'RestServices'])
.factory('SelectionInit', ['Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'Wait',
function (Rest, Alert, ProcessErrors, ReturnToCaller, Wait) {
return function (params) {
var scope = params.scope,
list = params.list,
target_url = params.url,
returnToCaller = params.returnToCaller,
selected;
if (params.selected !== undefined) {
selected = params.selected;
} else {
selected = []; //array of selected row IDs
}
scope.formModalActionDisabled = true;
scope.disableSelectBtn = true;
// toggle row selection
scope['toggle_' + list.iterator] = function (id, ischeckbox) {
var i, j, found;
for (i = 0; i < scope[list.name].length; i++) {
if (scope[list.name][i].id === id) {
var control = scope[list.name][i];
if (ischeckbox && control.checked) {
scope[list.name][i].success_class = 'success';
// add selected object to the array
found = false;
for (j = 0; j < selected.length; j++) {
if (selected[j].id === id) {
found = true;
break;
}
}
if (!found) {
selected.push(scope[list.name][i]);
}
} else if (ischeckbox) {
scope[list.name][i].success_class = '';
// remove selected object from the array
for (j = 0; j < selected.length; j++) {
if (selected[j].id === id) {
selected.splice(j, 1);
break;
}
}
}
}
}
if (selected.length > 0) {
scope.formModalActionDisabled = false;
scope.disableSelectBtn = false;
} else {
scope.formModalActionDisabled = true;
scope.disableSelectBtn = true;
}
};
// Add the selections
scope.finishSelection = function () {
Rest.setUrl(target_url);
var queue = [], j;
scope.formModalActionDisabled = true;
scope.disableSelectBtn = true;
Wait('start');
function finished() {
selected = [];
if (returnToCaller !== undefined) {
ReturnToCaller(returnToCaller);
} else {
$('#form-modal').modal('hide');
scope.$emit('modalClosed');
}
}
function postIt(data) {
Rest.post(data)
.success(function (data, status) {
queue.push({ result: 'success', data: data, status: status });
scope.$emit('callFinished');
})
.error(function (data, status, headers) {
queue.push({ result: 'error', data: data, status: status, headers: headers });
scope.$emit('callFinished');
});
}
if (scope.callFinishedRemove) {
scope.callFinishedRemove();
}
scope.callFinishedRemove = scope.$on('callFinished', function () {
// We call the API for each selected item. We need to hang out until all the api
// calls are finished.
var i, errors=0;
if (queue.length === selected.length) {
Wait('stop');
for (i = 0; i < queue.length; i++) {
if (queue[i].result === 'error') {
ProcessErrors(scope, queue[i].data, queue[i].status, null, { hdr: 'POST Failure',
msg: 'Failed to add ' + list.iterator + '. POST returned status: ' + queue[i].status });
errors++;
}
}
if (errors === 0) {
finished();
}
}
});
if (selected.length > 0) {
for (j = 0; j < selected.length; j++) {
postIt(selected[j]);
}
} else {
finished();
}
};
scope.formModalAction = scope.finishSelection;
// Initialize our data set after a refresh (page change or search)
if (scope.SelectPostRefreshRemove) {
scope.SelectPostRefreshRemove();
}
scope.SelectPostRefreshRemove = scope.$on('PostRefresh', function () {
var i, j, found;
if (scope[list.name]) {
for (i = 0; i < scope[list.name].length; i++) {
found = false;
for (j = 0; j < selected.length; j++) {
if (selected[j].id === scope[list.name][i].id) {
found = true;
break;
}
}
if (found) {
scope[list.name][i].checked = '1';
scope[list.name][i].success_class = 'success';
} else {
scope[list.name][i].checked = '0';
scope[list.name][i].success_class = '';
}
}
}
});
};
}
]);

View File

@@ -1,46 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Users
* @description
* UserHelper
* Routines shared amongst the user controllers
*
*/
export default
angular.module('UserHelper', ['UserFormDefinition'])
.factory('ResetForm', ['UserForm',
function (UserForm) {
return function () {
// Restore form to default conditions. Run before applying LDAP configuration.
// LDAP may manage some or all of these fields in which case the user cannot
// make changes to their values in AWX.
UserForm.fields.first_name.readonly = false;
UserForm.fields.first_name.editRequired = true;
UserForm.fields.last_name.readonly = false;
UserForm.fields.last_name.editRequired = true;
UserForm.fields.email.readonly = false;
UserForm.fields.email.editRequired = true;
UserForm.fields.organization.awRequiredWhen = {
reqExpression: "orgrequired",
init: true
};
UserForm.fields.organization.readonly = false;
UserForm.fields.username.awRequiredWhen = {
reqExpression: "not_ldap_user",
init: true
};
UserForm.fields.username.readonly = false;
UserForm.fields.password.editRequired = false;
UserForm.fields.password.addRrequired = true;
};
}
]);

View File

@@ -1,180 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Variables
* @description
* VariablesHelper
*
*
*/
export default
angular.module('VariablesHelper', ['Utilities'])
/**
variables: string containing YAML or JSON | a JSON object.
If JSON string, convert to JSON object and run through jsyaml.safeDump() to create a YAML document. If YAML,
will attempt to load via jsyaml.safeLoad() and return a YAML document using jsyaml.safeDump(). In all cases
a YAML document is returned.
**/
.factory('ParseVariableString', ['$log', 'ProcessErrors', 'SortVariables', function ($log, ProcessErrors, SortVariables) {
return function (variables) {
var result = "---", json_obj;
if (typeof variables === 'string') {
if (variables === "{}" || variables === "null" || variables === "" || variables === "\"\"") {
// String is empty, return ---
} else {
try {
json_obj = JSON.parse(variables);
json_obj = SortVariables(json_obj);
result = jsyaml.safeDump(json_obj);
}
catch (e) {
$log.debug('Attempt to parse extra_vars as JSON failed. Check that the variables parse as yaml. Set the raw string as the result.');
try {
// do safeLoad, which well error if not valid yaml
json_obj = jsyaml.safeLoad(variables);
// but just send the variables
result = variables;
}
catch(e2) {
ProcessErrors(null, variables, e2.message, null, { hdr: 'Error!',
msg: 'Attempts to parse variables as JSON and YAML failed. Last attempt returned: ' + e2.message });
}
}
}
}
else {
if ($.isEmptyObject(variables) || variables === null) {
// Empty object, return ---
}
else {
// convert object to yaml
try {
json_obj = SortVariables(variables);
result = jsyaml.safeDump(json_obj);
// result = variables;
}
catch(e3) {
ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!',
msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message });
}
}
}
return result;
};
}])
/**
parseType: 'json' | 'yaml'
variables: string containing JSON or YAML
stringify: optional, boolean
Parse the given string according to the parseType to a JSON object. If stringify true,
stringify the object and return the string. Otherwise, return the JSON object.
**/
.factory('ToJSON', ['$log', 'ProcessErrors', function($log, ProcessErrors) {
return function(parseType, variables, stringify, reviver) {
var json_data,
result,
tmp;
// bracketVar,
// key,
// lines, i, newVars = [];
if (parseType === 'json') {
try {
// perform a check to see if the user cleared the field completly
if(variables.trim() === "" || variables.trim() === "{" || variables.trim() === "}" ){
variables = "{}";
}
//parse a JSON string
if (reviver) {
json_data = JSON.parse(variables, reviver);
}
else {
json_data = JSON.parse(variables);
}
}
catch(e) {
json_data = {};
$log.error('Failed to parse JSON string. Parser returned: ' + e.message);
ProcessErrors(null, variables, e.message, null, { hdr: 'Error!',
msg: 'Failed to parse JSON string. Parser returned: ' + e.message });
throw 'Parse error. Failed to parse variables.';
}
} else {
try {
if(variables.trim() === "" || variables.trim() === "-" || variables.trim() === "--"){
variables = '---';
}
json_data = jsyaml.safeLoad(variables);
if(json_data!==null){
// unparsing just to make sure no weird characters are included.
tmp = jsyaml.dump(json_data);
if(tmp.indexOf('[object Object]')!==-1){
throw "Failed to parse YAML string. Parser returned' + key + ' : ' +value + '.' ";
}
}
}
catch(e) {
json_data = undefined; // {};
// $log.error('Failed to parse YAML string. Parser returned undefined');
ProcessErrors(null, variables, e.message, null, { hdr: 'Error!',
msg: 'Failed to parse YAML string. Parser returned undefined'});
}
}
// Make sure our JSON is actually an object
if (typeof json_data !== 'object') {
ProcessErrors(null, variables, null, null, { hdr: 'Error!',
msg: 'Failed to parse variables. Attempted to parse ' + parseType + '. Parser did not return an object.' });
// setTimeout( function() {
throw 'Parse error. Failed to parse variables.';
// }, 1000);
}
result = json_data;
if (stringify) {
if(json_data === undefined){
result = undefined;
}
else if ($.isEmptyObject(json_data)) {
result = "";
} else {
// utilize the parsing to get here
// but send the raw variable string
result = variables;
}
}
return result;
};
}])
.factory('SortVariables', [ function() {
return function(variableObj) {
var newObj;
function sortIt(objToSort) {
var i,
keys = Object.keys(objToSort),
newObj = {};
keys = keys.sort();
for (i=0; i < keys.length; i++) {
if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) {
newObj[keys[i]] = sortIt(objToSort[keys[i]]);
}
else {
newObj[keys[i]] = objToSort[keys[i]];
}
}
return newObj;
}
newObj = sortIt(variableObj);
return newObj;
};
}]);

View File

@@ -1,94 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:api-defaults
* @description this could use more discussion
*/
export default
angular.module('APIDefaults', ['RestServices', 'Utilities'])
.factory('GetAPIDefaults', ['Alert', 'Rest', '$rootScope',
function (Alert, Rest, $rootScope) {
return function (key) {
//Reload a related collection on pagination or search change
var result = {}, cnt = 0, url;
function lookup(key) {
var id, result = {};
for (id in $rootScope.apiDefaults) {
if (id === key || id.iterator === key) {
result[id] = $rootScope.apiDefaults[id];
break;
}
}
return result;
}
function wait() {
if ($.isEmptyObject(result) && cnt < 5) {
cnt++;
setTimeout(1000, wait());
} else if (result.status === 'success') {
return lookup(key);
}
}
if ($rootScope.apiDefaults === null || $rootScope.apiDefaults === undefined) {
url = '/api/v1/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
var id, defaults = data;
for (id in defaults) {
switch (id) {
case 'organizations':
defaults[id].iterator = 'organization';
break;
case 'jobs':
defaults[id].iterator = 'job';
break;
case 'users':
defaults[id].iterator = 'user';
break;
case 'teams':
defaults[id].iterator = 'team';
break;
case 'hosts':
defaults[id].iterator = 'host';
break;
case 'groups':
defaults[id].iterator = 'group';
break;
case 'projects':
defaults[id].iterator = 'project';
break;
case 'inventories':
defaults[id].iterator = 'inventory';
break;
}
}
$rootScope.apiDefaults = defaults;
result = {
status: 'success'
};
})
.error(function (data, status) {
result = {
status: 'error',
msg: 'Call to ' + url + ' failed. GET returned status: ' + status
};
});
return wait();
} else {
return lookup(key);
}
};
}
]);

View File

@@ -1,84 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Inventory
* @description InventoryHelper
* Routines for building the tree. Everything related to the tree is here except
* for the menu piece. The routine for building the menu is in InventoriesEdit controller
* (controllers/Inventories.js)
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('InventoryHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name,
'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'VariablesHelper',
])
.factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'OrganizationList',
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON',
function (InventoryForm, Rest, Alert, ProcessErrors, OrganizationList, GetBasePath, ParseTypeChange, Wait,
ToJSON) {
return function (params) {
// Save inventory property modifications
var scope = params.scope,
form = InventoryForm,
defaultUrl = GetBasePath('inventory'),
fld, json_data, data;
Wait('start');
// Make sure we have valid variable data
json_data = ToJSON(scope.parseType, scope.variables);
data = {};
for (fld in form.fields) {
if (fld !== 'variables') {
if (form.fields[fld].realName) {
data[form.fields[fld].realName] = scope[fld];
} else {
data[fld] = scope[fld];
}
}
}
if (scope.removeUpdateInventoryVariables) {
scope.removeUpdateInventoryVariables();
}
scope.removeUpdateInventoryVariables = scope.$on('UpdateInventoryVariables', function(e, data) {
Rest.setUrl(data.related.variable_data);
Rest.put(json_data)
.success(function () {
Wait('stop');
scope.$emit('InventorySaved');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to update inventory varaibles. PUT returned status: ' + status
});
});
});
Rest.setUrl(defaultUrl + scope.inventory_id + '/');
Rest.put(data)
.success(function (data) {
if (scope.variables) {
scope.$emit('UpdateInventoryVariables', data);
} else {
scope.$emit('InventorySaved');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to update inventory. PUT returned status: ' + status });
});
};
}
]);

View File

@@ -1,46 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:md5
* @description
* Run md5Setup({ scope: , master:, check_field:, default_val: })
* to initialize md5 fields (checkbox and text field).
* discussion
*/
export default
angular.module('md5Helper', ['RestServices', 'Utilities', require('angular-md5')])
.factory('md5Setup', ['md5', function (md5) {
return function (params) {
var scope = params.scope,
master = params.master,
check_field = params.check_field,
default_val = params.default_val;
scope[check_field] = default_val;
master[check_field] = default_val;
scope.genMD5 = function (fld) {
var now = new Date();
scope[fld] = md5.createHash('AnsibleWorks' + now.getTime());
scope.$emit('NewMD5Generated');
};
scope.toggleCallback = function (fld) {
if (scope.allow_callbacks === false) {
scope[fld] = '';
}
};
scope.selectAll = function (fld) {
$('input[name="' + fld + '"]').focus().select();
};
};
}]);

View File

@@ -1,73 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:teams
* @description
* TeamHelper
* Routines shared amongst the team controllers
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name
])
.factory('SetTeamListeners', ['Alert', 'Rest',
function (Alert, Rest) {
return function (params) {
var scope = params.scope,
set = params.set;
// Listeners to perform lookups after main inventory list loads
scope.$on('TeamResultFound', function (e, results, lookup_results) {
var i, j, key, property;
if (lookup_results.length === results.length) {
key = 'organization';
property = 'organization_name';
for (i = 0; i < results.length; i++) {
for (j = 0; j < lookup_results.length; j++) {
if (results[i][key] === lookup_results[j].id) {
results[i][property] = lookup_results[j].value;
}
}
}
scope[set] = results;
}
});
scope.$on('TeamRefreshFinished', function (e, results) {
// Loop through the result set (sent to us by the search helper) and
// lookup the id and name of each organization. After each lookup
// completes, call resultFound.
var i, lookup_results = [], url;
function getOrganization(url) {
Rest.setUrl(url);
Rest.get()
.success(function (data) {
lookup_results.push({ id: data.id, value: data.name });
scope.$emit('TeamResultFound', results, lookup_results);
})
.error(function () {
lookup_results.push({ id: 'error' });
scope.$emit('TeamResultFound', results, lookup_results);
});
}
for (i = 0; i < results.length; i++) {
url = '/api/v1/organizations/' + results[i].organization + '/';
getOrganization(url);
}
});
};
}
]);

View File

@@ -1,6 +1,5 @@
import JobTemplatesListDirective from './job-templates-list.directive'; import JobTemplatesListDirective from './job-templates-list.directive';
import systemStatus from '../../../../smart-status/main'; import systemStatus from '../../../../smart-status/main';
import jobSubmissionHelper from '../../../../helpers/JobSubmission';
export default angular.module('JobTemplatesList', [systemStatus.name, jobSubmissionHelper.name]) export default angular.module('JobTemplatesList', [systemStatus.name])
.directive('jobTemplatesList', JobTemplatesListDirective); .directive('jobTemplatesList', JobTemplatesListDirective);

View File

@@ -0,0 +1,33 @@
export default
function GetHostsStatusMsg() {
return function(params) {
var active_failures = params.active_failures,
total_hosts = params.total_hosts,
tip, failures, html_class;
// Return values for use on host status indicator
if (active_failures > 0) {
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + ' with failed jobs.';
html_class = 'error';
failures = true;
} else {
failures = false;
if (total_hosts === 0) {
// no hosts
tip = "Contains 0 hosts.";
html_class = 'none';
} else {
// many hosts with 0 failures
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. No job failures';
html_class = 'success';
}
}
return {
tooltip: tip,
failures: failures,
'class': html_class
};
};
}

View File

@@ -0,0 +1,37 @@
export default
function GetSourceTypeOptions(Rest, ProcessErrors, GetBasePath) {
return function(params) {
var scope = params.scope,
variable = params.variable;
if (scope[variable] === undefined) {
scope[variable] = [];
Rest.setUrl(GetBasePath('inventory_sources'));
Rest.options()
.success(function (data) {
var i, choices = data.actions.GET.source.choices;
for (i = 0; i < choices.length; i++) {
if (choices[i][0] !== 'file') {
scope[variable].push({
label: choices[i][1],
value: choices[i][0]
});
}
}
scope.cloudCredentialRequired = false;
scope.$emit('sourceTypeOptionsReady');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status
});
});
}
};
}
GetSourceTypeOptions.$inject =
[ 'Rest',
'ProcessErrors',
'GetBasePath'
];

View File

@@ -0,0 +1,77 @@
export default
function GetSyncStatusMsg(Empty) {
return function(params) {
var status = params.status,
source = params.source,
has_inventory_sources = params.has_inventory_sources,
launch_class = '',
launch_tip = 'Start sync process',
schedule_tip = 'Schedule future inventory syncs',
stat, stat_class, status_tip;
stat = status;
stat_class = stat;
switch (status) {
case 'never updated':
stat = 'never';
stat_class = 'na';
status_tip = 'Sync not performed. Click <i class="fa fa-refresh"></i> to start it now.';
break;
case 'none':
case 'ok':
case '':
launch_class = 'btn-disabled';
stat = 'n/a';
stat_class = 'na';
status_tip = 'Cloud source not configured. Click <i class="fa fa-pencil"></i> to update.';
launch_tip = 'Cloud source not configured.';
break;
case 'canceled':
status_tip = 'Sync canceled. Click to view log.';
break;
case 'failed':
status_tip = 'Sync failed. Click to view log.';
break;
case 'successful':
status_tip = 'Sync completed. Click to view log.';
break;
case 'pending':
status_tip = 'Sync pending.';
launch_class = "btn-disabled";
launch_tip = "Sync pending";
break;
case 'updating':
case 'running':
launch_class = "btn-disabled";
launch_tip = "Sync running";
status_tip = "Sync running. Click to view log.";
break;
}
if (has_inventory_sources && Empty(source)) {
// parent has a source, therefore this group should not have a source
launch_class = "btn-disabled";
status_tip = 'Managed by an external cloud source.';
launch_tip = 'Can only be updated by running a sync on the parent group.';
}
if (has_inventory_sources === false && Empty(source)) {
launch_class = 'btn-disabled';
status_tip = 'Cloud source not configured. Click <i class="fa fa-pencil"></i> to update.';
launch_tip = 'Cloud source not configured.';
}
return {
"class": stat_class,
"tooltip": status_tip,
"status": stat,
"launch_class": launch_class,
"launch_tip": launch_tip,
"schedule_tip": schedule_tip
};
};
}
GetSyncStatusMsg.$inject =
[ 'Empty' ];

View File

@@ -0,0 +1,81 @@
export default
function GroupsCancelUpdate(Empty, Rest, ProcessErrors, Alert, Wait, Find) {
return function(params) {
var scope = params.scope,
id = params.id,
group = params.group;
if (scope.removeCancelUpdate) {
scope.removeCancelUpdate();
}
scope.removeCancelUpdate = scope.$on('CancelUpdate', function (e, url) {
// Cancel the update process
Rest.setUrl(url);
Rest.post()
.success(function () {
Wait('stop');
//Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' +
// 'Click the <i class="fa fa-refresh fa-lg"></i> button to monitor the status.', 'alert-info');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. POST status: ' + status
});
});
});
if (scope.removeCheckCancel) {
scope.removeCheckCancel();
}
scope.removeCheckCancel = scope.$on('CheckCancel', function (e, last_update, current_update) {
// Check that we have access to cancelling an update
var url = (current_update) ? current_update : last_update;
url += 'cancel/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
if (data.can_cancel) {
scope.$emit('CancelUpdate', url);
//} else {
// Wait('stop');
// Alert('Cancel Inventory Sync', 'The sync process completed. Click the <i class="fa fa-refresh fa-lg"></i> button to view ' +
// 'the latest status.', 'alert-info');
}
else {
Wait('stop');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET status: ' + status
});
});
});
// Cancel the update process
if (Empty(group)) {
group = Find({ list: scope.groups, key: 'id', val: id });
scope.selected_group_id = group.id;
}
if (group && (group.status === 'running' || group.status === 'pending')) {
// We found the group, and there is a running update
Wait('start');
Rest.setUrl(group.related.inventory_source);
Rest.get()
.success(function (data) {
scope.$emit('CheckCancel', data.related.last_update, data.related.current_update);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + group.related.inventory_source + ' failed. GET status: ' + status
});
});
}
};
}
GroupsCancelUpdate.$inject =
[ 'Empty', 'Rest', 'ProcessErrors',
'Alert', 'Wait', 'Find'
];

View File

@@ -0,0 +1,46 @@
export default
function ViewUpdateStatus($state, Rest, ProcessErrors, GetBasePath, Alert, Wait, Empty, Find) {
return function(params) {
var scope = params.scope,
group_id = params.group_id,
group = Find({ list: scope.groups, key: 'id', val: group_id });
if (scope.removeSourceReady) {
scope.removeSourceReady();
}
scope.removeSourceReady = scope.$on('SourceReady', function(e, source) {
// Get the ID from the correct summary field
var update_id = (source.summary_fields.current_update) ? source.summary_fields.current_update.id : source.summary_fields.last_update.id;
$state.go('inventorySyncStdout', {id: update_id});
});
if (group) {
if (Empty(group.source)) {
// do nothing
} else if (Empty(group.status) || group.status === "never updated") {
Alert('No Status Available', '<div>An inventory sync has not been performed for the selected group. Start the process by ' +
'clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
} else {
Wait('start');
Rest.setUrl(group.related.inventory_source);
Rest.get()
.success(function (data) {
scope.$emit('SourceReady', data);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source +
' GET returned status: ' + status });
});
}
}
};
}
ViewUpdateStatus.$inject =
[ '$state', 'Rest', 'ProcessErrors',
'GetBasePath', 'Alert', 'Wait', 'Empty', 'Find'
];

View File

@@ -6,8 +6,18 @@
import GroupAddController from './groups-add.controller'; import GroupAddController from './groups-add.controller';
import GroupEditController from './groups-edit.controller'; import GroupEditController from './groups-edit.controller';
import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory';
import GetSourceTypeOptions from './factories/get-source-type-options.factory';
import GetSyncStatusMsg from './factories/get-sync-status-msg.factory';
import GroupsCancelUpdate from './factories/groups-cancel-update.factory';
import ViewUpdateStatus from './factories/view-update-status.factory';
export default export default
angular.module('manageGroups', []) angular.module('manageGroups', [])
.factory('GetHostsStatusMsg', GetHostsStatusMsg)
.factory('GetSourceTypeOptions', GetSourceTypeOptions)
.factory('GetSyncStatusMsg', GetSyncStatusMsg)
.factory('GroupsCancelUpdate', GroupsCancelUpdate)
.factory('ViewUpdateStatus', ViewUpdateStatus)
.controller('GroupAddController', GroupAddController) .controller('GroupAddController', GroupAddController)
.controller('GroupEditController', GroupEditController); .controller('GroupEditController', GroupEditController);

View File

@@ -0,0 +1,13 @@
export default
function SetEnabledMsg() {
return function(host) {
if (host.has_inventory_sources) {
// Inventory sync managed, so not clickable
host.enabledToolTip = (host.enabled) ? 'Host is available' : 'Host is not available';
}
else {
// Clickable
host.enabledToolTip = (host.enabled) ? 'Host is available. Click to toggle.' : 'Host is not available. Click to toggle.';
}
};
}

View File

@@ -0,0 +1,102 @@
export default
function SetStatus($filter, SetEnabledMsg, Empty) {
return function(params) {
var scope = params.scope,
host = params.host,
i, html, title;
function ellipsis(a) {
if (a.length > 25) {
return a.substr(0,25) + '...';
}
return a;
}
function noRecentJobs() {
title = 'No job data';
html = "<p>No recent job data available for this host.</p>\n";
}
function setMsg(host) {
var j, job, jobs;
if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) {
if (host.has_active_failures === true) {
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
host.active_failures = 'error';
}
else {
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
host.active_failures = 'successful';
}
if (host.summary_fields.recent_jobs.length > 0) {
// build html table of job status info
jobs = host.summary_fields.recent_jobs.sort(
function(a,b) {
// reverse numerical order
return -1 * (a - b);
});
title = "Recent Jobs";
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>\n";
html += "<th>Status</th>\n";
html += "<th>Finished</th>\n";
html += "<th>Name</th>\n";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
for (j=0; j < jobs.length; j++) {
job = jobs[j];
html += "<tr>\n";
// SmartStatus-tooltips are named --success whereas icon-job uses successful
var iconStatus = (job.status === 'successful') ? 'success' : 'failed';
html += "<td><a href=\"#/jobs/" + job.id + "\"><i class=\"fa DashboardList-status SmartStatus-tooltip--" + iconStatus + " icon-job-" +
job.status + "\"></i></a></td>\n";
html += "<td>" + ($filter('longDate')(job.finished)).replace(/ /,'<br />') + "</td>\n";
html += "<td class=\"break\"><a href=\"#/jobs/" + job.id + "\" " +
"aw-tool-tip=\"" + job.status.charAt(0).toUpperCase() + job.status.slice(1) +
". Click for details\" data-placement=\"top\">" + ellipsis(job.name) + "</a></td>\n";
html += "</tr>\n";
}
html += "</tbody>\n";
html += "</table>\n";
}
else {
noRecentJobs();
}
}
else if (host.has_active_failures === false && host.last_job === null) {
host.badgeToolTip = "No job data available.";
host.active_failures = 'none';
noRecentJobs();
}
host.job_status_html = html;
host.job_status_title = title;
}
if (!Empty(host)) {
// update single host
setMsg(host);
SetEnabledMsg(host);
}
else {
// update all hosts
for (i=0; i < scope.hosts.length; i++) {
setMsg(scope.hosts[i]);
SetEnabledMsg(scope.hosts[i]);
}
}
};
}
SetStatus.$inject =
[ '$filter',
'SetEnabledMsg',
'Empty'
];

View File

@@ -6,8 +6,12 @@
import HostsAddController from './hosts-add.controller'; import HostsAddController from './hosts-add.controller';
import HostsEditController from './hosts-edit.controller'; import HostsEditController from './hosts-edit.controller';
import SetStatus from './factories/set-status.factory';
import SetEnabledMsg from './factories/set-enabled-msg.factory';
export default export default
angular.module('manageHosts', []) angular.module('manageHosts', [])
.factory('SetStatus', SetStatus)
.factory('SetEnabledMsg', SetEnabledMsg)
.controller('HostsAddController', HostsAddController) .controller('HostsAddController', HostsAddController)
.controller('HostEditController', HostsEditController); .controller('HostEditController', HostsEditController);

View File

@@ -0,0 +1,162 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Adhoc
* @description These routines are shared by adhoc command related controllers.
* The content here is very similar to the JobSubmission helper, and in fact,
* certain services are pulled from that helper. This leads to an important
* point: if you need to create functionality that is shared between the command
* and playbook run process, put that code in the JobSubmission helper and make
* it into a reusable step (by specifying a callback parameter in the factory).
* For a good example of this, please see how the AdhocLaunch factory in this
* file utilizes the CheckPasswords factory from the JobSubmission helper.
*
* #AdhocRelaunch Step 1: preparing the GET to ad_hoc_commands/n/relaunch
* The adhoc relaunch process is called from the JobSubmission helper. It is a
* separate process from the initial adhoc run becuase of the way the API
* endpoints work. For AdhocRelaunch, we have access to the original run and
* we can pull the related relaunch URL by knowing the original Adhoc runs ID.
*
* #AdhocRelaunch Step 2: If we got passwords back, add them
* The relaunch URL gives us back the passwords we need to prompt for (if any).
* We'll go to step 3 if there are passwords, and step 4 if not.
*
* #AdhocRelaunch Step 3: PromptForPasswords and the CreateLaunchDialog
*
* #AdhocRelaunch Step 5: StartAdhocRun
*
* #AdhocRelaunch Step 6: LaunchJob and navigate to the standard out page.
* **If you are
* TODO: once the API endpoint is figured out for running an adhoc command
* from the form is figured out, the rest work should probably be excised from
* the controller and moved into here. See the todo statements in the
* controller for more information about this.
*/
export default
function AdhocRun($location, $stateParams, LaunchJob, PromptForPasswords,
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) {
return function(params) {
var id = params.project_id,
scope = params.scope.$new(),
new_job_id,
html,
url;
// this is used to cancel a running adhoc command from
// the jobs page
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
// Delete the job
Wait('start');
Rest.setUrl(GetBasePath('ad_hoc_commands') + new_job_id + '/');
Rest.destroy()
.success(function() {
Wait('stop');
})
.error(function (data, status) {
ProcessErrors(scope, data, status,
null, { hdr: 'Error!',
msg: 'Call to ' + url +
' failed. DELETE returned status: ' +
status });
});
});
if (scope.removeStartAdhocRun) {
scope.removeStartAdhocRun();
}
scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() {
var password,
postData={};
for (password in scope.passwords) {
postData[scope.passwords[password]] = scope[
scope.passwords[password]
];
}
// Re-launch the adhoc job
Rest.setUrl(url);
Rest.post(postData)
.success(function (data) {
Wait('stop');
if($location.path().replace(/^\//, '').split('/')[0] !== 'jobs') {
$state.go('adHocJobStdout', {id: data.id});
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, {
hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status });
});
});
// start routine only if passwords need to be prompted
if (scope.removeCreateLaunchDialog) {
scope.removeCreateLaunchDialog();
}
scope.removeCreateLaunchDialog = scope.$on('CreateLaunchDialog',
function(e, html, url) {
CreateLaunchDialog({
scope: scope,
html: html,
url: url,
callback: 'StartAdhocRun'
});
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords',
function(e, passwords_needed_to_start,html, url) {
PromptForPasswords({
scope: scope,
passwords: passwords_needed_to_start,
callback: 'CreateLaunchDialog',
html: html,
url: url
});
}); // end password prompting routine
// start the adhoc relaunch routine
Wait('start');
url = GetBasePath('ad_hoc_commands') + id + '/relaunch/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
new_job_id = data.id;
scope.passwords_needed_to_start = data.passwords_needed_to_start;
if (!Empty(data.passwords_needed_to_start) &&
data.passwords_needed_to_start.length > 0) {
// go through the password prompt routine before
// starting the adhoc run
scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url);
}
else {
// no prompting of passwords needed
scope.$emit('StartAdhocRun');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job template details. GET returned status: ' + status });
});
};
}
AdhocRun.$inject =
[ '$location','$stateParams', 'LaunchJob',
'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors',
'Wait', 'Empty', 'CreateLaunchDialog', '$state'
];

View File

@@ -0,0 +1,43 @@
export default
function CheckPasswords(Rest, GetBasePath, ProcessErrors, Empty) {
return function(params) {
var scope = params.scope,
callback = params.callback,
credential = params.credential;
var passwords = [];
if (!Empty(credential)) {
Rest.setUrl(GetBasePath('credentials')+credential);
Rest.get()
.success(function (data) {
if(data.kind === "ssh"){
if(data.password === "ASK" ){
passwords.push("ssh_password");
}
if(data.ssh_key_unlock === "ASK"){
passwords.push("ssh_key_unlock");
}
if(data.become_password === "ASK"){
passwords.push("become_password");
}
if(data.vault_password === "ASK"){
passwords.push("vault_password");
}
}
scope.$emit(callback, passwords);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job template details. GET returned status: ' + status });
});
}
};
}
CheckPasswords.$inject =
[ 'Rest',
'GetBasePath',
'ProcessErrors',
'Empty'
];

View File

@@ -0,0 +1,75 @@
export default
function CreateLaunchDialog($compile, CreateDialog, Wait, ParseTypeChange) {
return function(params) {
var buttons,
scope = params.scope,
html = params.html,
// job_launch_data = {},
callback = params.callback || 'PlaybookLaunchFinished',
// url = params.url,
e;
// html+='<br>job_launch_form.$valid = {{job_launch_form.$valid}}<br>';
html+='</form>';
$('#password-modal').empty().html(html);
$('#password-modal').find('#job_extra_vars').before(scope.helpContainer);
e = angular.element(document.getElementById('password-modal'));
$compile(e)(scope);
if(scope.prompt_for_vars===true){
ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"});
}
buttons = [{
label: "Cancel",
onClick: function() {
$('#password-modal').dialog('close');
// scope.$emit('CancelJob');
// scope.$destroy();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "password-cancel-button"
},{
label: "Launch",
onClick: function() {
scope.$emit(callback);
$('#password-modal').dialog('close');
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "password-accept-button"
}];
CreateDialog({
id: 'password-modal',
scope: scope,
buttons: buttons,
width: 620,
height: 700, //(scope.passwords.length > 1) ? 700 : 500,
minWidth: 500,
title: 'Launch Configuration',
callback: 'DialogReady',
onOpen: function(){
Wait('stop');
}
});
if (scope.removeDialogReady) {
scope.removeDialogReady();
}
scope.removeDialogReady = scope.$on('DialogReady', function() {
$('#password-modal').dialog('open');
$('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' );
e = angular.element(document.getElementById('password-accept-button'));
$compile(e)(scope);
});
};
}
CreateLaunchDialog.$inject =
[ '$compile',
'CreateDialog',
'Wait',
'ParseTypeChange'
];

View File

@@ -0,0 +1,76 @@
export default
function InventoryUpdate(PromptForPasswords, LaunchJob, Rest, GetBasePath, ProcessErrors, Alert, Wait) {
return function (params) {
var scope = params.scope,
url = params.url,
inventory_source;
if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted();
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () {
Wait('stop');
if (scope.socketStatus === 'error') {
Alert('Sync Started', '<div>The request to start the inventory sync process was submitted. ' +
'To monitor the status refresh the page by clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
if (scope.refreshGroups) {
// inventory detail page
scope.refreshGroups();
}
else if (scope.refresh) {
scope.refresh();
}
}
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate();
}
scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
});
// Check to see if we have permission to perform the update and if any passwords are needed
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
inventory_source = data;
if (data.can_update) {
if (data.passwords_needed_to_update) {
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
} else {
Wait('stop');
Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.',
'alert-danger');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status });
});
};
}
InventoryUpdate.$inject =
[ 'PromptForPasswords',
'LaunchJob',
'Rest',
'GetBasePath',
'ProcessErrors',
'Alert',
'Wait'
];

View File

@@ -0,0 +1,78 @@
export default
function ProjectUpdate(PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) {
return function (params) {
var scope = params.scope,
project_id = params.project_id,
url = GetBasePath('projects') + project_id + '/update/',
project;
if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted();
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() {
// Refresh the project list after update request submitted
Wait('stop');
if (/\d$/.test($location.path())) {
//Request submitted from projects/N page. Navigate back to the list so user can see status
$location.path('/projects');
}
if (scope.socketStatus === 'error') {
Alert('Update Started', '<div>The request to start the SCM update process was submitted. ' +
'To monitor the update status, refresh the page by clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
if (scope.refresh) {
scope.refresh();
}
}
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate();
}
scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
});
// Check to see if we have permission to perform the update and if any passwords are needed
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
project = data;
if (project.can_update) {
if (project.passwords_needed_to_updated) {
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
}
else {
Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.',
'alert-danger');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to lookup project ' + url + ' GET returned: ' + status });
});
};
}
ProjectUpdate.$inject =
[ 'PromptForPasswords',
'LaunchJob',
'Rest',
'$location',
'GetBasePath',
'ProcessErrors',
'Alert',
'Wait'
];

View File

@@ -0,0 +1,82 @@
export default
function PromptForPasswords(CredentialForm) {
return function(params) {
var scope = params.scope,
callback = params.callback || 'PasswordsAccepted',
url = params.url,
form = CredentialForm,
fld, field,
html=params.html || "";
scope.passwords = params.passwords;
html += "<div class=\"alert alert-info\">Launching this job requires the passwords listed below. Enter and confirm each password before continuing.</div>\n";
scope.passwords.forEach(function(password) {
// Prompt for password
field = form.fields[password];
fld = password;
scope[fld] = '';
html += "<div class=\"form-group prepend-asterisk\">\n";
html += "<label for=\"" + fld + "\">" + field.label + "</label>\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"password-field form-control input-sm\" ";
html += (field.associated) ? "ng-change=\"clearPWConfirm('" + field.associated + "')\" " : "";
html += "required ";
html += " >";
// Add error messages
html += "<div class=\"error\" ng-show=\"job_launch_form." + fld + ".$dirty && " +
"job_launch_form." + fld + ".$error.required\">Please enter a password.</div>\n";
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
// Add the related confirm field
if (field.associated) {
fld = field.associated;
field = form.fields[field.associated];
scope[fld] = '';
html += "<div class=\"form-group prepend-asterisk\">\n";
html += "<label for=\"" + fld + "\"> " + field.label + "</label>\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"form-control input-sm\" ";
html += "ng-change=\"checkStatus()\" ";
html += "required ";
html += (field.awPassMatch) ? "awpassmatch=\"" + field.associated + "\" " : "";
html += "/>";
// Add error messages
html += "<div class=\"error\" ng-show=\"job_launch_form." + fld + ".$dirty && " +
"job_launch_form." + fld + ".$error.required\">Please confirm the password.</span>\n";
html += (field.awPassMatch) ? "<span class=\"error\" ng-show=\"job_launch_form." + fld +
".$error.awpassmatch\">This value does not match the password you entered previously. Please confirm that password.</div>\n" : "";
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
}
});
scope.$emit(callback, html, url);
// Password change
scope.clearPWConfirm = function (fld) {
// If password value changes, make sure password_confirm must be re-entered
scope[fld] = '';
scope.job_launch_form[fld].$setValidity('awpassmatch', false);
scope.checkStatus();
};
scope.checkStatus = function() {
if (!scope.job_launch_form.$invalid) {
$('#password-accept-button').removeAttr('disabled');
}
else {
$('#password-accept-button').attr({ "disabled": "disabled" });
}
};
};
}
PromptForPasswords.$inject =
[ 'CredentialForm' ];

View File

@@ -7,6 +7,12 @@
import InitiatePlaybookRun from './job-submission-factories/initiateplaybookrun.factory'; import InitiatePlaybookRun from './job-submission-factories/initiateplaybookrun.factory';
import LaunchJob from './job-submission-factories/launchjob.factory'; import LaunchJob from './job-submission-factories/launchjob.factory';
import GetSurveyQuestions from './job-submission-factories/getsurveyquestions.factory'; import GetSurveyQuestions from './job-submission-factories/getsurveyquestions.factory';
import AdhocRun from './job-submission-factories/adhoc-run.factory.js';
import CheckPasswords from './job-submission-factories/check-passwords.factory';
import CreateLaunchDialog from './job-submission-factories/create-launch-dialog.factory';
import InventoryUpdate from './job-submission-factories/inventory-update.factory';
import ProjectUpdate from './job-submission-factories/project-update.factory';
import PromptForPasswords from './job-submission-factories/prompt-for-passwords.factory';
import submitJob from './job-submission.directive'; import submitJob from './job-submission.directive';
import credentialList from './lists/credential/job-sub-cred-list.directive'; import credentialList from './lists/credential/job-sub-cred-list.directive';
import inventoryList from './lists/inventory/job-sub-inv-list.directive'; import inventoryList from './lists/inventory/job-sub-inv-list.directive';
@@ -16,6 +22,12 @@ export default
.factory('InitiatePlaybookRun', InitiatePlaybookRun) .factory('InitiatePlaybookRun', InitiatePlaybookRun)
.factory('LaunchJob', LaunchJob) .factory('LaunchJob', LaunchJob)
.factory('GetSurveyQuestions', GetSurveyQuestions) .factory('GetSurveyQuestions', GetSurveyQuestions)
.factory('AdhocRun', AdhocRun)
.factory('CheckPasswords', CheckPasswords)
.factory('CreateLaunchDialog', CreateLaunchDialog)
.factory('InventoryUpdate', InventoryUpdate)
.factory('ProjectUpdate', ProjectUpdate)
.factory('PromptForPasswords', PromptForPasswords)
.directive('submitJob', submitJob) .directive('submitJob', submitJob)
.directive('jobSubCredList', credentialList) .directive('jobSubCredList', credentialList)
.directive('jobSubInvList', inventoryList); .directive('jobSubInvList', inventoryList);

View File

@@ -0,0 +1,137 @@
export default
function DeleteJob($state, Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert,
$filter, i18n) {
return function(params) {
var scope = params.scope,
id = params.id,
job = params.job,
callback = params.callback,
action, jobs, url, action_label, hdr;
if (!job) {
if (scope.completed_jobs) {
jobs = scope.completed_jobs;
}
else if (scope.running_jobs) {
jobs = scope.running_jobs;
}
else if (scope.queued_jobs) {
jobs = scope.queued_jobs;
}
else if (scope.all_jobs) {
jobs = scope.all_jobs;
}
else if (scope.jobs) {
jobs = scope.jobs;
}
job = Find({list: jobs, key: 'id', val: id });
}
if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') {
url = job.related.cancel;
action_label = 'cancel';
hdr = i18n._('Cancel');
} else {
url = job.url;
action_label = 'delete';
hdr = i18n._('Delete');
}
action = function () {
Wait('start');
Rest.setUrl(url);
if (action_label === 'cancel') {
Rest.post()
.success(function () {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.error(function(obj, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
// ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. POST returned status: ' + status });
});
} else {
Rest.destroy()
.success(function () {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.error(function (obj, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
//ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. DELETE returned status: ' + status });
});
}
};
if (scope.removeCancelNotAllowed) {
scope.removeCancelNotAllowed();
}
scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() {
Wait('stop');
Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info');
});
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
var cancelBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Submit the request to cancel?") + "</div>";
var deleteBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to delete the job below?") + "</div><div class=\"Prompt-bodyTarget\">#" + id + " " + $filter('sanitize')(job.name) + "</div>";
Prompt({
hdr: hdr,
body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody,
action: action,
actionText: (action_label === 'cancel' || job.status === 'new') ? "OK" : "DELETE"
});
});
if (action_label === 'cancel') {
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.can_cancel) {
scope.$emit('CancelJob');
}
else {
scope.$emit('CancelNotAllowed');
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. GET returned: ' + status });
});
}
else {
scope.$emit('CancelJob');
}
};
}
DeleteJob.$inject =
[ '$state', 'Find', 'GetBasePath', 'Rest', 'Wait',
'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n'
];

View File

@@ -0,0 +1,31 @@
export default
function JobStatusToolTip() {
return function(status) {
var toolTip;
switch (status) {
case 'successful':
case 'success':
toolTip = 'There were no failed tasks.';
break;
case 'failed':
toolTip = 'Some tasks encountered errors.';
break;
case 'canceled':
toolTip = 'Stopped by user request.';
break;
case 'new':
toolTip = 'In queue, waiting on task manager.';
break;
case 'waiting':
toolTip = 'SCM Update or Inventory Update is executing.';
break;
case 'pending':
toolTip = 'Not in queue, waiting on task manager.';
break;
case 'running':
toolTip = 'Playbook tasks executing.';
break;
}
return toolTip;
};
}

View File

@@ -0,0 +1,49 @@
export default
function JobsListUpdate() {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope,
list = params.list;
scope[list.name].forEach(function(item, item_idx) {
var fld, field,
itm = scope[list.name][item_idx];
//if (item.type === 'inventory_update') {
// itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,'');
//}
// Set the item type label
if (list.fields.type) {
parent_scope.type_choices.forEach(function(choice) {
if (choice.value === item.type) {
itm.type_label = choice.label;
}
});
}
// Set the job status label
parent_scope.status_choices.forEach(function(status) {
if (status.value === item.status) {
itm.status_label = status.label;
}
});
if (list.name === 'completed_jobs' || list.name === 'running_jobs') {
itm.status_tip = itm.status_label + '. Click for details.';
}
else if (list.name === 'queued_jobs') {
itm.status_tip = 'Pending';
}
// Copy summary_field values
for (field in list.fields) {
fld = list.fields[field];
if (fld.sourceModel) {
if (itm.summary_fields[fld.sourceModel]) {
itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField];
}
}
}
});
};
}

View File

@@ -0,0 +1,11 @@
export default
function RelaunchAdhoc(AdhocRun) {
return function(params) {
var scope = params.scope,
id = params.id;
AdhocRun({ scope: scope, project_id: id, relaunch: true });
};
}
RelaunchAdhoc.$inject =
[ 'AdhocRun' ];

View File

@@ -0,0 +1,30 @@
export default
function RelaunchInventory(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('inventory_sources') + id + '/';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
InventoryUpdate({
scope: scope,
url: data.related.update,
group_name: data.summary_fields.group.name,
group_source: data.source,
tree_id: null,
group_id: data.group
});
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
url + ' GET returned: ' + status });
});
};
}
RelaunchInventory.$inject =
[ 'Find', 'Wait', 'Rest',
'InventoryUpdate', 'ProcessErrors', 'GetBasePath'
];

View File

@@ -0,0 +1,28 @@
export default
function RelaunchJob(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) {
return function(params) {
var scope = params.scope,
id = params.id,
type = params.type,
name = params.name;
if (type === 'inventory_update') {
RelaunchInventory({ scope: scope, id: id});
}
else if (type === 'ad_hoc_command') {
RelaunchAdhoc({ scope: scope, id: id, name: name });
}
else if (type === 'job' || type === 'system_job' || type === 'workflow_job') {
RelaunchPlaybook({ scope: scope, id: id, name: name, job_type: type });
}
else if (type === 'project_update') {
RelaunchSCM({ scope: scope, id: id });
}
};
}
RelaunchJob.$inject =
[ 'RelaunchInventory',
'RelaunchPlaybook',
'RelaunchSCM',
'RelaunchAdhoc'
];

View File

@@ -0,0 +1,12 @@
export default
function RelaunchPlaybook(InitiatePlaybookRun) {
return function(params) {
var scope = params.scope,
id = params.id,
job_type = params.job_type;
InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type });
};
}
RelaunchPlaybook.$inject =
[ 'InitiatePlaybookRun' ];

View File

@@ -0,0 +1,11 @@
export default
function RelaunchSCM(ProjectUpdate) {
return function(params) {
var scope = params.scope,
id = params.id;
ProjectUpdate({ scope: scope, project_id: id });
};
}
RelaunchSCM.$inject =
[ 'ProjectUpdate' ];

View File

@@ -6,10 +6,26 @@
import jobsList from './jobs-list.controller'; import jobsList from './jobs-list.controller';
import jobsRoute from './jobs.route'; import jobsRoute from './jobs.route';
import DeleteJob from './factories/delete-job.factory';
import JobStatusToolTip from './factories/job-status-tool-tip.factory';
import JobsListUpdate from './factories/jobs-list-update.factory';
import RelaunchAdhoc from './factories/relaunch-adhoc.factory';
import RelaunchInventory from './factories/relaunch-inventory.factory';
import RelaunchJob from './factories/relaunch-job.factory';
import RelaunchPlaybook from './factories/relaunch-playbook.factory';
import RelaunchSCM from './factories/relaunch-scm.factory';
export default export default
angular.module('JobsModule', []) angular.module('JobsModule', [])
.run(['$stateExtender', function($stateExtender) { .run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(jobsRoute); $stateExtender.addState(jobsRoute);
}]) }])
.controller('JobsList', jobsList); .controller('JobsList', jobsList)
.factory('DeleteJob', DeleteJob)
.factory('JobStatusToolTip', JobStatusToolTip)
.factory('JobsListUpdate', JobsListUpdate)
.factory('RelaunchAdhoc', RelaunchAdhoc)
.factory('RelaunchInventory', RelaunchInventory)
.factory('RelaunchJob', RelaunchJob)
.factory('RelaunchPlaybook', RelaunchPlaybook)
.factory('RelaunchSCM', RelaunchSCM);

View File

@@ -12,9 +12,9 @@
*/ */
export default ['$scope', '$rootScope', 'ProcessErrors', 'GetBasePath', 'generateList', export default ['$scope', '$rootScope', 'ProcessErrors', 'GetBasePath', 'generateList',
'SelectionInit', 'templateUrl', '$state', 'Rest', '$q', 'Wait', '$window', 'QuerySet', 'UserList', 'templateUrl', '$state', 'Rest', '$q', 'Wait', '$window', 'QuerySet', 'UserList',
function($scope, $rootScope, ProcessErrors, GetBasePath, generateList, function($scope, $rootScope, ProcessErrors, GetBasePath, generateList,
SelectionInit, templateUrl, $state, Rest, $q, Wait, $window, qs, UserList) { templateUrl, $state, Rest, $q, Wait, $window, qs, UserList) {
$scope.$on("linkLists", function() { $scope.$on("linkLists", function() {
if ($state.current.name.split(".")[1] === "users") { if ($state.current.name.split(".")[1] === "users") {

View File

@@ -7,11 +7,11 @@
export default ['$scope', '$rootScope', '$location', '$log', export default ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'Prompt', '$stateParams', 'Rest', 'Alert', 'Prompt',
'ReturnToCaller', 'ClearScope', 'OrgProjectList', 'OrgProjectDataset', 'ReturnToCaller', 'ClearScope', 'OrgProjectList', 'OrgProjectDataset',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate', 'ProcessErrors', 'GetBasePath', 'ProjectUpdate',
'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state', 'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state',
function($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, Prompt, function($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, Prompt,
ReturnToCaller, ClearScope, OrgProjectList, Dataset, ReturnToCaller, ClearScope, OrgProjectList, Dataset,
ProcessErrors, GetBasePath, SelectionInit, ProjectUpdate, ProcessErrors, GetBasePath, ProjectUpdate,
Wait, GetChoices, Empty, Find, GetProjectIcon, GetProjectToolTip, $filter, $state) { Wait, GetChoices, Empty, Find, GetProjectIcon, GetProjectToolTip, $filter, $state) {
var list = OrgProjectList, var list = OrgProjectList,

View File

@@ -6,12 +6,12 @@
export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'OrgTeamList', export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'OrgTeamList',
'Rest', 'Alert', 'Prompt', 'OrgTeamsDataset', 'ReturnToCaller', 'ClearScope', 'Rest', 'Alert', 'Prompt', 'OrgTeamsDataset', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'SetTeamListeners', 'GetBasePath', 'ProcessErrors', 'GetBasePath',
'SelectionInit', 'Wait', '$state', 'Wait', '$state',
function($scope, $rootScope, $location, $log, $stateParams, OrgTeamList, function($scope, $rootScope, $location, $log, $stateParams, OrgTeamList,
Rest, Alert, Prompt, Dataset, ReturnToCaller, ClearScope, Rest, Alert, Prompt, Dataset, ReturnToCaller, ClearScope,
ProcessErrors, SetTeamListeners, GetBasePath, ProcessErrors, GetBasePath,
SelectionInit, Wait, $state) { Wait, $state) {
var list = OrgTeamList, var list = OrgTeamList,
orgBase = GetBasePath('organizations'); orgBase = GetBasePath('organizations');

View File

@@ -0,0 +1,30 @@
export default
function GetProjectIcon() {
return function(status) {
var result = '';
switch (status) {
case 'n/a':
case 'ok':
case 'never updated':
result = 'none';
break;
case 'pending':
case 'waiting':
case 'new':
result = 'none';
break;
case 'updating':
case 'running':
result = 'running';
break;
case 'successful':
result = 'success';
break;
case 'failed':
case 'missing':
case 'canceled':
result = 'error';
}
return result;
};
}

View File

@@ -0,0 +1,78 @@
export default
function GetProjectPath(Alert, Rest, GetBasePath, ProcessErrors) {
return function(params) {
var scope = params.scope,
master = params.master;
function arraySort(data) {
//Sort nodes by name
var i, j, names = [],
newData = [];
for (i = 0; i < data.length; i++) {
names.push(data[i].value);
}
names.sort();
for (j = 0; j < names.length; j++) {
for (i = 0; i < data.length; i++) {
if (data[i].value === names[j]) {
newData.push(data[i]);
}
}
}
return newData;
}
scope.showMissingPlaybooksAlert = false;
Rest.setUrl(GetBasePath('config'));
Rest.get()
.success(function (data) {
var opts = [], i;
if (data.project_local_paths) {
for (i = 0; i < data.project_local_paths.length; i++) {
opts.push({
label: data.project_local_paths[i],
value: data.project_local_paths[i]
});
}
}
if (scope.local_path) {
// List only includes paths not assigned to projects, so add the
// path assigned to the current project.
opts.push({
label: scope.local_path,
value: scope.local_path
});
}
scope.project_local_paths = arraySort(opts);
if (scope.local_path) {
for (i = 0; scope.project_local_paths.length; i++) {
if (scope.project_local_paths[i].value === scope.local_path) {
scope.local_path = scope.project_local_paths[i];
break;
}
}
}
scope.base_dir = data.project_base_dir;
master.local_path = scope.local_path;
master.base_dir = scope.base_dir; // Keep in master object so that it doesn't get
// wiped out on form reset.
if (opts.length === 0) {
// trigger display of alert block when scm_type == manual
scope.showMissingPlaybooksAlert = true;
}
scope.$emit('pathsReady');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to access API config. GET status: ' + status });
});
};
}
GetProjectPath.$inject =
[ 'Alert',
'Rest',
'GetBasePath',
'ProcessErrors'
];

View File

@@ -0,0 +1,38 @@
export default
function GetProjectToolTip(i18n) {
return function(status) {
var result = '';
switch (status) {
case 'n/a':
case 'ok':
case 'never updated':
result = i18n._('No SCM updates have run for this project');
break;
case 'pending':
case 'waiting':
case 'new':
result = i18n._('Queued. Click for details');
break;
case 'updating':
case 'running':
result = i18n._('Running! Click for details');
break;
case 'successful':
result = i18n._('Success! Click for details');
break;
case 'failed':
result = i18n._('Failed. Click for details');
break;
case 'missing':
result = i18n._('Missing. Click for details');
break;
case 'canceled':
result = i18n._('Canceled. Click for details');
break;
}
return result;
};
}
GetProjectToolTip.$inject =
[ 'i18n' ];

View File

@@ -8,12 +8,18 @@ import ProjectsList from './list/projects-list.controller';
import ProjectsAdd from './add/projects-add.controller'; import ProjectsAdd from './add/projects-add.controller';
import ProjectsEdit from './edit/projects-edit.controller'; import ProjectsEdit from './edit/projects-edit.controller';
import { N_ } from '../i18n'; import { N_ } from '../i18n';
import GetProjectPath from './factories/get-project-path.factory';
import GetProjectIcon from './factories/get-project-icon.factory';
import GetProjectToolTip from './factories/get-project-tool-tip.factory';
export default export default
angular.module('Projects', []) angular.module('Projects', [])
.controller('ProjectsList', ProjectsList) .controller('ProjectsList', ProjectsList)
.controller('ProjectsAdd', ProjectsAdd) .controller('ProjectsAdd', ProjectsAdd)
.controller('ProjectsEdit', ProjectsEdit) .controller('ProjectsEdit', ProjectsEdit)
.factory('GetProjectPath', GetProjectPath)
.factory('GetProjectIcon', GetProjectIcon)
.factory('GetProjectToolTip', GetProjectToolTip)
.config(['$stateProvider', 'stateDefinitionsProvider', .config(['$stateProvider', 'stateDefinitionsProvider',
function($stateProvider, stateDefinitionsProvider) { function($stateProvider, stateDefinitionsProvider) {
let stateDefinitions = stateDefinitionsProvider.$get(); let stateDefinitions = stateDefinitionsProvider.$get();

View File

@@ -0,0 +1,135 @@
export default
function AddSchedule($location, $rootScope, $stateParams, SchedulerInit,
Wait, GetBasePath, Empty, SchedulePost, $state, Rest,
ProcessErrors) {
return function(params) {
var scope = params.scope,
callback= params.callback,
base = params.base || $location.path().replace(/^\//, '').split('/')[0],
url = params.url || null,
scheduler,
job_type;
job_type = scope.parentObject.job_type;
if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !url) {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
}
else if(base === "inventories"){
if (!params.url){
url = GetBasePath('groups') + $stateParams.id + '/';
Rest.setUrl(url);
Rest.get().
then(function (data) {
url = data.data.related.inventory_source + 'schedules/';
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory group info. GET returned status: ' +
response.status
});
});
}
else {
url = params.url;
}
}
else if (base === 'system_job_templates') {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
if(job_type === "cleanup_facts"){
scope.isFactCleanup = true;
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30);
scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1);
scope.keep_unit = scope.keep_unit_choices[0];
scope.granularity_keep_unit = scope.granularity_keep_unit_choices[1];
}
else {
scope.cleanupJob = true;
}
}
Wait('start');
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
if(scope.schedulerUTCTime) {
// The UTC time is already set
scope.processSchedulerEndDt();
}
else {
// We need to wait for it to be set by angular-scheduler because the following function depends
// on it
var schedulerUTCTimeWatcher = scope.$watch('schedulerUTCTime', function(newVal) {
if(newVal) {
// Remove the watcher
schedulerUTCTimeWatcher();
scope.processSchedulerEndDt();
}
});
}
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
scheduler.clear();
scope.$on("htmlDetailReady", function() {
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^", null, {reload: true});
});
scope.saveSchedule = function() {
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'add'
});
};
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
if ($(e.target).text() === 'Details') {
if (!scheduler.isValid()) {
$('#scheduler-tabs a:first').tab('show');
}
}
});
};
}
AddSchedule.$inject =
[ '$location', '$rootScope', '$stateParams',
'SchedulerInit', 'Wait', 'GetBasePath',
'Empty', 'SchedulePost', '$state',
'Rest', 'ProcessErrors'
];

View File

@@ -0,0 +1,61 @@
export default
function DeleteSchedule(GetBasePath, Rest, Wait, $state,
ProcessErrors, Prompt, Find, $location, $filter) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
action, schedule, list, url, hdr;
if (scope.schedules) {
list = scope.schedules;
}
else if (scope.scheduled_jobs) {
list = scope.scheduled_jobs;
}
url = GetBasePath('schedules') + id + '/';
schedule = Find({list: list, key: 'id', val: id });
hdr = 'Delete Schedule';
action = function () {
Wait('start');
Rest.setUrl(url);
Rest.destroy()
.success(function () {
$('#prompt-modal').modal('hide');
scope.$emit(callback, id);
if (new RegExp('/' + id + '$').test($location.$$url)) {
$location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view
}
else{
$state.go('.', null, {reload: true});
}
})
.error(function (data, status) {
try {
$('#prompt-modal').modal('hide');
}
catch(e) {
// ignore
}
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. DELETE returned: ' + status });
});
};
Prompt({
hdr: hdr,
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the schedule below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(schedule.name) + '</div>',
action: action,
actionText: 'DELETE',
backdrop: false
});
};
}
DeleteSchedule.$inject =
[ 'GetBasePath','Rest', 'Wait', '$state',
'ProcessErrors', 'Prompt', 'Find', '$location',
'$filter'
];

View File

@@ -0,0 +1,154 @@
export default
function EditSchedule(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors,
GetBasePath, SchedulePost, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
schedule, scheduler,
url = GetBasePath('schedules') + id + '/';
delete scope.isFactCleanup;
delete scope.cleanupJob;
function setGranularity(){
var a,b, prompt_for_days,
keep_unit,
granularity,
granularity_keep_unit;
if(scope.cleanupJob){
scope.schedulerPurgeDays = Number(schedule.extra_data.days);
// scope.scheduler_form.schedulerPurgeDays.$setViewValue( Number(schedule.extra_data.days));
}
else if(scope.isFactCleanup){
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
// the API returns something like 20w or 1y
a = schedule.extra_data.older_than; // "20y"
b = schedule.extra_data.granularity; // "1w"
prompt_for_days = Number(_.initial(a,1).join('')); // 20
keep_unit = _.last(a); // "y"
granularity = Number(_.initial(b,1).join('')); // 1
granularity_keep_unit = _.last(b); // "w"
scope.keep_amount = prompt_for_days;
scope.granularity_keep_amount = granularity;
scope.keep_unit = _.find(scope.keep_unit_choices, function(i){
return i.value === keep_unit;
});
scope.granularity_keep_unit =_.find(scope.granularity_keep_unit_choices, function(i){
return i.value === granularity_keep_unit;
});
}
}
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function() {
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
if (!/DTSTART/.test(schedule.rrule)) {
schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z');
}
schedule.rrule = schedule.rrule.replace(/ RRULE:/,';');
schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART=');
scope.$on("htmlDetailReady", function() {
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
if(scope.isFactCleanup || scope.cleanupJob){
setGranularity();
}
});
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^");
});
scope.saveSchedule = function() {
schedule.extra_data = scope.extraVars;
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'edit',
schedule: schedule
});
};
Wait('start');
// Get the existing record
Rest.setUrl(url);
Rest.get()
.success(function(data) {
schedule = data;
try {
schedule.extra_data = JSON.parse(schedule.extra_data);
} catch(e) {
// do nothing
}
scope.extraVars = data.extra_data === '' ? '---' : '---\n' + jsyaml.safeDump(data.extra_data);
if(schedule.extra_data.hasOwnProperty('granularity')){
scope.isFactCleanup = true;
}
if (schedule.extra_data.hasOwnProperty('days')){
scope.cleanupJob = true;
}
scope.schedule_obj = data;
scope.$emit('ScheduleFound');
})
.error(function(data,status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}
EditSchedule.$inject =
[ 'SchedulerInit', '$rootScope', 'Wait', 'Rest',
'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state'
];

View File

@@ -0,0 +1,10 @@
export default
function RRuleToAPI() {
return function(rrule) {
var response;
response = rrule.replace(/(^.*(?=DTSTART))(DTSTART=.*?;)(.*$)/, function(str, p1, p2, p3) {
return p2.replace(/\;/,'').replace(/=/,':') + ' ' + 'RRULE:' + p1 + p3;
});
return response;
};
}

View File

@@ -0,0 +1,78 @@
export default
function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait) {
return function(params) {
var scope = params.scope,
url = params.url,
scheduler = params.scheduler,
mode = params.mode,
schedule = (params.schedule) ? params.schedule : {},
callback = params.callback,
newSchedule, rrule, extra_vars;
if (scheduler.isValid()) {
Wait('start');
newSchedule = scheduler.getValue();
rrule = scheduler.getRRule();
schedule.name = newSchedule.name;
schedule.rrule = RRuleToAPI(rrule.toString());
schedule.description = (/error/.test(rrule.toText())) ? '' : rrule.toText();
if (scope.isFactCleanup) {
extra_vars = {
"older_than": scope.scheduler_form.keep_amount.$viewValue + scope.scheduler_form.keep_unit.$viewValue.value,
"granularity": scope.scheduler_form.granularity_keep_amount.$viewValue + scope.scheduler_form.granularity_keep_unit.$viewValue.value
};
schedule.extra_data = JSON.stringify(extra_vars);
} else if (scope.cleanupJob) {
extra_vars = {
"days" : scope.scheduler_form.schedulerPurgeDays.$viewValue
};
schedule.extra_data = JSON.stringify(extra_vars);
}
else if(scope.extraVars){
schedule.extra_data = scope.parseType === 'yaml' ?
(scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars;
}
Rest.setUrl(url);
if (mode === 'add') {
Rest.post(schedule)
.success(function(){
if (callback) {
scope.$emit(callback);
}
else {
Wait('stop');
}
})
.error(function(data, status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status });
});
}
else {
Rest.put(schedule)
.success(function(){
if (callback) {
scope.$emit(callback, schedule);
}
else {
Wait('stop');
}
})
.error(function(data, status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status });
});
}
}
else {
return false;
}
};
}
SchedulePost.$inject =
[ 'Rest',
'ProcessErrors',
'RRuleToAPI',
'Wait'
];

View File

@@ -0,0 +1,46 @@
export default
function ToggleSchedule(Wait, GetBasePath, ProcessErrors, Rest, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('schedules') + id +'/';
// Perform the update
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function(e, data) {
data.enabled = (data.enabled) ? false : true;
Rest.put(data)
.success( function() {
Wait('stop');
$state.go('.', null, {reload: true});
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status });
});
});
Wait('start');
// Get the schedule
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.$emit('ScheduleFound', data);
})
.error(function(data,status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}
ToggleSchedule.$inject =
[ 'Wait',
'GetBasePath',
'ProcessErrors',
'Rest',
'$state'
];

View File

@@ -10,12 +10,24 @@ import editController from './schedulerEdit.controller';
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import schedulerDatePicker from './schedulerDatePicker.directive'; import schedulerDatePicker from './schedulerDatePicker.directive';
import { N_ } from '../i18n'; import { N_ } from '../i18n';
import AddSchedule from './factories/add-schedule.factory';
import DeleteSchedule from './factories/delete-schedule.factory';
import EditSchedule from './factories/edit-schedule.factory';
import RRuleToAPI from './factories/r-rule-to-api.factory';
import SchedulePost from './factories/schedule-post.factory';
import ToggleSchedule from './factories/toggle-schedule.factory';
export default export default
angular.module('scheduler', []) angular.module('scheduler', [])
.controller('schedulerListController', listController) .controller('schedulerListController', listController)
.controller('schedulerAddController', addController) .controller('schedulerAddController', addController)
.controller('schedulerEditController', editController) .controller('schedulerEditController', editController)
.factory('AddSchedule', AddSchedule)
.factory('DeleteSchedule', DeleteSchedule)
.factory('EditSchedule', EditSchedule)
.factory('RRuleToAPI', RRuleToAPI)
.factory('SchedulePost', SchedulePost)
.factory('ToggleSchedule', ToggleSchedule)
.directive('schedulerDatePicker', schedulerDatePicker) .directive('schedulerDatePicker', schedulerDatePicker)
.run(['$stateExtender', function($stateExtender) { .run(['$stateExtender', function($stateExtender) {
// Inventory sync schedule states registered in: awx/ui/client/src/inventories/manage/groups/main.js // Inventory sync schedule states registered in: awx/ui/client/src/inventories/manage/groups/main.js

View File

@@ -17,7 +17,7 @@
export default export default
angular.module('ModalDialog', ['Utilities', 'ParseHelper']) angular.module('ModalDialog', ['Utilities'])
/** /**
* @ngdoc method * @ngdoc method

View File

@@ -14,7 +14,7 @@
*/ */
export default export default
angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) angular.module('AWDirectives', ['RestServices', 'Utilities'])
// awpassmatch: Add to password_confirm field. Will test if value // awpassmatch: Add to password_confirm field. Will test if value
// matches that of 'input[name="password"]' // matches that of 'input[name="password"]'

View File

@@ -1,30 +1,5 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:LoadConfig
* @description Attempts to load local_config.js. If not found, loads config.js. Then evaluates the loaded
* javascript, putting the result in $AnsibleConfig.
* LoadConfigHelper
*
*
*
*/
/*jshint evil:true */
export default export default
angular.module('LoadConfigHelper', ['Utilities']) function LoadConfig($log, $rootScope, $http, $location, GetBasePath, ProcessErrors, Rest, Store) {
.factory('LoadConfig', ['$log', '$rootScope', '$http', '$location', 'GetBasePath',
'ProcessErrors', 'Rest', 'Store',
function($log, $rootScope, $http, $location, GetBasePath, ProcessErrors, Rest, Store) {
return function() { return function() {
// These ettings used to be found in config.js, hardcoded now. // These ettings used to be found in config.js, hardcoded now.
@@ -105,4 +80,8 @@ angular.module('LoadConfigHelper', ['Utilities'])
}; };
} }
]);
LoadConfig.$inject =
[ '$log', '$rootScope', '$http', '$location',
'GetBasePath', 'ProcessErrors', 'Rest', 'Store'
];

View File

@@ -0,0 +1,5 @@
import LoadConfig from './load-config.factory';
export default
angular.module('loadconfig', [])
.factory('LoadConfig', LoadConfig);

View File

@@ -20,6 +20,10 @@ import templateUrl from './template-url/main';
import RestServices from '../rest/main'; import RestServices from '../rest/main';
import stateDefinitions from './stateDefinitions.factory'; import stateDefinitions from './stateDefinitions.factory';
import apiLoader from './api-loader'; import apiLoader from './api-loader';
import variables from './variables/main';
import parse from './parse/main';
import loadconfig from './load-config/main';
import Modal from './Modal';
import 'angular-duration-format'; import 'angular-duration-format';
export default export default
@@ -36,6 +40,10 @@ angular.module('shared', [listGenerator.name,
templateUrl.name, templateUrl.name,
RestServices.name, RestServices.name,
apiLoader.name, apiLoader.name,
variables.name,
parse.name,
loadconfig.name,
Modal.name,
require('angular-cookies'), require('angular-cookies'),
'angular-duration-format' 'angular-duration-format'
]) ])

View File

@@ -0,0 +1,5 @@
import ParseTypeChange from './parse-type-change.factory';
export default
angular.module('parse', [])
.factory('ParseTypeChange', ParseTypeChange);

View File

@@ -0,0 +1,93 @@
import 'codemirror/lib/codemirror.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/yaml/yaml.js';
import 'codemirror/addon/lint/lint.js';
import 'angular-codemirror/lib/yaml-lint.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/selection/active-line.js';
export default
function ParseTypeChange(Alert, AngularCodeMirror) {
return function(params) {
var scope = params.scope,
field_id = params.field_id,
fld = (params.variable) ? params.variable : 'variables',
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
onReady = params.onReady,
onChange = params.onChange,
readOnly = params.readOnly;
function removeField(fld) {
//set our model to the last change in CodeMirror and then destroy CodeMirror
scope[fld] = scope[fld + 'codeMirror'].getValue();
$('#cm-' + fld + '-container > .CodeMirror').empty().remove();
}
function createField(onChange, onReady, fld) {
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes);
scope[fld + 'codeMirror'].showTextArea({
scope: scope,
model: fld,
element: field_id,
lineNumbers: true,
mode: scope[pfld],
onReady: onReady,
onChange: onChange
});
}
// Hide the textarea and show a CodeMirror editor
createField(onChange, onReady, fld);
// Toggle displayed variable string between JSON and YAML
scope.parseTypeChange = function(model, fld) {
var json_obj;
if (scope[model] === 'json') {
// converting yaml to json
try {
removeField(fld);
json_obj = jsyaml.load(scope[fld]);
if ($.isEmptyObject(json_obj)) {
scope[fld] = "{}";
}
else {
scope[fld] = JSON.stringify(json_obj, null, " ");
}
createField(onReady, onChange, fld);
}
catch (e) {
Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message);
setTimeout( function() { scope.$apply( function() { scope[model] = 'yaml'; createField(onReady, onChange, fld); }); }, 500);
}
}
else {
// convert json to yaml
try {
removeField(fld);
json_obj = JSON.parse(scope[fld]);
if ($.isEmptyObject(json_obj)) {
scope[fld] = '---';
}
else {
scope[fld] = jsyaml.safeDump(json_obj);
}
createField(onReady, onChange, fld);
}
catch (e) {
Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message);
setTimeout( function() { scope.$apply( function() { scope[model] = 'json'; createField(onReady, onChange, fld); }); }, 500 );
}
}
};
};
}
ParseTypeChange.$inject =
[ 'Alert',
'AngularCodeMirror'
];

View File

@@ -0,0 +1,9 @@
import ParseVariableString from './parse-variable-string.factory';
import SortVariables from './sort-variables.factory';
import ToJSON from './to-json.factory';
export default
angular.module('variables', [])
.factory('ParseVariableString', ParseVariableString)
.factory('SortVariables', SortVariables)
.factory('ToJSON', ToJSON);

View File

@@ -0,0 +1,55 @@
export default
function ParseVariableString($log, ProcessErrors, SortVariables) {
return function (variables) {
var result = "---", json_obj;
if (typeof variables === 'string') {
if (variables === "{}" || variables === "null" || variables === "" || variables === "\"\"") {
// String is empty, return ---
} else {
try {
json_obj = JSON.parse(variables);
json_obj = SortVariables(json_obj);
result = jsyaml.safeDump(json_obj);
}
catch (e) {
$log.debug('Attempt to parse extra_vars as JSON failed. Check that the variables parse as yaml. Set the raw string as the result.');
try {
// do safeLoad, which well error if not valid yaml
json_obj = jsyaml.safeLoad(variables);
// but just send the variables
result = variables;
}
catch(e2) {
ProcessErrors(null, variables, e2.message, null, { hdr: 'Error!',
msg: 'Attempts to parse variables as JSON and YAML failed. Last attempt returned: ' + e2.message });
}
}
}
}
else {
if ($.isEmptyObject(variables) || variables === null) {
// Empty object, return ---
}
else {
// convert object to yaml
try {
json_obj = SortVariables(variables);
result = jsyaml.safeDump(json_obj);
// result = variables;
}
catch(e3) {
ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!',
msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message });
}
}
}
return result;
};
}
ParseVariableString.$inject =
[ '$log',
'ProcessErrors',
'SortVariables'
];

View File

@@ -0,0 +1,23 @@
export default
function SortVariables() {
return function(variableObj) {
var newObj;
function sortIt(objToSort) {
var i,
keys = Object.keys(objToSort),
newObj = {};
keys = keys.sort();
for (i=0; i < keys.length; i++) {
if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) {
newObj[keys[i]] = sortIt(objToSort[keys[i]]);
}
else {
newObj[keys[i]] = objToSort[keys[i]];
}
}
return newObj;
}
newObj = sortIt(variableObj);
return newObj;
};
}

View File

@@ -0,0 +1,80 @@
export default
function ToJSON($log, ProcessErrors) {
return function(parseType, variables, stringify, reviver) {
var json_data,
result,
tmp;
// bracketVar,
// key,
// lines, i, newVars = [];
if (parseType === 'json') {
try {
// perform a check to see if the user cleared the field completly
if(variables.trim() === "" || variables.trim() === "{" || variables.trim() === "}" ){
variables = "{}";
}
//parse a JSON string
if (reviver) {
json_data = JSON.parse(variables, reviver);
}
else {
json_data = JSON.parse(variables);
}
}
catch(e) {
json_data = {};
$log.error('Failed to parse JSON string. Parser returned: ' + e.message);
ProcessErrors(null, variables, e.message, null, { hdr: 'Error!',
msg: 'Failed to parse JSON string. Parser returned: ' + e.message });
throw 'Parse error. Failed to parse variables.';
}
} else {
try {
if(variables.trim() === "" || variables.trim() === "-" || variables.trim() === "--"){
variables = '---';
}
json_data = jsyaml.safeLoad(variables);
if(json_data!==null){
// unparsing just to make sure no weird characters are included.
tmp = jsyaml.dump(json_data);
if(tmp.indexOf('[object Object]')!==-1){
throw "Failed to parse YAML string. Parser returned' + key + ' : ' +value + '.' ";
}
}
}
catch(e) {
json_data = undefined; // {};
// $log.error('Failed to parse YAML string. Parser returned undefined');
ProcessErrors(null, variables, e.message, null, { hdr: 'Error!',
msg: 'Failed to parse YAML string. Parser returned undefined'});
}
}
// Make sure our JSON is actually an object
if (typeof json_data !== 'object') {
ProcessErrors(null, variables, null, null, { hdr: 'Error!',
msg: 'Failed to parse variables. Attempted to parse ' + parseType + '. Parser did not return an object.' });
// setTimeout( function() {
throw 'Parse error. Failed to parse variables.';
// }, 1000);
}
result = json_data;
if (stringify) {
if(json_data === undefined){
result = undefined;
}
else if ($.isEmptyObject(json_data)) {
result = "";
} else {
// utilize the parsing to get here
// but send the raw variable string
result = variables;
}
}
return result;
};
}
ToJSON.$inject =
[ '$log',
'ProcessErrors'
];

View File

@@ -0,0 +1,156 @@
export default
function CallbackHelpInit($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, ParseTypeChange,
ParseVariableString, Empty, InventoryList, CredentialList, ProjectList, Wait) {
return function(params) {
var scope = params.scope,
defaultUrl = GetBasePath('job_templates'),
// generator = GenerateForm,
form = JobTemplateForm(),
// loadingFinishedCount = 0,
// base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.job_template_id;
// checkSCMStatus, getPlaybooks, callback,
// choicesCount = 0;
CredentialList = _.cloneDeep(CredentialList);
// 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 provisioning 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();
// this fills the job template form both on copy of the job template
// and on edit
scope.fillJobTemplate = function(){
// id = id || $rootScope.copy.id;
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl + id);
Rest.get()
.success(function (data) {
scope.job_template_obj = data;
scope.name = data.name;
var fld, i;
for (fld in form.fields) {
if (fld !== 'variables' && fld !== 'survey' && data[fld] !== null && data[fld] !== undefined) {
if (form.fields[fld].type === 'select') {
if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) {
for (i = 0; i < scope[fld + '_options'].length; i++) {
if (data[fld] === scope[fld + '_options'][i].value) {
scope[fld] = scope[fld + '_options'][i];
}
}
} else {
scope[fld] = data[fld];
}
} else {
scope[fld] = data[fld];
if(!Empty(data.summary_fields.survey)) {
scope.survey_exists = true;
}
}
master[fld] = scope[fld];
}
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
scope.variables = ParseVariableString(data.extra_vars);
master.variables = scope.variables;
}
if (form.fields[fld].type === 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
if (form.fields[fld].type === 'checkbox_group') {
for(var j=0; j<form.fields[fld].fields.length; j++) {
scope[form.fields[fld].fields[j].name] = data[form.fields[fld].fields[j].name];
}
}
}
Wait('stop');
scope.url = data.url;
scope.survey_enabled = data.survey_enabled;
scope.ask_variables_on_launch = (data.ask_variables_on_launch) ? true : false;
master.ask_variables_on_launch = scope.ask_variables_on_launch;
scope.ask_limit_on_launch = (data.ask_limit_on_launch) ? true : false;
master.ask_limit_on_launch = scope.ask_limit_on_launch;
scope.ask_tags_on_launch = (data.ask_tags_on_launch) ? true : false;
master.ask_tags_on_launch = scope.ask_tags_on_launch;
scope.ask_skip_tags_on_launch = (data.ask_skip_tags_on_launch) ? true : false;
master.ask_skip_tags_on_launch = scope.ask_skip_tags_on_launch;
scope.ask_job_type_on_launch = (data.ask_job_type_on_launch) ? true : false;
master.ask_job_type_on_launch = scope.ask_job_type_on_launch;
scope.ask_inventory_on_launch = (data.ask_inventory_on_launch) ? true : false;
master.ask_inventory_on_launch = scope.ask_inventory_on_launch;
scope.ask_credential_on_launch = (data.ask_credential_on_launch) ? true : false;
master.ask_credential_on_launch = scope.ask_credential_on_launch;
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;
scope.can_edit = data.summary_fields.user_capabilities.edit;
if (scope.job_type.value === "scan" && (!scope.project || scope.project === "") && (!scope.playbook || scope.playbook === "")) {
scope.resetProjectToDefault();
}
scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to retrieve job template: ' + $stateParams.id + '. GET status: ' + status
});
});
};
};
}
CallbackHelpInit.$inject =
[ '$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm',
'$stateParams', 'ProcessErrors', 'ParseTypeChange', 'ParseVariableString',
'Empty', 'InventoryList', 'CredentialList','ProjectList', 'Wait'
];

View File

@@ -0,0 +1,30 @@
export default
function md5Setup(md5) {
return function(params) {
var scope = params.scope,
master = params.master,
check_field = params.check_field,
default_val = params.default_val;
scope[check_field] = default_val;
master[check_field] = default_val;
scope.genMD5 = function (fld) {
var now = new Date();
scope[fld] = md5.createHash('AnsibleWorks' + now.getTime());
scope.$emit('NewMD5Generated');
};
scope.toggleCallback = function (fld) {
if (scope.allow_callbacks === false) {
scope[fld] = '';
}
};
scope.selectAll = function (fld) {
$('input[name="' + fld + '"]').focus().select();
};
};
}
md5Setup.$inject = [ 'md5' ];

View File

@@ -18,6 +18,8 @@ import workflowControls from './workflows/workflow-controls/main';
import templatesListRoute from './list/templates-list.route'; import templatesListRoute from './list/templates-list.route';
import workflowService from './workflows/workflow.service'; import workflowService from './workflows/workflow.service';
import templateCopyService from './copy-template/template-copy.service'; import templateCopyService from './copy-template/template-copy.service';
import CallbackHelpInit from './job_templates/factories/callback-help-init.factory';
import md5Setup from './job_templates/factories/md-5-setup.factory';
export default export default
angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesAdd.name, angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesAdd.name,
@@ -27,6 +29,8 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA
.service('TemplatesService', templatesService) .service('TemplatesService', templatesService)
.service('WorkflowService', workflowService) .service('WorkflowService', workflowService)
.service('TemplateCopyService', templateCopyService) .service('TemplateCopyService', templateCopyService)
.factory('CallbackHelpInit', CallbackHelpInit)
.factory('md5Setup', md5Setup)
.config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider',
function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) {
let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow, let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow,

View File

@@ -14,10 +14,10 @@ const user_type_options = [
export default ['$scope', '$rootScope', '$stateParams', 'UserForm', 'GenerateForm', export default ['$scope', '$rootScope', '$stateParams', 'UserForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath',
'ResetForm', 'Wait', 'CreateSelect2', '$state', '$location', 'i18n', 'Wait', 'CreateSelect2', '$state', '$location', 'i18n',
function($scope, $rootScope, $stateParams, UserForm, function($scope, $rootScope, $stateParams, UserForm,
GenerateForm, Rest, Alert, ProcessErrors, ReturnToCaller, ClearScope, GenerateForm, Rest, Alert, ProcessErrors, ReturnToCaller, ClearScope,
GetBasePath, ResetForm, Wait, CreateSelect2, $state, $location, i18n) { GetBasePath, Wait, CreateSelect2, $state, $location, i18n) {
ClearScope(); ClearScope();

View File

@@ -14,10 +14,10 @@ const user_type_options = [
export default ['$scope', '$rootScope', '$location', export default ['$scope', '$rootScope', '$location',
'$stateParams', 'UserForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath', '$stateParams', 'UserForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
'ResetForm', 'Wait', 'CreateSelect2', '$state', 'i18n', 'Wait', 'CreateSelect2', '$state', 'i18n',
function($scope, $rootScope, $location, function($scope, $rootScope, $location,
$stateParams, UserForm, Rest, ProcessErrors, $stateParams, UserForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ResetForm, Wait, CreateSelect2, $state, i18n) { ClearScope, GetBasePath, Wait, CreateSelect2, $state, i18n) {
for (var i = 0; i < user_type_options.length; i++) { for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label); user_type_options[i].label = i18n._(user_type_options[i].label);

View File

@@ -7,7 +7,6 @@ describe('Controller: workflowResults', () => {
let $controller; let $controller;
let workflowResults; let workflowResults;
let $rootScope; let $rootScope;
let ParseVariableString;
let workflowResultsService; let workflowResultsService;
let $interval; let $interval;
@@ -17,8 +16,6 @@ describe('Controller: workflowResults', () => {
} }
}; };
beforeEach(angular.mock.module('VariablesHelper'));
beforeEach(angular.mock.module('workflowResults', ($provide) => { beforeEach(angular.mock.module('workflowResults', ($provide) => {
['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors', ['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors',
'InitiatePlaybookRun', 'jobLabels', 'workflowNodes', 'count', 'InitiatePlaybookRun', 'jobLabels', 'workflowNodes', 'count',
@@ -30,8 +27,9 @@ describe('Controller: workflowResults', () => {
$provide.value('workflowData', workflow_job_json); $provide.value('workflowData', workflow_job_json);
$provide.value('workflowDataOptions', workflow_job_options_json); $provide.value('workflowDataOptions', workflow_job_options_json);
$provide.value('ParseTypeChange', function() {}); $provide.value('ParseTypeChange', function() {});
$provide.value('ParseVariableString', function() {});
$provide.value('i18n', { '_': (a) => { return a; } }); $provide.value('i18n', { '_': (a) => { return a; } });
$provide.provider('$stateProvider', { '$get': function() { return function() {} } }); $provide.provider('$stateProvider', { '$get': function() { return function() {}; } });
$provide.service('WorkflowService', function($q) { $provide.service('WorkflowService', function($q) {
return { return {
buildTree: function() { buildTree: function() {
@@ -39,14 +37,13 @@ describe('Controller: workflowResults', () => {
deferred.resolve(treeData); deferred.resolve(treeData);
return deferred.promise; return deferred.promise;
} }
} };
}); });
})); }));
beforeEach(angular.mock.inject(function(_$controller_, _$rootScope_, _ParseVariableString_, _workflowResultsService_, _$interval_){ beforeEach(angular.mock.inject(function(_$controller_, _$rootScope_, _workflowResultsService_, _$interval_){
$controller = _$controller_; $controller = _$controller_;
$rootScope = _$rootScope_; $rootScope = _$rootScope_;
ParseVariableString = _ParseVariableString_;
workflowResultsService = _workflowResultsService_; workflowResultsService = _workflowResultsService_;
$interval = _$interval_; $interval = _$interval_;