job template labels ui implementation

This commit is contained in:
John Mitchell
2016-04-13 14:16:15 -04:00
parent d403c5fb3d
commit 9d9e2b254c
14 changed files with 591 additions and 126 deletions

View File

@@ -38,6 +38,8 @@
.RoleList-deleteContainer { .RoleList-deleteContainer {
border: 1px solid @default-second-border; border: 1px solid @default-second-border;
border-left-color: @default-bg;
background-color: @default-bg;
border-top-right-radius: 5px; border-top-right-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
padding: 0 5px; padding: 0 5px;

View File

@@ -189,6 +189,18 @@ export default
dataPlacement: "right", dataPlacement: "right",
dataContainer: "body" dataContainer: "body"
}, },
labels: {
label: 'Labels',
type: 'select',
ngOptions: 'label.label for label in labelOptions track by label.value',
multiSelect: true,
addRequired: false,
editRequired: false,
dataTitle: 'Labels',
dataPlacement: 'right',
awPopOver: 'You can add labels to a job template to aid in filtering',
dataContainer: 'body'
},
variables: { variables: {
label: 'Extra Variables', label: 'Extra Variables',
type: 'textarea', type: 'textarea',

View File

@@ -11,14 +11,14 @@
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList',
'LookUpInit', 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON', 'LookUpInit', 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON',
'CallbackHelpInit', 'initSurvey', 'Prompt', 'GetChoices', '$state', 'CallbackHelpInit', 'initSurvey', 'Prompt', 'GetChoices', '$state',
'CreateSelect2', 'CreateSelect2', '$q',
function( function(
Refresh, $filter, $scope, $rootScope, $compile, Refresh, $filter, $scope, $rootScope, $compile,
$location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, InventoryList, ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, InventoryList,
CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait,
Empty, ToJSON, CallbackHelpInit, SurveyControllerInit, Prompt, GetChoices, Empty, ToJSON, CallbackHelpInit, SurveyControllerInit, Prompt, GetChoices,
$state, CreateSelect2 $state, CreateSelect2, $q
) { ) {
ClearScope(); ClearScope();
@@ -113,7 +113,7 @@
} }
$scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () { $scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () {
selectCount++; selectCount++;
if (selectCount === 2) { if (selectCount === 3) {
var verbosity; var verbosity;
// this sets the default options for the selects as specified by the controller. // this sets the default options for the selects as specified by the controller.
for (verbosity in $scope.verbosity_options) { for (verbosity in $scope.verbosity_options) {
@@ -145,7 +145,11 @@
element:'#job_templates_job_type', element:'#job_templates_job_type',
multiple: false multiple: false
}); });
CreateSelect2({
element:'#job_templates_labels',
multiple: true,
addNew: true
});
CreateSelect2({ CreateSelect2({
element:'#playbook-select', element:'#playbook-select',
multiple: false multiple: false
@@ -178,6 +182,21 @@
callback: 'choicesReadyVerbosity' callback: 'choicesReadyVerbosity'
}); });
Rest.setUrl('api/v1/labels');
Rest.get()
.success(function (data) {
$scope.labelOptions = data.results
.map((i) => ({label: i.name, value: i.id}));
$scope.$emit("choicesReadyVerbosity");
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to get labels. GET returned ' +
'status: ' + status
});
});
// Update playbook select whenever project value changes // Update playbook select whenever project value changes
selectPlaybook = function (oldValue, newValue) { selectPlaybook = function (oldValue, newValue) {
var url; var url;
@@ -265,12 +284,6 @@
} }
}; };
// $scope.selectPlaybookUnregister = $scope.$watch('project_name', function (newval, oldval) {
// selectPlaybook(oldval, newval);
// checkSCMStatus(oldval, newval);
// });
// Register a watcher on project_name // Register a watcher on project_name
if ($scope.selectPlaybookUnregister) { if ($scope.selectPlaybookUnregister) {
$scope.selectPlaybookUnregister(); $scope.selectPlaybookUnregister();
@@ -311,14 +324,126 @@
} }
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
Wait('stop'); Wait('stop');
if (data.related && data.related.callback) { if (data.related &&
Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is:</p>'+ data.related.callback) {
'<p style="padding: 10px 0;"><strong>' + $scope.callback_server_path + data.related.callback + '</strong></p>'+ Alert('Callback URL',
'<p>The host configuration key is: <strong>' + $filter('sanitize')(data.host_config_key) + '</strong></p>', 'alert-info', saveCompleted, null, null, null, true); `
} <p>Host callbacks are enabled for this template. The callback URL is:</p>
else { <p style=\"padding: 10px 0;\">
saveCompleted(); <strong>
${$scope.callback_server_path}
${data.related.callback}
</string>
</p>
<p>The host configuration key is:
<strong>
${$filter('sanitize')(data.host_config_key)}
</string>
</p>
`,
'alert-info', saveCompleted, null, null,
null, true);
} }
var orgDefer = $q.defer();
var associationDefer = $q.defer();
Rest.setUrl(data.related.labels);
var currentLabels = Rest.get()
.then(function(data) {
return data.data.results
.map(val => val.id);
});
currentLabels.then(function (current) {
var labelsToAdd = $scope.labels
.map(val => val.value);
var labelsToDisassociate = current
.filter(val => labelsToAdd
.indexOf(val) === -1)
.map(val => ({id: val, disassociate: true}));
var labelsToAssociate = labelsToAdd
.filter(val => current
.indexOf(val) === -1)
.map(val => ({id: val, associate: true}));
var pass = labelsToDisassociate
.concat(labelsToAssociate);
associationDefer.resolve(pass);
});
Rest.setUrl(GetBasePath("organizations"));
Rest.get()
.success(function(data) {
orgDefer.resolve(data.results[0].id);
});
orgDefer.promise.then(function(orgId) {
var toPost = [];
$scope.newLabels = $scope.newLabels
.map(function(i, val) {
val.organization = orgId;
return val;
});
$scope.newLabels.each(function(i, val) {
toPost.push(val);
});
associationDefer.promise.then(function(arr) {
toPost = toPost
.concat(arr);
Rest.setUrl(data.related.labels);
var defers = [];
for (var i = 0; i < toPost.length; i++) {
defers.push(Rest.post(toPost[i]));
}
$q.all(defers)
.then(function() {
$scope.addedItem = data.id;
Refresh({
scope: $scope,
set: 'job_templates',
iterator: 'job_template',
url: $scope.current_url
});
if($scope.survey_questions &&
$scope.survey_questions.length > 0){
//once the job template information
// is saved we submit the survey
// info to the correct endpoint
var url = data.url+ 'survey_spec/';
Rest.setUrl(url);
Rest.post({ name: $scope.survey_name,
description: $scope.survey_description,
spec: $scope.survey_questions })
.success(function () {
Wait('stop');
})
.error(function (data,
status) {
ProcessErrors(
$scope,
data,
status,
form,
{
hdr: 'Error!',
msg: 'Failed to add new ' +
'survey. Post returned ' +
'status: ' +
status
});
});
}
saveCompleted();
});
});
});
}); });
// Save // Save
@@ -327,11 +452,13 @@
$scope.invalid_survey = false; $scope.invalid_survey = false;
// users can't save a survey with a scan job // users can't save a survey with a scan job
if($scope.job_type.value === "scan" && $scope.survey_enabled === true){ if($scope.job_type.value === "scan" &&
$scope.survey_enabled === true){
$scope.survey_enabled = false; $scope.survey_enabled = false;
} }
// Can't have a survey enabled without a survey // Can't have a survey enabled without a survey
if($scope.survey_enabled === true && $scope.survey_exists!==true){ if($scope.survey_enabled === true &&
$scope.survey_exists!==true){
$scope.survey_enabled = false; $scope.survey_enabled = false;
} }
@@ -341,70 +468,61 @@
try { try {
for (fld in form.fields) { for (fld in form.fields) {
if (form.fields[fld].type === 'select' && fld !== 'playbook') { if (form.fields[fld].type === 'select' &&
fld !== 'playbook') {
data[fld] = $scope[fld].value; data[fld] = $scope[fld].value;
} else { } else {
if (fld !== 'variables' && fld !== 'survey') { if (fld !== 'variables' &&
fld !== 'survey') {
data[fld] = $scope[fld]; data[fld] = $scope[fld];
} }
} }
} }
data.extra_vars = ToJSON($scope.parseType, $scope.variables, true); data.extra_vars = ToJSON($scope.parseType,
if(data.job_type === 'scan' && $scope.default_scan === true){ $scope.variables, true);
if(data.job_type === 'scan' &&
$scope.default_scan === true){
data.project = ""; data.project = "";
data.playbook = ""; data.playbook = "";
} }
// We only want to set the survey_enabled flag to true for this job template if a survey exists // We only want to set the survey_enabled flag to
// and it's been enabled. By default, survey_enabled is explicitly set to true but if no survey // true for this job template if a survey exists
// is created then we don't want it enabled. // and it's been enabled. By default,
data.survey_enabled = ($scope.survey_enabled && $scope.survey_exists) ? $scope.survey_enabled : false; // survey_enabled is explicitly set to true but
// if no survey is created then we don't want
// it enabled.
data.survey_enabled = ($scope.survey_enabled &&
$scope.survey_exists) ? $scope.survey_enabled : false;
$scope.newLabels = $("#job_templates_labels > option")
.filter("[data-select2-tag=true]")
.map((i, val) => ({name: $(val).text()}));
Rest.setUrl(defaultUrl); Rest.setUrl(defaultUrl);
Rest.post(data) Rest.post(data)
.success(function(data) { .success(function(data) {
$scope.$emit('templateSaveSuccess', data); $scope.$emit('templateSaveSuccess',
data);
$scope.addedItem = data.id;
Refresh({
scope: $scope,
set: 'job_templates',
iterator: 'job_template',
url: $scope.current_url
});
if($scope.survey_questions && $scope.survey_questions.length > 0){
//once the job template information is saved we submit the survey info to the correct endpoint
var url = data.url+ 'survey_spec/';
Rest.setUrl(url);
Rest.post({ name: $scope.survey_name, description: $scope.survey_description, spec: $scope.survey_questions })
.success(function () {
Wait('stop');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to add new survey. Post returned status: ' + status });
});
}
}) })
.error(function (data, status) { .error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!', ProcessErrors($scope, data, status, form,
msg: 'Failed to add new job template. POST returned status: ' + status {
}); hdr: 'Error!',
msg: 'Failed to add new job ' +
'template. POST returned status: ' +
status
});
}); });
} catch (err) { } catch (err) {
Wait('stop'); Wait('stop');
Alert("Error", "Error parsing extra variables. Parser returned: " + err); Alert("Error", "Error parsing extra variables. " +
"Parser returned: " + err);
} }
}; };
$scope.formCancel = function () { $scope.formCancel = function () {
$state.transitionTo('jobTemplates'); $state.transitionTo('jobTemplates');
}; };
} }
]; ];

View File

@@ -21,7 +21,7 @@ export default
'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate',
'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit', 'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit',
'PlaybookRun' , 'initSurvey', '$state', 'CreateSelect2', 'PlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
'ToggleNotification', 'NotificationsListInit', 'ToggleNotification', 'NotificationsListInit', '$q',
function( function(
$filter, $scope, $rootScope, $compile, $filter, $scope, $rootScope, $compile,
$location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
@@ -31,7 +31,7 @@ export default
Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit, Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit,
JobsControllerInit, JobsListUpdate, GetChoices, SchedulesListInit, JobsControllerInit, JobsListUpdate, GetChoices, SchedulesListInit,
SchedulesList, CallbackHelpInit, PlaybookRun, SurveyControllerInit, $state, SchedulesList, CallbackHelpInit, PlaybookRun, SurveyControllerInit, $state,
CreateSelect2, ToggleNotification, NotificationsListInit CreateSelect2, ToggleNotification, NotificationsListInit, $q
) { ) {
ClearScope(); ClearScope();
@@ -232,11 +232,6 @@ export default
multiple: false multiple: false
}); });
CreateSelect2({
element:'#job_templates_verbosity',
multiple: false
});
for (var set in relatedSets) { for (var set in relatedSets) {
$scope.search(relatedSets[set].iterator); $scope.search(relatedSets[set].iterator);
} }
@@ -353,7 +348,7 @@ export default
} }
$scope.removeChoicesReady = $scope.$on('choicesReady', function() { $scope.removeChoicesReady = $scope.$on('choicesReady', function() {
choicesCount++; choicesCount++;
if (choicesCount === 4) { if (choicesCount === 5) {
$scope.$emit('LoadJobs'); $scope.$emit('LoadJobs');
} }
}); });
@@ -392,6 +387,41 @@ export default
callback: 'choicesReady' callback: 'choicesReady'
}); });
Rest.setUrl('api/v1/labels');
Wait("start");
Rest.get()
.success(function (data) {
$scope.labelOptions = data.results
.map((i) => ({label: i.name, value: i.id}));
$scope.$emit("choicesReady");
Rest.setUrl(defaultUrl + $state.params.template_id +
"/labels");
Rest.get()
.success(function(data) {
var opts = data.results
.map(i => ({id: i.id + "",
test: i.name}));
CreateSelect2({
element:'#job_templates_labels',
multiple: true,
addNew: true,
opts: opts
});
Wait("stop");
});
CreateSelect2({
element:'#job_templates_verbosity',
multiple: false
});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to get labels. GET returned ' +
'status: ' + status
});
});
function saveCompleted() { function saveCompleted() {
$state.go('jobTemplates', null, {reload: true}); $state.go('jobTemplates', null, {reload: true});
} }
@@ -401,34 +431,105 @@ export default
} }
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
Wait('stop'); Wait('stop');
if ($scope.allow_callbacks && ($scope.host_config_key !== master.host_config_key || $scope.callback_url !== master.callback_url)) { if (data.related &&
if (data.related && data.related.callback) { data.related.callback) {
Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is:</p>'+ Alert('Callback URL',
'<p style="padding: 10px 0;"><strong>' + $scope.callback_server_path + data.related.callback + '</strong></p>'+ `
'<p>The host configuration key is: <strong>' + $filter('sanitize')(data.host_config_key) + '</strong></p>', 'alert-info', saveCompleted, null, null, null, true); <p>Host callbacks are enabled for this template. The callback URL is:</p>
} <p style=\"padding: 10px 0;\">
else { <strong>
saveCompleted(); ${$scope.callback_server_path}
} ${data.related.callback}
} </string>
else { </p>
saveCompleted(); <p>The host configuration key is:
<strong>
${$filter('sanitize')(data.host_config_key)}
</string>
</p>
`,
'alert-info', saveCompleted, null, null,
null, true);
} }
var orgDefer = $q.defer();
var associationDefer = $q.defer();
Rest.setUrl(data.related.labels);
var currentLabels = Rest.get()
.then(function(data) {
return data.data.results
.map(val => val.id);
});
currentLabels.then(function (current) {
var labelsToAdd = $scope.labels
.map(val => val.value);
var labelsToDisassociate = current
.filter(val => labelsToAdd
.indexOf(val) === -1)
.map(val => ({id: val, disassociate: true}));
var labelsToAssociate = labelsToAdd
.filter(val => current
.indexOf(val) === -1)
.map(val => ({id: val, associate: true}));
var pass = labelsToDisassociate
.concat(labelsToAssociate);
associationDefer.resolve(pass);
});
Rest.setUrl(GetBasePath("organizations"));
Rest.get()
.success(function(data) {
orgDefer.resolve(data.results[0].id);
});
orgDefer.promise.then(function(orgId) {
var toPost = [];
$scope.newLabels = $scope.newLabels
.map(function(i, val) {
val.organization = orgId;
return val;
});
$scope.newLabels.each(function(i, val) {
toPost.push(val);
});
associationDefer.promise.then(function(arr) {
toPost = toPost
.concat(arr);
Rest.setUrl(data.related.labels);
var defers = [];
for (var i = 0; i < toPost.length; i++) {
defers.push(Rest.post(toPost[i]));
}
$q.all(defers)
.then(function() {
saveCompleted();
});
});
});
}); });
// Save changes to the parent // Save changes to the parent
// Save
$scope.formSave = function () { $scope.formSave = function () {
var fld, data = {}; var fld, data = {};
$scope.invalid_survey = false; $scope.invalid_survey = false;
// users can't save a survey with a scan job // users can't save a survey with a scan job
if($scope.job_type.value === "scan" && $scope.survey_enabled === true){ if($scope.job_type.value === "scan" &&
$scope.survey_enabled === true){
$scope.survey_enabled = false; $scope.survey_enabled = false;
} }
// Can't have a survey enabled without a survey // Can't have a survey enabled without a survey
if($scope.survey_enabled === true && $scope.survey_exists!==true){ if($scope.survey_enabled === true &&
$scope.survey_exists!==true){
$scope.survey_enabled = false; $scope.survey_enabled = false;
} }
@@ -437,21 +538,38 @@ export default
Wait('start'); Wait('start');
try { try {
// Make sure we have valid variable data
data.extra_vars = ToJSON($scope.parseType, $scope.variables, true);
if(data.extra_vars === undefined ){
throw 'undefined variables';
}
for (fld in form.fields) { for (fld in form.fields) {
if (form.fields[fld].type === 'select' && fld !== 'playbook') { if (form.fields[fld].type === 'select' &&
fld !== 'playbook') {
data[fld] = $scope[fld].value; data[fld] = $scope[fld].value;
} else { } else {
if (fld !== 'variables' && fld !== 'callback_url') { if (fld !== 'variables' &&
fld !== 'survey') {
data[fld] = $scope[fld]; data[fld] = $scope[fld];
} }
} }
} }
Rest.setUrl(defaultUrl + id + '/'); data.extra_vars = ToJSON($scope.parseType,
$scope.variables, true);
if(data.job_type === 'scan' &&
$scope.default_scan === true){
data.project = "";
data.playbook = "";
}
// We only want to set the survey_enabled flag to
// true for this job template if a survey exists
// and it's been enabled. By default,
// survey_enabled is explicitly set to true but
// if no survey is created then we don't want
// it enabled.
data.survey_enabled = ($scope.survey_enabled &&
$scope.survey_exists) ? $scope.survey_enabled : false;
$scope.newLabels = $("#job_templates_labels > option")
.filter("[data-select2-tag=true]")
.map((i, val) => ({name: $(val).text()}));
Rest.setUrl(defaultUrl + $state.params.template_id);
Rest.put(data) Rest.put(data)
.success(function (data) { .success(function (data) {
$scope.$emit('templateSaveSuccess', data); $scope.$emit('templateSaveSuccess', data);
@@ -460,12 +578,11 @@ export default
ProcessErrors($scope, data, status, form, { hdr: 'Error!', ProcessErrors($scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to update job template. PUT returned status: ' + status }); msg: 'Failed to update job template. PUT returned status: ' + status });
}); });
} catch (err) { } catch (err) {
Wait('stop'); Wait('stop');
Alert("Error", "Error parsing extra variables. Parser returned: " + err); Alert("Error", "Error parsing extra variables. " +
"Parser returned: " + err);
} }
}; };
$scope.formCancel = function () { $scope.formCancel = function () {
@@ -474,7 +591,7 @@ export default
var defaultUrl = GetBasePath('job_templates') + $state.params.template_id; var defaultUrl = GetBasePath('job_templates') + $state.params.template_id;
Rest.setUrl(defaultUrl); Rest.setUrl(defaultUrl);
Rest.destroy() Rest.destroy()
.success(function(res){ .success(function(){
$state.go('jobTemplates', null, {reload: true, notify:true}); $state.go('jobTemplates', null, {reload: true, notify:true});
}) })
.error(function(res, status){ .error(function(res, status){

View File

@@ -0,0 +1,74 @@
/** @define LabelList */
@import "../shared/branding/colors.default.less";
.LabelList {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
.LabelList-tagContainer {
display: flex;
max-width: 100%;
}
.LabelList-tag {
border-radius: 5px;
padding: 2px 10px;
margin: 4px 0px;
border: 1px solid @default-second-border;
font-size: 12px;
color: @default-interface-txt;
text-transform: uppercase;
background-color: @default-bg;
margin-right: 5px;
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.LabelList-tag--deletable {
margin-right: 0px;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border-right: 0;
max-wdith: ~"calc(100% - 23px)";
}
.LabelList-deleteContainer {
border: 1px solid @default-second-border;
border-left-color: @default-bg;
background-color: @default-bg;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
padding: 0 5px;
margin: 4px 0px;
margin-right: 5px;
align-items: center;
display: flex;
cursor: pointer;
}
.LabelList-tagDelete {
font-size: 13px;
color: @default-icon;
}
.LabelList-name {
flex: initial;
max-width: 100%;
}
.LabelList-tag--deletable > .LabelList-name {
max-width: ~"calc(100% - 23px)";
}
.LabelList-deleteContainer:hover, {
border-color: @default-err;
background-color: @default-err;
}
.LabelList-deleteContainer:hover > .LabelList-tagDelete {
color: @default-bg;
}

View File

@@ -0,0 +1,45 @@
/* jshint unused: vars */
export default
[ 'templateUrl',
'Wait',
'Rest',
'GetBasePath',
'ProcessErrors',
'Prompt',
function(templateUrl, Wait, Rest, GetBasePath, ProcessErrors, Prompt) {
return {
restrict: 'E',
scope: false,
templateUrl: templateUrl('job-templates/labels/labelsList'),
link: function(scope, element, attrs) {
scope.labels = scope.
job_template.summary_fields.labels;
scope.deleteLabel = function(templateId, templateName, labelId, labelName) {
var action = function () {
$('#prompt-modal').modal('hide');
Wait('start');
var url = GetBasePath("job_templates") + templateId + "/labels/";
Rest.setUrl(url);
Rest.post({"disassociate": true, "id": labelId})
.success(function () {
Wait('stop');
scope.search("job_template");
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Could not disacssociate label from JT. Call to ' + url + ' failed. DELETE returned status: ' + status });
});
};
Prompt({
hdr: 'Remove Label from ' + templateName,
body: '<div class="Prompt-bodyQuery">Confirm the removal of the <span class="Prompt-emphasis">' + labelName + '</span> label.</div>',
action: action,
actionText: 'REMOVE'
});
};
}
};
}
];

View File

@@ -0,0 +1,10 @@
<div class="LabelList-tagContainer"
ng-repeat="label in labels">
<div class="LabelList-tag LabelList-tag--deletable">
<span class="LabelList-name">{{ label.name }}</span>
</div>
<div class="LabelList-deleteContainer"
ng-click="deleteLabel(job_template.id, job_template.name, label.id, label.name)">
<i class="fa fa-times LabelList-tagDelete"></i>
</div>
</div>

View File

@@ -0,0 +1,11 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import labelsList from './labelsList.directive';
export default
angular.module('labels', [])
.directive('labelsList', labelsList);

View File

@@ -11,9 +11,10 @@ import jobTemplatesList from './list/main';
import jobTemplatesAdd from './add/main'; import jobTemplatesAdd from './add/main';
import jobTemplatesEdit from './edit/main'; import jobTemplatesEdit from './edit/main';
import jobTemplatesCopy from './copy/main'; import jobTemplatesCopy from './copy/main';
import labels from './labels/main';
export default export default
angular.module('jobTemplates', angular.module('jobTemplates',
[surveyMaker.name, jobTemplatesList.name, jobTemplatesAdd.name, [surveyMaker.name, jobTemplatesList.name, jobTemplatesAdd.name,
jobTemplatesEdit.name, jobTemplatesCopy.name]) jobTemplatesEdit.name, jobTemplatesCopy.name, labels.name])
.service('deleteJobTemplate', deleteJobTemplate); .service('deleteJobTemplate', deleteJobTemplate);

View File

@@ -23,19 +23,28 @@ export default
name: { name: {
key: true, key: true,
label: 'Name', label: 'Name',
columnClass: 'col-lg-3 col-md-3 col-sm-4 col-xs-4' columnClass: 'col-lg-2 col-md-2 col-sm-4 col-xs-9'
}, },
description: { description: {
label: 'Description', label: 'Description',
columnClass: 'col-lg-3 col-md-3 hidden-sm hidden-xs' columnClass: 'col-lg-2 hidden-md hidden-sm hidden-xs'
}, },
smart_status: { smart_status: {
label: 'Activity', label: 'Activity',
columnClass: 'List-tableCell col-lg-4 col-md-4 col-sm-5 col-xs-5', columnClass: 'List-tableCell col-lg-3 col-md-4 hidden-sm hidden-xs',
searchable: false, searchable: false,
nosort: true, nosort: true,
ngInclude: "'/static/partials/job-template-smart-status.html'", ngInclude: "'/static/partials/job-template-smart-status.html'",
type: 'template' type: 'template'
},
labels: {
label: 'Labels',
type: 'labels',
nosort: true,
columnClass: 'List-tableCell col-lg-3 col-md-3 hidden-sm hidden-xs',
searchType: 'related',
sourceModel: 'labels',
sourceField: 'name'
} }
}, },
@@ -52,7 +61,7 @@ export default
fieldActions: { fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-3', columnClass: 'col-lg-2 col-md-3 col-sm-3 col-xs-3',
submit: { submit: {
label: 'Launch', label: 'Launch',

View File

@@ -196,6 +196,8 @@
.TagSearch-deleteContainer { .TagSearch-deleteContainer {
border: 1px solid @default-second-border; border: 1px solid @default-second-border;
border-left-color: @default-bg;
background-color: @default-bg;
border-top-right-radius: 5px; border-top-right-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
padding: 0 5px; padding: 0 5px;

View File

@@ -3,9 +3,13 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', function(R
// parse the field config object to return // parse the field config object to return
// one of the searchTypes (for the left dropdown) // one of the searchTypes (for the left dropdown)
this.buildType = function (field, key, id) { this.buildType = function (field, key, id) {
var obj = {};
// build the value (key) // build the value (key)
var value; var value;
if (typeof(field.key) === String) { if (field.sourceModel && field.sourceField) {
value = field.sourceModel + '__' + field.sourceField;
obj.related = true;
} else if (typeof(field.key) === String) {
value = field.key; value = field.key;
} else { } else {
value = key; value = key;
@@ -27,23 +31,19 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', function(R
type = 'text'; type = 'text';
} }
obj.id = id;
obj.value = value;
obj.label = label;
obj.type = type;
// return the built option // return the built option
if (type === 'select') { if (type === 'select') {
return { obj.typeOptions = typeOptions;
id: id,
value: value,
label: label,
type: type,
typeOptions: typeOptions
};
} else {
return {
id: id,
value: value,
label: label,
type: type
};
} }
return obj;
}; };
// given the fields that are searchable, // given the fields that are searchable,
@@ -116,6 +116,45 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', function(R
// returns the url with filter params // returns the url with filter params
this.updateFilteredUrl = function(basePath, tags, pageSize) { this.updateFilteredUrl = function(basePath, tags, pageSize) {
// remove the chain directive from all the urls that might have
// been added previously
tags = (tags || []).map(function(val) {
if (val.url.indexOf("chain__") !== -1) {
val.url = val.url.substring(("chain__").length);
}
return val;
});
// separate those tags with the related: true attribute
var separateRelated = _.partition(tags, function(i) {
return i.related;
});
var relatedTags = separateRelated[0];
var nonRelatedTags = separateRelated[1];
if (relatedTags.length > 1) {
// separate query params that need the change directive
// but have different keys
var chainGroups = _.groupBy(relatedTags, function(i) {
return i.value;
});
// iterate over those groups and add the "chain__" to the
// beginning of all but the first of each url
relatedTags = _.flatten(_.map(chainGroups, function(group) {
return group.map(function(val, i) {
if (i !== 0) {
val.url = "chain__" + val.url;
}
return val;
});
}));
// combine the related and non related tags after chainifying
tags = relatedTags.concat(nonRelatedTags);
}
return basePath + "?" + return basePath + "?" +
(tags || []).map(function (t) { (tags || []).map(function (t) {
return t.url; return t.url;

View File

@@ -616,7 +616,8 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
options = params.opts, options = params.opts,
multiple = (params.multiple!==undefined) ? params.multiple : true, multiple = (params.multiple!==undefined) ? params.multiple : true,
placeholder = params.placeholder, placeholder = params.placeholder,
customDropdownAdapter = (params.customDropdownAdapter!==undefined) ? params.customDropdownAdapter : true; customDropdownAdapter = (params.customDropdownAdapter!==undefined) ? params.customDropdownAdapter : true,
addNew = params.addNew;
$.fn.select2.amd.require([ $.fn.select2.amd.require([
'select2/utils', 'select2/utils',
@@ -624,11 +625,12 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
'select2/dropdown/search', 'select2/dropdown/search',
'select2/dropdown/attachContainer', 'select2/dropdown/attachContainer',
'select2/dropdown/closeOnSelect', 'select2/dropdown/closeOnSelect',
'select2/dropdown/minimumResultsForSearch' 'select2/dropdown/minimumResultsForSearch',
], function (Utils, Dropdown, Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch) { 'select2/data/tokenizer'
], function (Utils, Dropdown, Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch, Tokenizer) {
var CustomAdapter = var CustomAdapter =
_.reduce([Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch], _.reduce([Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch, Tokenizer],
function(Adapter, Decorator) { function(Adapter, Decorator) {
return Utils.Decorate(Adapter, Decorator); return Utils.Decorate(Adapter, Decorator);
}, Dropdown); }, Dropdown);
@@ -639,14 +641,20 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
containerCssClass: 'Form-dropDown', containerCssClass: 'Form-dropDown',
width: '100%', width: '100%',
minimumResultsForSearch: Infinity, minimumResultsForSearch: Infinity,
} };
// multiple-choice directive calls select2 but needs to do so without this custom adapter // multiple-choice directive calls select2 but needs to do so without this custom adapter
// to allow the element to be draggable on survey preview. // to allow the element to be draggable on survey preview.
if(customDropdownAdapter) { if (customDropdownAdapter) {
config.dropdownAdapter = CustomAdapter; config.dropdownAdapter = CustomAdapter;
} }
if (addNew) {
$(element).prepend("<option></option>");
config.tags = true;
config.tokenSeparators = [];
}
$(element).select2(config); $(element).select2(config);
if(options){ if(options){
@@ -884,7 +892,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
} }
]) ])
.factory('ParamPass', function() { .factory('ParamPass', function() {
var savedData = undefined; var savedData;
function set(data) { function set(data) {
savedData = data; savedData = data;
@@ -899,5 +907,5 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
return { return {
set: set, set: set,
get: get get: get
} };
}); });

View File

@@ -448,12 +448,29 @@ angular.module('GeneratorHelpers', [systemStatus.name])
options = params.options, options = params.options,
base = params.base, base = params.base,
field = list.fields[fld], field = list.fields[fld],
html = ''; html = '',
classList;
if (field.type !== undefined && field.type === 'DropDown') { if (field.type !== undefined && field.type === 'DropDown') {
html = DropDown(params); html = DropDown(params);
} else if (field.type === 'role') { } else if (field.type === 'role') {
html += "<td class=\"List-tableCell\"><role-list class=\"RoleList\"></role-list></td>"; classList = (field.columnClass) ?
Attr(field, 'columnClass') : "";
html += `
<td ${classList}>
<role-list class=\"RoleList\">
</role-list>
</td>
`;
} else if (field.type === 'labels') {
classList = (field.columnClass) ?
Attr(field, 'columnClass') : "";
html += `
<td ${classList}>
<labels-list class=\"LabelList\">
</labels-list>
</td>
`;
} else if (field.type === 'badgeCount') { } else if (field.type === 'badgeCount') {
html = BadgeCount(params); html = BadgeCount(params);
} else if (field.type === 'badgeOnly') { } else if (field.type === 'badgeOnly') {