mirror of
https://github.com/ansible/awx.git
synced 2024-11-02 18:21:12 +03:00
job template labels ui implementation
This commit is contained in:
parent
d403c5fb3d
commit
9d9e2b254c
@ -38,6 +38,8 @@
|
||||
|
||||
.RoleList-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;
|
||||
|
@ -189,6 +189,18 @@ export default
|
||||
dataPlacement: "right",
|
||||
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: {
|
||||
label: 'Extra Variables',
|
||||
type: 'textarea',
|
||||
|
@ -11,14 +11,14 @@
|
||||
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList',
|
||||
'LookUpInit', 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON',
|
||||
'CallbackHelpInit', 'initSurvey', 'Prompt', 'GetChoices', '$state',
|
||||
'CreateSelect2',
|
||||
'CreateSelect2', '$q',
|
||||
function(
|
||||
Refresh, $filter, $scope, $rootScope, $compile,
|
||||
$location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
|
||||
ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, InventoryList,
|
||||
CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait,
|
||||
Empty, ToJSON, CallbackHelpInit, SurveyControllerInit, Prompt, GetChoices,
|
||||
$state, CreateSelect2
|
||||
$state, CreateSelect2, $q
|
||||
) {
|
||||
|
||||
ClearScope();
|
||||
@ -113,7 +113,7 @@
|
||||
}
|
||||
$scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () {
|
||||
selectCount++;
|
||||
if (selectCount === 2) {
|
||||
if (selectCount === 3) {
|
||||
var verbosity;
|
||||
// this sets the default options for the selects as specified by the controller.
|
||||
for (verbosity in $scope.verbosity_options) {
|
||||
@ -145,7 +145,11 @@
|
||||
element:'#job_templates_job_type',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
CreateSelect2({
|
||||
element:'#job_templates_labels',
|
||||
multiple: true,
|
||||
addNew: true
|
||||
});
|
||||
CreateSelect2({
|
||||
element:'#playbook-select',
|
||||
multiple: false
|
||||
@ -178,6 +182,21 @@
|
||||
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
|
||||
selectPlaybook = function (oldValue, newValue) {
|
||||
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
|
||||
if ($scope.selectPlaybookUnregister) {
|
||||
$scope.selectPlaybookUnregister();
|
||||
@ -311,14 +324,126 @@
|
||||
}
|
||||
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
|
||||
Wait('stop');
|
||||
if (data.related && data.related.callback) {
|
||||
Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is:</p>'+
|
||||
'<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);
|
||||
}
|
||||
else {
|
||||
saveCompleted();
|
||||
if (data.related &&
|
||||
data.related.callback) {
|
||||
Alert('Callback URL',
|
||||
`
|
||||
<p>Host callbacks are enabled for this template. The callback URL is:</p>
|
||||
<p style=\"padding: 10px 0;\">
|
||||
<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
|
||||
@ -327,11 +452,13 @@
|
||||
$scope.invalid_survey = false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -341,70 +468,61 @@
|
||||
|
||||
try {
|
||||
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;
|
||||
} else {
|
||||
if (fld !== 'variables' && fld !== 'survey') {
|
||||
if (fld !== 'variables' &&
|
||||
fld !== 'survey') {
|
||||
data[fld] = $scope[fld];
|
||||
}
|
||||
}
|
||||
}
|
||||
data.extra_vars = ToJSON($scope.parseType, $scope.variables, true);
|
||||
if(data.job_type === 'scan' && $scope.default_scan === true){
|
||||
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;
|
||||
// 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);
|
||||
Rest.post(data)
|
||||
.success(function(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 });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$scope.$emit('templateSaveSuccess',
|
||||
data);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
|
||||
msg: 'Failed to add new job template. POST returned status: ' + status
|
||||
});
|
||||
ProcessErrors($scope, data, status, form,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to add new job ' +
|
||||
'template. POST returned status: ' +
|
||||
status
|
||||
});
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Wait('stop');
|
||||
Alert("Error", "Error parsing extra variables. Parser returned: " + err);
|
||||
Alert("Error", "Error parsing extra variables. " +
|
||||
"Parser returned: " + err);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.formCancel = function () {
|
||||
$state.transitionTo('jobTemplates');
|
||||
};
|
||||
}
|
||||
|
||||
];
|
||||
|
@ -21,7 +21,7 @@ export default
|
||||
'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate',
|
||||
'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit',
|
||||
'PlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
|
||||
'ToggleNotification', 'NotificationsListInit',
|
||||
'ToggleNotification', 'NotificationsListInit', '$q',
|
||||
function(
|
||||
$filter, $scope, $rootScope, $compile,
|
||||
$location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
|
||||
@ -31,7 +31,7 @@ export default
|
||||
Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit,
|
||||
JobsControllerInit, JobsListUpdate, GetChoices, SchedulesListInit,
|
||||
SchedulesList, CallbackHelpInit, PlaybookRun, SurveyControllerInit, $state,
|
||||
CreateSelect2, ToggleNotification, NotificationsListInit
|
||||
CreateSelect2, ToggleNotification, NotificationsListInit, $q
|
||||
) {
|
||||
|
||||
ClearScope();
|
||||
@ -232,11 +232,6 @@ export default
|
||||
multiple: false
|
||||
});
|
||||
|
||||
CreateSelect2({
|
||||
element:'#job_templates_verbosity',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
for (var set in relatedSets) {
|
||||
$scope.search(relatedSets[set].iterator);
|
||||
}
|
||||
@ -353,7 +348,7 @@ export default
|
||||
}
|
||||
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
|
||||
choicesCount++;
|
||||
if (choicesCount === 4) {
|
||||
if (choicesCount === 5) {
|
||||
$scope.$emit('LoadJobs');
|
||||
}
|
||||
});
|
||||
@ -392,6 +387,41 @@ export default
|
||||
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() {
|
||||
$state.go('jobTemplates', null, {reload: true});
|
||||
}
|
||||
@ -401,34 +431,105 @@ export default
|
||||
}
|
||||
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
|
||||
Wait('stop');
|
||||
if ($scope.allow_callbacks && ($scope.host_config_key !== master.host_config_key || $scope.callback_url !== master.callback_url)) {
|
||||
if (data.related && data.related.callback) {
|
||||
Alert('Callback URL', '<p>Host callbacks are enabled for this template. The callback URL is:</p>'+
|
||||
'<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);
|
||||
}
|
||||
else {
|
||||
saveCompleted();
|
||||
}
|
||||
}
|
||||
else {
|
||||
saveCompleted();
|
||||
if (data.related &&
|
||||
data.related.callback) {
|
||||
Alert('Callback URL',
|
||||
`
|
||||
<p>Host callbacks are enabled for this template. The callback URL is:</p>
|
||||
<p style=\"padding: 10px 0;\">
|
||||
<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() {
|
||||
saveCompleted();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Save changes to the parent
|
||||
// Save
|
||||
$scope.formSave = function () {
|
||||
var fld, data = {};
|
||||
$scope.invalid_survey = false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -437,21 +538,38 @@ export default
|
||||
Wait('start');
|
||||
|
||||
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) {
|
||||
if (form.fields[fld].type === 'select' && fld !== 'playbook') {
|
||||
if (form.fields[fld].type === 'select' &&
|
||||
fld !== 'playbook') {
|
||||
data[fld] = $scope[fld].value;
|
||||
} else {
|
||||
if (fld !== 'variables' && fld !== 'callback_url') {
|
||||
if (fld !== 'variables' &&
|
||||
fld !== 'survey') {
|
||||
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)
|
||||
.success(function (data) {
|
||||
$scope.$emit('templateSaveSuccess', data);
|
||||
@ -460,12 +578,11 @@ export default
|
||||
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
|
||||
msg: 'Failed to update job template. PUT returned status: ' + status });
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Wait('stop');
|
||||
Alert("Error", "Error parsing extra variables. Parser returned: " + err);
|
||||
Alert("Error", "Error parsing extra variables. " +
|
||||
"Parser returned: " + err);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.formCancel = function () {
|
||||
@ -474,7 +591,7 @@ export default
|
||||
var defaultUrl = GetBasePath('job_templates') + $state.params.template_id;
|
||||
Rest.setUrl(defaultUrl);
|
||||
Rest.destroy()
|
||||
.success(function(res){
|
||||
.success(function(){
|
||||
$state.go('jobTemplates', null, {reload: true, notify:true});
|
||||
})
|
||||
.error(function(res, status){
|
||||
|
74
awx/ui/client/src/job-templates/labels/labelsList.block.less
Normal file
74
awx/ui/client/src/job-templates/labels/labelsList.block.less
Normal 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;
|
||||
}
|
@ -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'
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
@ -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>
|
11
awx/ui/client/src/job-templates/labels/main.js
Normal file
11
awx/ui/client/src/job-templates/labels/main.js
Normal 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);
|
@ -11,9 +11,10 @@ import jobTemplatesList from './list/main';
|
||||
import jobTemplatesAdd from './add/main';
|
||||
import jobTemplatesEdit from './edit/main';
|
||||
import jobTemplatesCopy from './copy/main';
|
||||
import labels from './labels/main';
|
||||
|
||||
export default
|
||||
angular.module('jobTemplates',
|
||||
[surveyMaker.name, jobTemplatesList.name, jobTemplatesAdd.name,
|
||||
jobTemplatesEdit.name, jobTemplatesCopy.name])
|
||||
angular.module('jobTemplates',
|
||||
[surveyMaker.name, jobTemplatesList.name, jobTemplatesAdd.name,
|
||||
jobTemplatesEdit.name, jobTemplatesCopy.name, labels.name])
|
||||
.service('deleteJobTemplate', deleteJobTemplate);
|
||||
|
@ -23,19 +23,28 @@ export default
|
||||
name: {
|
||||
key: true,
|
||||
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: {
|
||||
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: {
|
||||
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,
|
||||
nosort: true,
|
||||
ngInclude: "'/static/partials/job-template-smart-status.html'",
|
||||
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: {
|
||||
|
||||
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: {
|
||||
label: 'Launch',
|
||||
|
@ -196,6 +196,8 @@
|
||||
|
||||
.TagSearch-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;
|
||||
|
@ -3,9 +3,13 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', function(R
|
||||
// parse the field config object to return
|
||||
// one of the searchTypes (for the left dropdown)
|
||||
this.buildType = function (field, key, id) {
|
||||
var obj = {};
|
||||
// build the value (key)
|
||||
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;
|
||||
} else {
|
||||
value = key;
|
||||
@ -27,23 +31,19 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', function(R
|
||||
type = 'text';
|
||||
}
|
||||
|
||||
obj.id = id;
|
||||
obj.value = value;
|
||||
obj.label = label;
|
||||
obj.type = type;
|
||||
|
||||
|
||||
|
||||
// return the built option
|
||||
if (type === 'select') {
|
||||
return {
|
||||
id: id,
|
||||
value: value,
|
||||
label: label,
|
||||
type: type,
|
||||
typeOptions: typeOptions
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
id: id,
|
||||
value: value,
|
||||
label: label,
|
||||
type: type
|
||||
};
|
||||
obj.typeOptions = typeOptions;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
// 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
|
||||
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 + "?" +
|
||||
(tags || []).map(function (t) {
|
||||
return t.url;
|
||||
|
@ -616,7 +616,8 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
|
||||
options = params.opts,
|
||||
multiple = (params.multiple!==undefined) ? params.multiple : true,
|
||||
placeholder = params.placeholder,
|
||||
customDropdownAdapter = (params.customDropdownAdapter!==undefined) ? params.customDropdownAdapter : true;
|
||||
customDropdownAdapter = (params.customDropdownAdapter!==undefined) ? params.customDropdownAdapter : true,
|
||||
addNew = params.addNew;
|
||||
|
||||
$.fn.select2.amd.require([
|
||||
'select2/utils',
|
||||
@ -624,11 +625,12 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
|
||||
'select2/dropdown/search',
|
||||
'select2/dropdown/attachContainer',
|
||||
'select2/dropdown/closeOnSelect',
|
||||
'select2/dropdown/minimumResultsForSearch'
|
||||
], function (Utils, Dropdown, Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch) {
|
||||
'select2/dropdown/minimumResultsForSearch',
|
||||
'select2/data/tokenizer'
|
||||
], function (Utils, Dropdown, Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch, Tokenizer) {
|
||||
|
||||
var CustomAdapter =
|
||||
_.reduce([Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch],
|
||||
_.reduce([Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch, Tokenizer],
|
||||
function(Adapter, Decorator) {
|
||||
return Utils.Decorate(Adapter, Decorator);
|
||||
}, Dropdown);
|
||||
@ -639,14 +641,20 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
|
||||
containerCssClass: 'Form-dropDown',
|
||||
width: '100%',
|
||||
minimumResultsForSearch: Infinity,
|
||||
}
|
||||
};
|
||||
|
||||
// multiple-choice directive calls select2 but needs to do so without this custom adapter
|
||||
// to allow the element to be draggable on survey preview.
|
||||
if(customDropdownAdapter) {
|
||||
if (customDropdownAdapter) {
|
||||
config.dropdownAdapter = CustomAdapter;
|
||||
}
|
||||
|
||||
if (addNew) {
|
||||
$(element).prepend("<option></option>");
|
||||
config.tags = true;
|
||||
config.tokenSeparators = [];
|
||||
}
|
||||
|
||||
$(element).select2(config);
|
||||
|
||||
if(options){
|
||||
@ -884,7 +892,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
|
||||
}
|
||||
])
|
||||
.factory('ParamPass', function() {
|
||||
var savedData = undefined;
|
||||
var savedData;
|
||||
|
||||
function set(data) {
|
||||
savedData = data;
|
||||
@ -899,5 +907,5 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
|
||||
return {
|
||||
set: set,
|
||||
get: get
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -448,12 +448,29 @@ angular.module('GeneratorHelpers', [systemStatus.name])
|
||||
options = params.options,
|
||||
base = params.base,
|
||||
field = list.fields[fld],
|
||||
html = '';
|
||||
html = '',
|
||||
classList;
|
||||
|
||||
if (field.type !== undefined && field.type === 'DropDown') {
|
||||
html = DropDown(params);
|
||||
} 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') {
|
||||
html = BadgeCount(params);
|
||||
} else if (field.type === 'badgeOnly') {
|
||||
|
Loading…
Reference in New Issue
Block a user