Merge branch 'release_3.3.0' into 1137-cancel-prompt

This commit is contained in:
Michael Abashian
2018-04-23 10:57:01 -04:00
committed by GitHub
139 changed files with 2222 additions and 3611 deletions

View File

@@ -24,16 +24,18 @@
header-link="{{ vm.getLink(job) }}"
header-tag="{{ vm.jobTypes[job.type] }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_STARTED') }}"
value="{{ job.started | longDate }}"
inline="true">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_FINISHED') }}"
value="{{ job.finished | longDate }}"
inline="true">
</at-row-item>
<div class="at-Row--inline">
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_STARTED') }}"
value="{{ job.started | longDate }}"
inline="true">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_FINISHED') }}"
value="{{ job.finished | longDate }}"
inline="true">
</at-row-item>
</div>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_WORKFLOW_JOB') }}"
value="{{ job.summary_fields.source_workflow_job.name }}"
@@ -64,7 +66,7 @@
tag-values="job.summary_fields.credentials"
tags-are-creds="true">
</at-row-item>
<labels-list class="LabelList" show-delete="false" is-row-item="true">
<labels-list class="LabelList" show-delete="false" is-row-item="true" state="job">
</labels-list>
</div>
<div class="at-Row-actions">

View File

@@ -89,6 +89,10 @@ function TemplatesStrings (BaseString) {
ns.warnings = {
WORKFLOW_RESTRICTED_COPY: t.s('You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.')
};
ns.workflows = {
INVALID_JOB_TEMPLATE: t.s('This Job Template is missing a default inventory or project. This must be addressed in the Job Template form before this node can be saved.')
};
}
TemplatesStrings.$inject = ['BaseStringService'];

View File

@@ -83,7 +83,7 @@
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_RAN') }}"
value="{{ vm.getLastRan(template) }}">
</at-row-item>
<labels-list class="LabelList" show-delete="false" is-row-item="true">
<labels-list class="LabelList" show-delete="false" is-row-item="true" state="template">
</labels-list>
</div>
<div class="at-Row-actions">

View File

@@ -879,7 +879,6 @@ input[type="checkbox"].checkbox-no-label {
.checkbox-options {
font-weight: normal;
padding-right: 20px;
}
/* Display list actions next to search widget */

View File

@@ -788,3 +788,8 @@ input[type='radio']:checked:before {
border-color: @b7grey;
background-color: @ebgrey;
}
.Form-checkboxRow {
display: flex;
clear: left;
}

View File

@@ -1,6 +1,4 @@
.at-LaunchTemplate {
margin-left: 15px;
&--button {
font-size: 16px;
height: 30px;
@@ -8,8 +6,9 @@
color: #848992;
background-color: inherit;
border: none;
border-radius: 4px;
border-radius: 5px;
}
&--button:hover {
background-color: @at-blue;
color: white;

View File

@@ -40,6 +40,7 @@
}
.at-List-toolbarActionButton {
border: none;
border-radius: @at-border-radius;
min-width: 80px;
}
@@ -67,12 +68,8 @@
}
.at-Row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: @at-padding-list-row;
position: relative;
display: grid;
grid-template-columns: 10px 1fr;
}
.at-Row--active {
@@ -85,15 +82,21 @@
border-left: @at-white solid 1px;
}
.at-Row--active .at-Row-content {
margin-left: -5px;
}
.at-Row ~ .at-Row {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
border-top: @at-border-default-width solid @at-color-list-border;
}
.at-Row--invalid {
align-items: center;
background: @at-color-error;
display: flex;
height: 100%;
justify-content: center;
left: 0;
position: absolute;
width: @at-space-2x;
.at-Popover {
padding: 0;
@@ -108,31 +111,15 @@
}
}
.at-Row ~ .at-Row {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
border-top: @at-border-default-width solid @at-color-list-border;
}
.at-Row--rowLayout {
.at-Row-content {
align-items: center;
display: flex;
flex-direction: row;
.at-RowItem {
margin-right: @at-space-4x;
&-label {
width: auto;
}
}
flex-wrap: wrap;
grid-column-start: 2;
padding: @at-padding-list-row;
}
.at-RowStatus {
align-self: flex-start;
margin: 0 10px 0 0;
}
.at-Row-firstColumn {
.at-Row-toggle {
margin-right: @at-space-4x;
}
@@ -141,12 +128,12 @@
}
.at-Row-items {
align-self: flex-start;
flex: 1;
}
.at-RowItem {
display: flex;
display: grid;
grid-template-columns: 120px 1fr;
align-items: center;
line-height: @at-height-list-row-item;
}
@@ -156,6 +143,7 @@
}
.at-RowItem--isHeader {
display: flex;
color: @at-color-body-text;
margin-bottom: @at-margin-bottom-list-header;
line-height: @at-line-height-list-row-item-header;
@@ -171,6 +159,12 @@
.at-RowItem--labels {
line-height: @at-line-height-list-row-item-labels;
display: flex;
flex-wrap: wrap;
* {
font-size: 10px;
}
}
.at-RowItem-header {
@@ -178,6 +172,7 @@
}
.at-RowItem-tagContainer {
display: flex;
margin-left: @at-margin-left-list-row-item-tag-container;
}
@@ -210,8 +205,6 @@
.at-RowItem-label {
text-transform: uppercase;
width: auto;
width: @at-width-list-row-item-label;
color: @at-color-list-row-item-label;
font-size: @at-font-size;
}
@@ -280,6 +273,7 @@
@media screen and (max-width: @at-breakpoint-compact-list) {
.at-Row-actions {
flex-direction: column;
align-items: center;
}
.at-RowAction {

View File

@@ -2,5 +2,5 @@
<div class="at-Row--invalid" ng-show="invalid">
<at-popover state="invalidTooltip"></at-popover>
</div>
<ng-transclude></ng-transclude>
<div class="at-Row-content" ng-transclude></div>
</div>

View File

@@ -129,7 +129,7 @@ function atRelaunchCtrl (
vm.$onInit = () => {
vm.showRelaunch = vm.job.type !== 'system_job' && vm.job.summary_fields.user_capabilities.start;
vm.showDropdown = vm.job.type === 'job' && vm.job.failed === true;
vm.showDropdown = vm.job.type === 'job' && vm.job.status === 'failed';
vm.createDropdown();
vm.createTooltips();

View File

@@ -1,21 +1,36 @@
let Base;
let Credential;
function setDependentResources (id) {
this.dependentResources = [
{
model: new Credential(),
params: {
organization: id
}
}
];
}
function OrganizationModel (method, resource, config) {
Base.call(this, 'organizations');
this.Constructor = OrganizationModel;
this.setDependentResources = setDependentResources.bind(this);
return this.create(method, resource, config);
}
function OrganizationModelLoader (BaseModel) {
function OrganizationModelLoader (BaseModel, CredentialModel) {
Base = BaseModel;
Credential = CredentialModel;
return OrganizationModel;
}
OrganizationModelLoader.$inject = [
'BaseModel'
'BaseModel',
'CredentialModel'
];
export default OrganizationModelLoader;

View File

@@ -73,6 +73,7 @@ function BaseStringService (namespace) {
this.deleteResource = {
HEADER: t.s('Delete'),
USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }),
UNAVAILABLE: resourceType => t.s('Deleting this {{ resourceType }} will make the following resources unavailable.', { resourceType }),
CONFIRM: resourceType => t.s('Are you sure you want to delete this {{ resourceType }}?', { resourceType })
};

View File

@@ -126,6 +126,7 @@
@import '../../src/templates/survey-maker/survey-maker.block.less';
@import '../../src/templates/survey-maker/shared/survey-controls.block.less';
@import '../../src/templates/survey-maker/survey-maker.block.less';
@import '../../src/templates/workflows/workflow.block.less';
@import '../../src/templates/workflows/workflow-chart/workflow-chart.block.less';
@import '../../src/templates/workflows/workflow-controls/workflow-controls.block.less';
@import '../../src/templates/workflows/workflow-maker/workflow-maker.block.less';

View File

@@ -46,4 +46,8 @@ capacity-bar {
.Capacity-details--percentage {
width: 40px;
}
&:only-child {
margin-right: 50px;
}
}

View File

@@ -17,6 +17,7 @@
top: 0px;
bottom: 0px;
z-index: 3;
overflow-y: scroll;
.modal-dialog {
padding-top: 100px;

View File

@@ -35,7 +35,7 @@
<at-list results='vm.instances'>
<at-row ng-repeat="instance in vm.instances"
ng-class="{'at-Row--active': (instance.id === vm.activeId)}">
<div class="at-Row-firstColumn">
<div class="at-Row-toggle">
<div class="ScheduleToggle"
ng-class="{'is-on': instance.enabled}">
<button ng-show="instance.enabled"
@@ -53,14 +53,13 @@
<div class="at-Row-items">
<at-row-item header-value="{{ instance.hostname }}"></at-row-item>
<div class="at-Row--rowLayout">
<at-row-item
label-value="Running Jobs"
label-state="instanceGroups.instanceJobs({instance_group_id: {{vm.instance_group_id}}, instance_id: {{instance.id}}})"
value="{{ instance.jobs_running }}"
badge="true">
</at-row-item>
</div>
<at-row-item
label-value="Running Jobs"
label-state="instanceGroups.instanceJobs({instance_group_id: {{vm.instance_group_id}}, instance_id: {{instance.id}}})"
value="{{ instance.jobs_running }}"
inline="true"
badge="true">
</at-row-item>
</div>
<div class="at-Row-actions">

View File

@@ -39,11 +39,12 @@
header-link="/#/instance_groups/{{ instance_group.id }}">
</at-row-item>
<div class="at-Row--rowLayout">
<div class="at-Row--inline">
<at-row-item
label-value="Instances"
label-link="/#/instance_groups/{{ instance_group.id }}/instances"
value="{{ instance_group.instances }}"
inline="true"
badge="true">
</at-row-item>
@@ -51,10 +52,10 @@
label-value="Running Jobs"
label-link="/#/instance_groups/{{ instance_group.id }}/jobs"
value="{{ instance_group.jobs_running }}"
inline="true"
badge="true">
</at-row-item>
</div>
</div>
<div class="at-Row-actions">

View File

@@ -15,7 +15,7 @@
hover: true,
multiSelect: true,
trackBy: 'group.id',
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/root_groups/',
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/groups/',
fields: {
failed_hosts: {

View File

@@ -6,39 +6,40 @@
export default ['$stateParams', '$scope', '$rootScope',
'Rest', 'OrganizationList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'rbacUiControlService', '$filter', 'Dataset', 'i18n',
'Rest', 'OrganizationList', 'Prompt', 'OrganizationModel',
'ProcessErrors', 'GetBasePath', 'Wait', '$state',
'rbacUiControlService', '$filter', 'Dataset', 'i18n',
'AppStrings',
function($stateParams, $scope, $rootScope,
Rest, OrganizationList, Prompt,
ProcessErrors, GetBasePath, Wait, $state, rbacUiControlService, $filter, Dataset, i18n) {
Rest, OrganizationList, Prompt, Organization,
ProcessErrors, GetBasePath, Wait, $state,
rbacUiControlService, $filter, Dataset, i18n,
AppStrings
) {
var defaultUrl = GetBasePath('organizations'),
list = OrganizationList;
init();
$scope.canAdd = false;
function init() {
$scope.canAdd = false;
rbacUiControlService.canAdd("organizations")
.then(function(params) {
$scope.canAdd = params.canAdd;
});
$scope.orgCount = Dataset.data.count;
rbacUiControlService.canAdd("organizations")
.then(function(params) {
$scope.canAdd = params.canAdd;
});
$scope.orgCount = Dataset.data.count;
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.orgCards = parseCardData($scope[list.name]);
$rootScope.flashMessage = null;
$scope.orgCards = parseCardData($scope[list.name]);
$rootScope.flashMessage = null;
// grab the pagination elements, move, destroy list generator elements
$('#organization-pagination').appendTo('#OrgCards');
$('#organizations tag-search').appendTo('.OrgCards-search');
$('#organizations-list').remove();
}
// grab the pagination elements, move, destroy list generator elements
$('#organization-pagination').appendTo('#OrgCards');
$('#organizations tag-search').appendTo('.OrgCards-search');
$('#organizations-list').remove();
function parseCardData(cards) {
return cards.map(function(card) {
@@ -167,13 +168,34 @@ export default ['$stateParams', '$scope', '$rootScope',
});
};
Prompt({
hdr: i18n._('Delete'),
resourceName: $filter('sanitize')(name),
body: '<div class="Prompt-bodyQuery">' + i18n._('Are you sure you want to delete this organization? This makes everything in this organization unavailable.') + '</div>',
action: action,
actionText: i18n._('DELETE')
});
const organization = new Organization();
organization.getDependentResourceCounts(id)
.then((counts) => {
const invalidateRelatedLines = [];
let deleteModalBody = `<div class="Prompt-bodyQuery">${AppStrings.get('deleteResource.CONFIRM', 'organization')}</div>`;
counts.forEach(countObj => {
if(countObj.count && countObj.count > 0) {
invalidateRelatedLines.push(`<div><span class="Prompt-warningResourceTitle">${countObj.label}</span><span class="badge List-titleBadge">${countObj.count}</span></div>`);
}
});
if (invalidateRelatedLines && invalidateRelatedLines.length > 0) {
deleteModalBody = `<div class="Prompt-bodyQuery">${AppStrings.get('deleteResource.UNAVAILABLE', 'organization')} ${AppStrings.get('deleteResource.CONFIRM', 'organization')}</div>`;
invalidateRelatedLines.forEach(invalidateRelatedLine => {
deleteModalBody += invalidateRelatedLine;
});
}
Prompt({
hdr: i18n._('Delete'),
resourceName: $filter('sanitize')(name),
body: deleteModalBody,
action: action,
actionText: i18n._('DELETE')
});
});
};
}
];

View File

@@ -241,7 +241,7 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert',
resourceName: $filter('sanitize')(name),
body: deleteModalBody,
action: action,
actionText: 'DELETE'
actionText: i18n._('DELETE')
});
});
};

View File

@@ -545,6 +545,8 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "' ";
html += (field.ngDisabled) ? `ng-disabled="${field.ngDisabled}" ` : "";
html += " class='ScheduleToggle-switch' ng-click='" + field.ngClick + "' translate>" + i18n._("OFF") + "</button></div></div>";
} else if (field.type === 'html') {
html += field.html;
}
return html;
},
@@ -599,6 +601,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
label = (includeLabel !== undefined && includeLabel === false) ? false : true;
if (label) {
html += "<span class=\"Form-checkboxRow\">";
html += "<label class=\"";
html += (field.inline === undefined || field.inline === true) ? "checkbox-inline" : "";
html += (field.labelClass) ? " " + field.labelClass : "";
@@ -628,6 +631,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += field.label + " ";
html += (field.awPopOver) ? Attr(field, 'awPopOver', fld) : "";
html += "</label>\n";
html += "</span>";
}
return html;

View File

@@ -155,16 +155,16 @@ function SmartSearchController (
const unmodifiedQueryset = _.clone(queryset);
const searchInputQueryset = qs.getSearchInputQueryset(terms, isRelatedField, isAnsibleFactField, singleSearchParam);
const modifiedQueryset = qs.mergeQueryset(queryset, searchInputQueryset, singleSearchParam);
queryset = qs.mergeQueryset(queryset, searchInputQueryset, singleSearchParam);
// Go back to the first page after a new search
delete modifiedQueryset.page;
delete queryset.page;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views but will register new
// $stateParams[searchKey] terms.
if (!$scope.querySet) {
$state.go('.', { [searchKey]: modifiedQueryset })
$state.go('.', { [searchKey]: queryset })
.then(() => {
// same as above in $scope.remove. For some reason deleting the page
// from the queryset works for all lists except lists in modals.
@@ -172,10 +172,10 @@ function SmartSearchController (
});
}
qs.search(path, modifiedQueryset)
qs.search(path, queryset)
.then(({ data }) => {
if ($scope.querySet) {
$scope.querySet = modifiedQueryset;
$scope.querySet = queryset;
}
$scope.dataset = data;
$scope.collection = data.results;
@@ -191,10 +191,10 @@ function SmartSearchController (
const { singleSearchParam } = $scope;
const [term] = $scope.searchTags.splice(index, 1);
const modifiedQueryset = qs.removeTermsFromQueryset(queryset, term, isRelatedField, singleSearchParam);
queryset = qs.removeTermsFromQueryset(queryset, term, isRelatedField, singleSearchParam);
if (!$scope.querySet) {
$state.go('.', { [searchKey]: modifiedQueryset })
$state.go('.', { [searchKey]: queryset })
.then(() => {
// for some reason deleting a tag from a list in a modal does not
// remove the param from $stateParams. Here we'll manually check to make sure

View File

@@ -6,6 +6,9 @@
align-items: flex-start;
}
.LabelList-tagContainer {
height: fit-content;
}
.LabelList-tagContainer,
.LabelList-seeMoreLess {
@@ -70,6 +73,7 @@
.LabelList-name {
flex: initial;
max-width: 100%;
word-break: break-word;
}
.LabelList-tag--deletable > .LabelList-name {

View File

@@ -12,7 +12,9 @@ export default
function(templateUrl, Wait, Rest, GetBasePath, ProcessErrors, Prompt, $q, $filter, $state) {
return {
restrict: 'E',
scope: false,
scope: {
state: '='
},
templateUrl: templateUrl('templates/labels/labelsList'),
link: function(scope, element, attrs) {
scope.showDelete = attrs.showDelete === 'true';
@@ -33,7 +35,9 @@ export default
scope.seeMore = function () {
var seeMoreResolve = $q.defer();
Rest.setUrl(scope[scope.$parent.list.iterator].related.labels);
if (scope.state) {
Rest.setUrl(scope.state.related.labels);
}
Rest.get()
.then(({data}) => {
if (data.next) {
@@ -92,23 +96,16 @@ export default
});
};
if (scope.$parent.$parent.template) {
if (_.has(scope, '$parent.$parent.template.summary_fields.labels.results')) {
scope.labels = scope.$parent.$parent.template.summary_fields.labels.results.slice(0, 5);
scope.count = scope.$parent.$parent.template.summary_fields.labels.count;
}
} else if (scope.$parent.$parent.job) {
if (_.has(scope, '$parent.$parent.job.summary_fields.labels.results')) {
scope.labels = scope.$parent.$parent.job.summary_fields.labels.results.slice(0, 5);
scope.count = scope.$parent.$parent.job.summary_fields.labels.count;
}
} else {
scope.$watchCollection(scope.$parent.list.iterator, function() {
if (_.has(scope.state, 'summary_fields.labels.results')) {
scope.labels = scope.state.summary_fields.labels.results.slice(0, 5);
scope.count = scope.state.summary_fields.labels.count;
} else {
scope.$watchCollection(scope.state, function() {
// To keep the array of labels fresh, we need to set up a watcher - otherwise, the
// array will get set initially and then never be updated as labels are removed
if (scope[scope.$parent.list.iterator].summary_fields.labels){
scope.labels = scope[scope.$parent.list.iterator].summary_fields.labels.results.slice(0, 5);
scope.count = scope[scope.$parent.list.iterator].summary_fields.labels.count;
if (scope.state.summary_fields.labels){
scope.labels = scope.state.summary_fields.labels.results.slice(0, 5);
scope.count = scope.state.summary_fields.labels.count;
}
else{
scope.labels = null;

View File

@@ -12,10 +12,11 @@
ng-click="seeMore()" ng-if="!isRowItem">View More</div>
<div class="LabelList-seeMoreLess" ng-show="count > 5 && !seeMoreInactive"
ng-click="seeLess()" ng-if="!isRowItem">View Less</div>
<div class="at-RowItem at-RowItem--labels" ng-show="count > 0" ng-if="isRowItem">
<div class="at-RowItem-label">
Labels
</div>
<div class="at-RowItem" ng-show="count > 0" ng-if="isRowItem">
<div class="at-RowItem-label">
Labels
</div>
<div class="at-RowItem--labels">
<div class="LabelList-tagContainer" ng-repeat="label in labels">
<div class="LabelList-tag" ng-class="{'LabelList-tag--deletable' : (showDelete && template.summary_fields.user_capabilities.edit)}">
<span class="LabelList-name">{{ label.name }}</span>
@@ -30,4 +31,5 @@
ng-click="seeMore()">View More</div>
<div class="LabelList-seeMoreLess" ng-show="count > 5 && !seeMoreInactive"
ng-click="seeLess()">View Less</div>
</div>
</div>

View File

@@ -18,7 +18,6 @@ import workflowService from './workflows/workflow.service';
import WorkflowForm from './workflows.form';
import InventorySourcesList from './inventory-sources.list';
import TemplateList from './templates.list';
import TemplatesStrings from './templates.strings';
import listRoute from '~features/templates/routes/templatesList.route.js';
import templateCompletedJobsRoute from '~features/jobs/routes/templateCompletedJobs.route.js';
@@ -32,7 +31,6 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
// TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc.
.factory('TemplateList', TemplateList)
.value('InventorySourcesList', InventorySourcesList)
.service('TemplatesStrings', TemplatesStrings)
.config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider',
function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) {
let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow,
@@ -379,6 +377,15 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
response.status
});
});
}],
workflowLaunch: ['$stateParams', 'WorkflowJobTemplateModel',
function($stateParams, WorkflowJobTemplate) {
let workflowJobTemplate = new WorkflowJobTemplate();
return workflowJobTemplate.getLaunch($stateParams.workflow_job_template_id)
.then(({data}) => {
return data;
});
}]
}
}

View File

@@ -70,31 +70,33 @@ export default [ 'Rest', 'GetBasePath', 'ProcessErrors', 'CredentialTypeModel',
vm.promptDataClone.prompts.credentials.passwords = {};
vm.promptDataClone.launchConf.passwords_needed_to_start.forEach((passwordNeeded) => {
if(passwordNeeded === "ssh_password") {
vm.promptDataClone.prompts.credentials.passwords.ssh = {};
}
if(passwordNeeded === "become_password") {
vm.promptDataClone.prompts.credentials.passwords.become = {};
}
if(passwordNeeded === "ssh_key_unlock") {
vm.promptDataClone.prompts.credentials.passwords.ssh_key_unlock = {};
}
if(passwordNeeded.startsWith("vault_password")) {
let vault_id;
if(passwordNeeded.includes('.')) {
vault_id = passwordNeeded.split(/\.(.+)/)[1];
if(vm.promptData.launchConf.passwords_needed_to_start) {
vm.promptData.launchConf.passwords_needed_to_start.forEach((passwordNeeded) => {
if(passwordNeeded === "ssh_password") {
vm.promptData.prompts.credentials.passwords.ssh = {};
}
if(!vm.promptDataClone.prompts.credentials.passwords.vault) {
vm.promptDataClone.prompts.credentials.passwords.vault = [];
if(passwordNeeded === "become_password") {
vm.promptData.prompts.credentials.passwords.become = {};
}
if(passwordNeeded === "ssh_key_unlock") {
vm.promptData.prompts.credentials.passwords.ssh_key_unlock = {};
}
if(passwordNeeded.startsWith("vault_password")) {
let vault_id;
if(passwordNeeded.includes('.')) {
vault_id = passwordNeeded.split(/\.(.+)/)[1];
}
vm.promptDataClone.prompts.credentials.passwords.vault.push({
vault_id: vault_id
});
}
});
if(!vm.promptData.prompts.credentials.passwords.vault) {
vm.promptData.prompts.credentials.passwords.vault = [];
}
vm.promptData.prompts.credentials.passwords.vault.push({
vault_id: vault_id
});
}
});
}
vm.promptDataClone.credentialTypeMissing = [];

View File

@@ -1,7 +0,0 @@
function TemplatesStrings (BaseString) {
BaseString.call(this, 'templates');
}
TemplatesStrings.$inject = ['BaseStringService'];
export default TemplatesStrings;

View File

@@ -27,6 +27,16 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
detailsClick: "$state.go('templates.editWorkflowJobTemplate')",
include: ['/static/partials/survey-maker-modal.html'],
headerFields: {
missingTemplates: {
type: 'html',
html: `<div ng-show="missingTemplates" class="Workflow-warning">
<span class="Workflow-warningIcon fa fa-warning"></span>` +
i18n._("Missing Job Templates found in the <span class='Workflow-warningLink' ng-click='openWorkflowMaker()'>Workflow Editor</span>") +
`</div>`
}
},
fields: {
name: {
label: i18n._('Name'),
@@ -82,6 +92,22 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' // TODO: get working
},
checkbox_group: {
label: i18n._('Options'),
type: 'checkbox_group',
fields: [{
name: 'allow_simultaneous',
label: i18n._('Enable Concurrent Jobs'),
type: 'checkbox',
column: 2,
awPopOver: "<p>" + i18n._("If enabled, simultaneous runs of this workflow job template will be allowed.") + "</p>",
dataPlacement: 'right',
dataTitle: i18n._('Enable Concurrent Jobs'),
dataContainer: "body",
labelClass: 'stack-inline',
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
}]
}
},

View File

@@ -59,7 +59,14 @@ export default [
try {
for (fld in form.fields) {
data[fld] = $scope[fld];
if(form.fields[fld].type === 'checkbox_group') {
// Loop across the checkboxes
for(var i=0; i<form.fields[fld].fields.length; i++) {
data[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name];
}
} else {
data[fld] = $scope[fld];
}
}
data.extra_vars = ToJSON($scope.parseType,

View File

@@ -10,10 +10,15 @@ export default [
'Wait', 'Empty', 'ToJSON', 'initSurvey', '$state', 'CreateSelect2',
'ParseVariableString', 'TemplatesService', 'Rest', 'ToggleNotification',
'OrgAdminLookup', 'availableLabels', 'selectedLabels', 'workflowJobTemplateData', 'i18n',
'workflowLaunch', '$transitions', 'WorkflowJobTemplateModel',
function($scope, $stateParams, WorkflowForm, GenerateForm, Alert,
ProcessErrors, GetBasePath, $q, ParseTypeChange, Wait, Empty,
ToJSON, SurveyControllerInit, $state, CreateSelect2, ParseVariableString,
TemplatesService, Rest, ToggleNotification, OrgAdminLookup, availableLabels, selectedLabels, workflowJobTemplateData, i18n) {
ProcessErrors, GetBasePath, $q, ParseTypeChange, Wait, Empty,
ToJSON, SurveyControllerInit, $state, CreateSelect2, ParseVariableString,
TemplatesService, Rest, ToggleNotification, OrgAdminLookup, availableLabels, selectedLabels, workflowJobTemplateData, i18n,
workflowLaunch, $transitions, WorkflowJobTemplate
) {
$scope.missingTemplates = _.has(workflowLaunch, 'node_templates_missing') && workflowLaunch.node_templates_missing.length > 0 ? true : false;
$scope.$watch('workflow_job_template_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
@@ -21,6 +26,25 @@ export default [
}
});
const criteriaObj = {
from: (state) => state.name === 'templates.editWorkflowJobTemplate.workflowMaker',
to: (state) => state.name === 'templates.editWorkflowJobTemplate'
};
$transitions.onSuccess(criteriaObj, function() {
if ($scope.missingTemplates) {
// Go out and check the new launch response to see if the user has fixed the
// missing node templates
let workflowJobTemplate = new WorkflowJobTemplate();
workflowJobTemplate.getLaunch($stateParams.workflow_job_template_id)
.then(({data}) => {
$scope.missingTemplates = _.has(data, 'node_templates_missing') && data.node_templates_missing.length > 0 ? true : false;
});
}
});
// Inject dynamic view
let form = WorkflowForm(),
generator = GenerateForm,
@@ -30,117 +54,34 @@ export default [
$scope.parseType = 'yaml';
$scope.includeWorkflowMaker = false;
function init() {
$scope.openWorkflowMaker = function() {
$state.go('.workflowMaker');
};
// Select2-ify the lables input
CreateSelect2({
element:'#workflow_job_template_labels',
multiple: true,
addNew: true
});
$scope.formSave = function () {
let fld, data = {};
$scope.invalid_survey = false;
SurveyControllerInit({
scope: $scope,
parent_scope: $scope,
id: id,
templateType: 'workflow_job_template'
});
// Can't have a survey enabled without a survey
if($scope.survey_enabled === true && $scope.survey_exists!==true){
$scope.survey_enabled = false;
}
$scope.labelOptions = availableLabels
.map((i) => ({label: i.name, value: i.id}));
generator.clearApiErrors($scope);
var opts = selectedLabels
.map(i => ({id: i.id + "",
test: i.name}));
Wait('start');
CreateSelect2({
element:'#workflow_job_template_labels',
multiple: true,
addNew: true,
opts: opts
});
$scope.workflowEditorTooltip = i18n._("Click here to open the workflow graph editor.");
$scope.surveyTooltip = i18n._('Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch.');
$scope.workflow_job_template_obj = workflowJobTemplateData;
$scope.name = workflowJobTemplateData.name;
$scope.can_edit = workflowJobTemplateData.summary_fields.user_capabilities.edit;
let fld, i;
for (fld in form.fields) {
if (fld !== 'variables' && fld !== 'survey' && workflowJobTemplateData[fld] !== null && workflowJobTemplateData[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 (workflowJobTemplateData[fld] === $scope[fld + '_options'][i].value) {
$scope[fld] = $scope[fld + '_options'][i];
}
}
} else {
$scope[fld] = workflowJobTemplateData[fld];
try {
for (fld in form.fields) {
if(form.fields[fld].type === 'checkbox_group') {
// Loop across the checkboxes
for(var i=0; i<form.fields[fld].fields.length; i++) {
data[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name];
}
} else {
$scope[fld] = workflowJobTemplateData[fld];
if(!Empty(workflowJobTemplateData.summary_fields.survey)) {
$scope.survey_exists = true;
}
data[fld] = $scope[fld];
}
}
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
$scope.variables = ParseVariableString(workflowJobTemplateData.extra_vars);
ParseTypeChange({ scope: $scope, field_id: 'workflow_job_template_variables' });
}
if (form.fields[fld].type === 'lookup' && workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
if(workflowJobTemplateData.organization) {
OrgAdminLookup.checkForRoleLevelAdminAccess(workflowJobTemplateData.organization, 'workflow_admin_role')
.then(function(canEditOrg){
$scope.canEditOrg = canEditOrg;
});
}
else {
$scope.canEditOrg = true;
}
$scope.url = workflowJobTemplateData.url;
$scope.survey_enabled = workflowJobTemplateData.survey_enabled;
$scope.includeWorkflowMaker = true;
$scope.$on('SurveySaved', function() {
Wait('stop');
$scope.survey_exists = true;
$scope.invalid_survey = false;
});
}
$scope.openWorkflowMaker = function() {
$state.go('.workflowMaker');
};
$scope.formSave = function () {
let fld, data = {};
$scope.invalid_survey = false;
// Can't have a survey enabled without a survey
if($scope.survey_enabled === true && $scope.survey_exists!==true){
$scope.survey_enabled = false;
}
generator.clearApiErrors($scope);
Wait('start');
try {
for (fld in form.fields) {
data[fld] = $scope[fld];
}
data.extra_vars = ToJSON($scope.parseType,
$scope.variables, true);
@@ -289,6 +230,91 @@ export default [
});
};
init();
// Select2-ify the lables input
CreateSelect2({
element:'#workflow_job_template_labels',
multiple: true,
addNew: true
});
SurveyControllerInit({
scope: $scope,
parent_scope: $scope,
id: id,
templateType: 'workflow_job_template'
});
$scope.labelOptions = availableLabels
.map((i) => ({label: i.name, value: i.id}));
var opts = selectedLabels
.map(i => ({id: i.id + "",
test: i.name}));
CreateSelect2({
element:'#workflow_job_template_labels',
multiple: true,
addNew: true,
opts: opts
});
$scope.workflowEditorTooltip = i18n._("Click here to open the workflow graph editor.");
$scope.surveyTooltip = i18n._('Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch.');
$scope.workflow_job_template_obj = workflowJobTemplateData;
$scope.name = workflowJobTemplateData.name;
$scope.can_edit = workflowJobTemplateData.summary_fields.user_capabilities.edit;
let fld, i;
for (fld in form.fields) {
if (fld !== 'variables' && fld !== 'survey' && workflowJobTemplateData[fld] !== null && workflowJobTemplateData[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 (workflowJobTemplateData[fld] === $scope[fld + '_options'][i].value) {
$scope[fld] = $scope[fld + '_options'][i];
}
}
} else {
$scope[fld] = workflowJobTemplateData[fld];
}
} else {
$scope[fld] = workflowJobTemplateData[fld];
if(!Empty(workflowJobTemplateData.summary_fields.survey)) {
$scope.survey_exists = true;
}
}
}
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
$scope.variables = ParseVariableString(workflowJobTemplateData.extra_vars);
ParseTypeChange({ scope: $scope, field_id: 'workflow_job_template_variables' });
}
if (form.fields[fld].type === 'lookup' && workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
if(workflowJobTemplateData.organization) {
OrgAdminLookup.checkForRoleLevelAdminAccess(workflowJobTemplateData.organization, 'workflow_admin_role')
.then(function(canEditOrg){
$scope.canEditOrg = canEditOrg;
});
}
else {
$scope.canEditOrg = true;
}
$scope.url = workflowJobTemplateData.url;
$scope.survey_enabled = workflowJobTemplateData.survey_enabled;
$scope.includeWorkflowMaker = true;
$scope.$on('SurveySaved', function() {
Wait('stop');
$scope.survey_exists = true;
$scope.invalid_survey = false;
});
}
];

View File

@@ -1,11 +1,9 @@
import workflowMaker from './workflow-maker.directive';
import WorkflowMakerController from './workflow-maker.controller';
import WorkflowMakerForm from './workflow-maker.form';
export default
angular.module('templates.workflowMaker', [])
// In order to test this controller I had to expose it at the module level
// like so. Is this correct? Is there a better pattern for doing this?
.controller('WorkflowMakerController', WorkflowMakerController)
.factory('WorkflowMakerForm', WorkflowMakerForm)
.directive('workflowMaker', workflowMaker);

View File

@@ -275,6 +275,10 @@
height: 100%;
overflow: hidden;
}
.WorkflowMaker-invalidJobTemplateWarning {
margin-bottom: 5px;
color: @default-err;
}
.Key-list {
margin: 0;

View File

@@ -5,15 +5,16 @@
*************************************************/
export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
'$state', 'ProcessErrors', 'CreateSelect2', 'WorkflowMakerForm', '$q', 'JobTemplateModel',
'Empty', 'PromptService', 'Rest',
function($scope, WorkflowService, GetBasePath, TemplatesService, $state,
ProcessErrors, CreateSelect2, WorkflowMakerForm, $q, JobTemplate,
Empty, PromptService, Rest) {
'$state', 'ProcessErrors', 'CreateSelect2', '$q', 'JobTemplateModel',
'Empty', 'PromptService', 'Rest', 'TemplatesStrings',
function($scope, WorkflowService, GetBasePath, TemplatesService,
$state, ProcessErrors, CreateSelect2, $q, JobTemplate,
Empty, PromptService, Rest, TemplatesStrings) {
let form = WorkflowMakerForm();
let promptWatcher, surveyQuestionWatcher;
$scope.strings = TemplatesStrings;
$scope.workflowMakerFormConfig = {
nodeMode: "idle",
activeTab: "jobs",
@@ -75,15 +76,19 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
// Create the node
let sendableNodeData = {
unified_job_template: params.node.unifiedJobTemplate.id,
credential: _.get(params, 'node.originalNodeObj.credential') || null
extra_data: {},
inventory: null,
job_type: null,
job_tags: null,
skip_tags: null,
limit: null,
diff_mode: null,
verbosity: null,
credential: null
};
if (_.has(params, 'node.promptData.extraVars')) {
if (_.get(params, 'node.promptData.launchConf.defaults.extra_vars')) {
if (!sendableNodeData.extra_data) {
sendableNodeData.extra_data = {};
}
const defaultVars = jsyaml.safeLoad(params.node.promptData.launchConf.defaults.extra_vars);
// Only include extra vars that differ from the template default vars
@@ -184,7 +189,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
params.node.isNew = false;
continueRecursing(data.data.id);
}, function(error) {
ProcessErrors($scope, error.data, error.status, form, {
ProcessErrors($scope, error.data, error.status, null, {
hdr: 'Error!',
msg: 'Failed to add workflow node. ' +
'POST returned status: ' +
@@ -403,7 +408,11 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
$q.all(associatePromises.concat(credentialPromises))
.then(function() {
$scope.closeDialog();
}).catch(({data, status}) => {
ProcessErrors($scope, data, status, null);
});
}).catch(({data, status}) => {
ProcessErrors($scope, data, status, null);
});
};
@@ -552,6 +561,8 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
}
$scope.promptData = null;
$scope.selectedTemplateInvalid = false;
$scope.showPromptButton = false;
// Reset the edgeConflict flag
resetEdgeConflict();
@@ -647,6 +658,12 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides);
if ((!$scope.nodeBeingEdited.unifiedJobTemplate.inventory && !launchConf.ask_inventory_on_launch) || !$scope.nodeBeingEdited.unifiedJobTemplate.project) {
$scope.selectedTemplateInvalid = true;
} else {
$scope.selectedTemplateInvalid = false;
}
if (!launchConf.survey_enabled &&
!launchConf.ask_inventory_on_launch &&
!launchConf.ask_credential_on_launch &&
@@ -658,15 +675,17 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
!launchConf.ask_diff_mode_on_launch &&
!launchConf.survey_enabled &&
!launchConf.credential_needed_to_start &&
!launchConf.inventory_needed_to_start &&
launchConf.passwords_needed_to_start.length === 0 &&
launchConf.variables_needed_to_start.length === 0) {
$scope.showPromptButton = false;
$scope.promptModalMissingReqFields = false;
} else {
$scope.showPromptButton = true;
if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeBeingEdited.originalNodeObj.summary_fields.inventory')) {
$scope.promptModalMissingReqFields = true;
} else {
$scope.promptModalMissingReqFields = false;
}
if (responses[1].data.survey_enabled) {
@@ -716,38 +735,42 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
});
}
if ($scope.nodeBeingEdited.unifiedJobTemplate.type === "job_template") {
if (_.get($scope, 'nodeBeingEdited.unifiedJobTemplate')) {
if (_.get($scope, 'nodeBeingEdited.unifiedJobTemplate.type') === "job_template") {
$scope.workflowMakerFormConfig.activeTab = "jobs";
}
$scope.selectedTemplate = $scope.nodeBeingEdited.unifiedJobTemplate;
if ($scope.selectedTemplate.unified_job_type) {
switch ($scope.selectedTemplate.unified_job_type) {
case "job":
$scope.workflowMakerFormConfig.activeTab = "jobs";
break;
case "project_update":
$scope.workflowMakerFormConfig.activeTab = "project_sync";
break;
case "inventory_update":
$scope.workflowMakerFormConfig.activeTab = "inventory_sync";
break;
}
} else if ($scope.selectedTemplate.type) {
switch ($scope.selectedTemplate.type) {
case "job_template":
$scope.workflowMakerFormConfig.activeTab = "jobs";
break;
case "project":
$scope.workflowMakerFormConfig.activeTab = "project_sync";
break;
case "inventory_source":
$scope.workflowMakerFormConfig.activeTab = "inventory_sync";
break;
}
}
} else {
$scope.workflowMakerFormConfig.activeTab = "jobs";
}
$scope.selectedTemplate = $scope.nodeBeingEdited.unifiedJobTemplate;
if ($scope.selectedTemplate.unified_job_type) {
switch ($scope.selectedTemplate.unified_job_type) {
case "job":
$scope.workflowMakerFormConfig.activeTab = "jobs";
break;
case "project_update":
$scope.workflowMakerFormConfig.activeTab = "project_sync";
break;
case "inventory_update":
$scope.workflowMakerFormConfig.activeTab = "inventory_sync";
break;
}
} else if ($scope.selectedTemplate.type) {
switch ($scope.selectedTemplate.type) {
case "job_template":
$scope.workflowMakerFormConfig.activeTab = "jobs";
break;
case "project":
$scope.workflowMakerFormConfig.activeTab = "project_sync";
break;
case "inventory_source":
$scope.workflowMakerFormConfig.activeTab = "inventory_sync";
break;
}
}
let siblingConnectionTypes = WorkflowService.getSiblingConnectionTypes({
tree: $scope.treeData.data,
parentId: parent.id,
@@ -759,7 +782,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
switch($scope.nodeBeingEdited.edgeType) {
case "always":
$scope.edgeType = {label: "Always", value: "always"};
if (siblingConnectionTypes.length === 1 && _.includes(siblingConnectionTypes, "always")) {
if (siblingConnectionTypes.length === 1 && _.includes(siblingConnectionTypes, "always") || $scope.nodeBeingEdited.isRoot) {
edgeDropdownOptions = ["always"];
}
break;
@@ -794,7 +817,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
$scope.nodeBeingEdited.unifiedJobTemplate = _.clone(data.data.results[0]);
finishConfiguringEdit();
}, function(error) {
ProcessErrors($scope, error.data, error.status, form, {
ProcessErrors($scope, error.data, error.status, null, {
hdr: 'Error!',
msg: 'Failed to get unified job template. GET returned ' +
'status: ' + error.status
@@ -946,8 +969,6 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
$scope.templateManuallySelected = function(selectedTemplate) {
$scope.selectedTemplate = angular.copy(selectedTemplate);
if (selectedTemplate.type === "job_template") {
let jobTemplate = new JobTemplate();
@@ -955,6 +976,14 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
.then((responses) => {
let launchConf = responses[1].data;
if ((!selectedTemplate.inventory && !launchConf.ask_inventory_on_launch) || !selectedTemplate.project) {
$scope.selectedTemplateInvalid = true;
} else {
$scope.selectedTemplateInvalid = false;
}
$scope.selectedTemplate = angular.copy(selectedTemplate);
if (!launchConf.survey_enabled &&
!launchConf.ask_inventory_on_launch &&
!launchConf.ask_credential_on_launch &&
@@ -966,15 +995,17 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
!launchConf.ask_diff_mode_on_launch &&
!launchConf.survey_enabled &&
!launchConf.credential_needed_to_start &&
!launchConf.inventory_needed_to_start &&
launchConf.passwords_needed_to_start.length === 0 &&
launchConf.variables_needed_to_start.length === 0) {
$scope.showPromptButton = false;
$scope.promptModalMissingReqFields = false;
} else {
$scope.showPromptButton = true;
if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory')) {
$scope.promptModalMissingReqFields = true;
} else {
$scope.promptModalMissingReqFields = false;
}
if (launchConf.survey_enabled) {
@@ -1028,7 +1059,10 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
});
} else {
// TODO - clear out prompt data?
$scope.selectedTemplate = angular.copy(selectedTemplate);
$scope.selectedTemplateInvalid = false;
$scope.showPromptButton = false;
$scope.promptModalMissingReqFields = false;
}
};
@@ -1114,7 +1148,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
buildTreeFromNodes();
}
}, function(error){
ProcessErrors($scope, error.data, error.status, form, {
ProcessErrors($scope, error.data, error.status, null, {
hdr: 'Error!',
msg: 'Failed to get workflow job template nodes. GET returned ' +
'status: ' + error.status

View File

@@ -1,183 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name forms.function:JobTemplate
* @description This form is for adding/editing a Job Template
*/
export default ['NotificationsList', 'i18n', '$rootScope', function(NotificationsList, i18n, $rootScope) {
return function() {
var WorkflowMakerFormObject = {
addTitle: '',
editTitle: '',
name: 'workflow_maker',
basePath: 'job_templates',
tabs: false,
cancelButton: false,
showHeader: false,
fields: {
edgeType: {
label: i18n._('Type'),
type: 'radio_group',
ngShow: 'selectedTemplate && edgeFlags.showTypeOptions',
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
options: [
{
label: i18n._('On&nbsp;Success'),
value: 'success',
ngShow: '!edgeFlags.typeRestriction || edgeFlags.typeRestriction === "successFailure"'
},
{
label: i18n._('On&nbsp;Failure'),
value: 'failure',
ngShow: '!edgeFlags.typeRestriction || edgeFlags.typeRestriction === "successFailure"'
},
{
label: i18n._('Always'),
value: 'always',
ngShow: '!edgeFlags.typeRestriction || edgeFlags.typeRestriction === "always"'
}
],
awRequiredWhen: {
reqExpression: 'edgeFlags.showTypeOptions'
}
},
credential: {
label: i18n._('Credential'),
type: 'lookup',
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
requiredErrorMsg: i18n._("Please select a Credential."),
class: 'Form-formGroup--fullWidth',
awPopOver: "<p>" + i18n._("Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " +
" the username and SSH key or password that Ansible will need to log into the remote hosts.") + "</p>",
dataTitle: i18n._('Credential'),
dataPlacement: 'right',
dataContainer: "body",
ngShow: "selectedTemplate.ask_credential_on_launch",
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
awRequiredWhen: {
reqExpression: 'selectedTemplate && selectedTemplate.ask_credential_on_launch'
}
},
inventory: {
label: i18n._('Inventory'),
type: 'lookup',
sourceModel: 'inventory',
sourceField: 'name',
list: 'OrganizationList',
basePath: 'organization',
ngClick: 'lookUpInventory()',
requiredErrorMsg: i18n._("Please select an Inventory."),
class: 'Form-formGroup--fullWidth',
awPopOver: "<p>" + i18n._("Select the inventory containing the hosts you want this job to manage.") + "</p>",
dataTitle: i18n._('Inventory'),
dataPlacement: 'right',
dataContainer: "body",
ngShow: "selectedTemplate.ask_inventory_on_launch",
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
awRequiredWhen: {
reqExpression: 'selectedTemplate && selectedTemplate.ask_inventory_on_launch'
}
},
job_type: {
label: i18n._('Job Type'),
type: 'select',
ngOptions: 'type.label for type in job_type_options track by type.value',
"default": 0,
class: 'Form-formGroup--fullWidth',
awPopOver: "<p>" + i18n.sprintf(i18n._("When this template is submitted as a job, setting the type to %s will execute the playbook, running tasks " +
" on the selected hosts."), "<em>run</em>") + "</p> <p>" +
i18n.sprintf(i18n._("Setting the type to %s will not execute the playbook. Instead, %s will check playbook " +
" syntax, test environment setup and report problems."), "<em>check</em>", "<code>ansible</code>") + "</p> <p>" +
i18n.sprintf(i18n._("Setting the type to %s will execute the playbook and store any " +
" scanned facts for use with " + $rootScope.BRAND_NAME + "'s System Tracking feature."), "<em>scan</em>") + "</p>",
dataTitle: i18n._('Job Type'),
dataPlacement: 'right',
dataContainer: "body",
ngShow: "selectedTemplate.ask_job_type_on_launch",
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
awRequiredWhen: {
reqExpression: 'selectedTemplate && selectedTemplate.ask_job_type_on_launch'
}
},
limit: {
label: i18n._('Limit'),
type: 'text',
class: 'Form-formGroup--fullWidth',
awPopOver: "<p>" + i18n.sprintf(i18n._("Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " +
"Multiple patterns can be separated by %s %s or %s"), "&#59;", "&#58;", "&#44;") + "</p><p>" +
i18n.sprintf(i18n._("For more information and examples see " +
"%sthe Patterns topic at docs.ansible.com%s."), "<a href=\"http://docs.ansible.com/intro_patterns.html\" target=\"_blank\">", "</a>") + "</p>",
dataTitle: i18n._('Limit'),
dataPlacement: 'right',
dataContainer: "body",
ngShow: "selectedTemplate.ask_limit_on_launch",
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
},
job_tags: {
label: i18n._('Job Tags'),
type: 'textarea',
rows: 5,
'elementClass': 'Form-textInput',
class: 'Form-formGroup--fullWidth',
awPopOver: "<p>" + i18n._("Provide a comma separated list of tags.") + "</p>\n" +
"<p>" + i18n._("Tags are useful when you have a large playbook, and you want to run a specific part of a play or task.") + "</p>" +
"<p>" + i18n._("Consult the Ansible documentation for further details on the usage of tags.") + "</p>",
dataTitle: i18n._("Job Tags"),
dataPlacement: "right",
dataContainer: "body",
ngShow: "selectedTemplate.ask_tags_on_launch",
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
},
skip_tags: {
label: i18n._('Skip Tags'),
type: 'textarea',
rows: 5,
'elementClass': 'Form-textInput',
class: 'Form-formGroup--fullWidth',
awPopOver: "<p>" + i18n._("Provide a comma separated list of tags.") + "</p>\n" +
"<p>" + i18n._("Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task.") + "</p>" +
"<p>" + i18n._("Consult the Ansible documentation for further details on the usage of tags.") + "</p>",
dataTitle: i18n._("Skip Tags"),
dataPlacement: "right",
dataContainer: "body",
ngShow: "selectedTemplate.ask_skip_tags_on_launch",
ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
}
},
buttons: {
cancel: {
ngClick: 'cancelNodeForm()',
ngShow: '(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
},
close: {
ngClick: 'cancelNodeForm()',
ngShow: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
},
select: {
ngClick: 'saveNodeForm()',
ngDisabled: "workflow_maker_form.$invalid || !selectedTemplate",
ngShow: '(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
}
}
};
var itm;
for (itm in WorkflowMakerFormObject.related) {
if (WorkflowMakerFormObject.related[itm].include === "NotificationsList") {
WorkflowMakerFormObject.related[itm] = NotificationsList;
WorkflowMakerFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list
}
}
return WorkflowMakerFormObject;
};
}];

View File

@@ -93,31 +93,35 @@
<div id="workflow-project-sync-list" ui-view="projectSyncList" ng-show="workflowMakerFormConfig.activeTab === 'project_sync'"></div>
<div id="workflow-inventory-sync-list" ui-view="inventorySyncList" ng-show="workflowMakerFormConfig.activeTab === 'inventory_sync'"></div>
</div>
<div ng-show="selectedTemplate">
<div class="form-group Form-formGroup Form-formGroup--singleColumn">
<label for="verbosity" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">RUN</span>
</label>
<div>
<select
id="workflow_node_edge"
ng-options="v as v.label for v in edgeTypeOptions track by v.value"
ng-model="edgeType"
class="form-control Form-dropDown"
name="edgeType"
tabindex="-1"
aria-hidden="true">
</select>
</div>
<div ng-if="selectedTemplate && selectedTemplateInvalid">
<div class="WorkflowMaker-invalidJobTemplateWarning">
<span class="fa fa-warning"></span>
<span>{{:: strings.get('workflows.INVALID_JOB_TEMPLATE') }}</span>
</div>
<div class="buttons Form-buttons" id="workflow_maker_controls">
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton" ng-click="openPromptModal()"> Prompt</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> Cancel</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_btn" ng-show="!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> Close</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="confirmNodeForm()" ng-disabled="workflow_maker_form.$invalid || !selectedTemplate || promptModalMissingReqFields" disabled="disabled"> Select</button>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid">
<label for="verbosity" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">RUN</span>
</label>
<div>
<select
id="workflow_node_edge"
ng-options="v as v.label for v in edgeTypeOptions track by v.value"
ng-model="edgeType"
class="form-control Form-dropDown"
name="edgeType"
tabindex="-1"
aria-hidden="true">
</select>
</div>
</div>
<div class="buttons Form-buttons" id="workflow_maker_controls">
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton" ng-click="openPromptModal()"> Prompt</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> Cancel</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_btn" ng-show="!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> Close</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate) && !selectedTemplateInvalid" ng-click="confirmNodeForm()" ng-disabled="!selectedTemplate || promptModalMissingReqFields"> Select</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,13 @@
.Workflow-warning {
float: right;
margin-right: 20px;
color: @default-interface-txt;
}
.Workflow-warningIcon {
color: @default-warning;
margin-right: 5px;
}
.Workflow-warningLink {
color: @default-link;
cursor: pointer;
}

View File

@@ -145,7 +145,8 @@ describe('Controller: WorkflowAdd', () => {
labels: undefined,
organization: undefined,
variables: undefined,
extra_vars: undefined
extra_vars: undefined,
allow_simultaneous: undefined
});
});