Lookup dialogs now created using Modal module, making them draggable and resizable. When job submission process prompts for missing job_template credential the dialog now includes instructions and only shows machine credentials (AC-1086). Added accordions to jobs page.

This commit is contained in:
Chris Houseknecht 2014-03-25 23:18:41 -04:00
parent 0b050466c5
commit 9ab6f3cbf6
10 changed files with 306 additions and 225 deletions

View File

@ -12,7 +12,7 @@
function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, ProjectList, GenerateList, LoadBreadCrumbs,
Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, ProjectUpdate,
ProjectStatus, FormatDate, Refresh, Wait, Stream, GetChoices, Empty) {
ProjectStatus, FormatDate, Refresh, Wait, Stream, GetChoices, Empty, Find) {
ClearScope();
@ -77,9 +77,11 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
$scope.projects[i].scm_type = $scope.project_scm_type_options[j].label;
if ($scope.projects[i].scm_type === 'Manual') {
$scope.projects[i].scm_update_tooltip = 'Manaul projects do not require an SCM update';
$scope.projects[i].scm_schedule_tooltip = 'Manual projects do not require a schedule';
$scope.projects[i].scm_type_class = 'btn-disabled';
} else {
$scope.projects[i].scm_update_tooltip = "Start an SCM update";
$scope.projects[i].scm_schedule_tooltip = "Schedule future SCM updates";
$scope.projects[i].scm_type_class = "";
}
break;
@ -339,11 +341,21 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
}
}
};
$scope.editSchedules = function(id) {
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (project.scm_type === "Manual" || Empty(project.scm_type)) {
// Nothing to do
}
else {
$location.path('/projects/' + id + '/schedules');
}
};
}
ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'ProjectList', 'GenerateList',
'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'GetBasePath',
'SelectionInit', 'ProjectUpdate', 'ProjectStatus', 'FormatDate', 'Refresh', 'Wait', 'Stream', 'GetChoices', 'Empty'
'SelectionInit', 'ProjectUpdate', 'ProjectStatus', 'FormatDate', 'Refresh', 'Wait', 'Stream', 'GetChoices', 'Empty', 'Find'
];
@ -475,7 +487,7 @@ function ProjectsAdd($scope, $rootScope, $compile, $location, $log, $routeParams
$rootScope.flashMessage = null;
generator.reset();
for (fld in master) {
$scope.fld = master.fld;
$scope[fld] = master[fld];
}
$scope.scmChange();
};

View File

@ -185,25 +185,25 @@ angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialF
])
.factory('SubmitJob', ['PromptPasswords', '$compile', 'Rest', '$location', 'GetBasePath', 'CredentialList',
'LookUpInit', 'CredentialForm', 'ProcessErrors', 'JobTemplateForm', 'Wait',
'LookUpInit', 'CredentialForm', 'ProcessErrors', 'JobTemplateForm', 'Wait', 'Empty', 'PromptForCredential',
function (PromptPasswords, $compile, Rest, $location, GetBasePath, CredentialList, LookUpInit, CredentialForm,
ProcessErrors, JobTemplateForm, Wait) {
ProcessErrors, JobTemplateForm, Wait, Empty, PromptForCredential) {
return function (params) {
var scope = params.scope,
id = params.id,
template_name = (params.template) ? params.template : null,
base = $location.path().replace(/^\//, '').split('/')[0],
url = GetBasePath(base) + id + '/';
function postJob(data) {
var dt, url, name;
// Create the job record
if (scope.credentialWatchRemove) {
scope.credentialWatchRemove();
}
dt = new Date().toISOString();
url = (data.related.jobs) ? data.related.jobs : data.related.job_template + 'jobs/';
name = (template_name) ? template_name : data.name;
if (scope.removePostTheJob) {
scope.removePostTheJob();
}
scope.removePostTheJob = scope.$on('PostTheJob', function(e, data) {
var dt = new Date().toISOString(),
url = (data.related.jobs) ? data.related.jobs : data.related.job_template + 'jobs/',
name = (template_name) ? template_name : data.name;
Wait('start');
Rest.setUrl(url);
Rest.post({
@ -251,43 +251,26 @@ angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialF
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to create job. POST returned status: ' + status });
});
});
if (scope.removePromptForCredential) {
scope.removePromptForCredential();
}
scope.removePromptForCredential = scope.$on('PromptForCredential', function(e, data) {
PromptForCredential({ scope: scope, template: data });
});
// Get the job or job_template record
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
// Create a job record
scope.credential = '';
if (data.credential === '' || data.credential === null) {
// Template does not have credential, prompt for one
Wait('stop');
if (scope.credentialWatchRemove) {
scope.credentialWatchRemove();
}
scope.credentialWatchRemove = scope.$watch('credential', function (newVal, oldVal) {
if (newVal !== oldVal) {
// After user selects a credential from the modal,
// submit the job
if (scope.credential !== '' && scope.credential !== null && scope.credential !== undefined) {
data.credential = scope.credential;
postJob(data);
}
}
});
LookUpInit({
scope: scope,
form: JobTemplateForm,
current_item: null,
list: CredentialList,
field: 'credential',
hdr: 'Credential Required'
});
scope.lookUpCredential();
if (Empty(data.credential)) {
scope.$emit('PromptForCredential', data);
} else {
// We have what we need, submit the job
postJob(data);
scope.$emit('PostTheJob');
}
})
.error(function (data, status) {
@ -298,6 +281,38 @@ angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialF
}
])
.factory('PromptForCredential', ['GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Empty',
function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
return function(params) {
var scope = params.scope,
template = params.template,
launchJob;
scope.credential = '';
launchJob = function () {
if (!Empty(scope.credential)) {
template.credential = scope.credential;
scope.$emit('PostTheJob', template);
}
};
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: scope,
form: JobTemplateForm,
current_item: null,
list: CredentialList,
field: 'credential',
hdr: 'Credential Required',
instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.",
postAction: launchJob
});
scope.lookUpCredential();
};
}])
// Sumbit SCM Update request
.factory('ProjectUpdate', ['PromptPasswords', '$compile', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert',
'ProjectsForm', 'Wait',

View File

@ -16,156 +16,204 @@
'use strict';
angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ApiLoader'])
.factory('LookUpInit', ['Alert', 'Rest', 'GenerateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty',
function (Alert, Rest, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty) {
return function (params) {
angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ApiLoader', 'ModalDialog'])
var scope = params.scope,
form = params.form,
list = params.list,
field = params.field,
postAction = params.postAction,
defaultUrl, name, hdr, watchUrl;
.factory('LookUpInit', ['Alert', 'Rest', 'GenerateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog',
function (Alert, Rest, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) {
return function (params) {
if (params.url) {
// pass in a url value to override the default
defaultUrl = params.url;
} else {
defaultUrl = (list.name === 'inventories') ? GetBasePath('inventory') : GetBasePath(list.name);
}
var parent_scope = params.scope,
form = params.form,
list = params.list,
field = params.field,
instructions = params.instructions,
postAction = params.postAction,
defaultUrl, name, watchUrl;
if (params.url) {
// pass in a url value to override the default
defaultUrl = params.url;
} else {
defaultUrl = (list.name === 'inventories') ? GetBasePath('inventory') : GetBasePath(list.name);
}
if ($('#htmlTemplate #lookup-modal-dialog').length > 0) {
$('#htmlTemplate #lookup-modal-dialog').empty();
}
else {
$('#htmlTemplate').append("<div id=\"lookup-modal-dialog\"></div>");
}
name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1);
watchUrl = (/\/$/.test(defaultUrl)) ? defaultUrl + '?' : defaultUrl + '&';
watchUrl += form.fields[field].sourceField + '__' + 'iexact=:value';
$('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl);
$('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field);
parent_scope['lookUp' + name] = function () {
var master = {},
scope = parent_scope.$new(),
name, hdr, buttons;
// Generating the search list potentially kills the values held in scope for the field.
// We'll keep a copy in master{} that we can revert back to on cancel;
master[field] = scope[field];
master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField];
GenerateList.inject(list, {
mode: 'lookup',
id: 'lookup-modal-dialog',
scope: scope,
instructions: instructions
});
// Show pop-up
name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1);
hdr = (params.hdr) ? params.hdr : 'Select ' + name;
watchUrl = (/\/$/.test(defaultUrl)) ? defaultUrl + '?' : defaultUrl + '&';
watchUrl += form.fields[field].sourceField + '__' + 'iexact=:value';
$('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl);
$('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field);
scope['lookUp' + name] = function () {
var master = {}, listGenerator, listScope;
// Generating the search list potentially kills the values held in scope for the field.
// We'll keep a copy in master{} that we can revert back to on cancel;
master[field] = scope[field];
master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField];
listGenerator = GenerateList;
listScope = listGenerator.inject(list, { mode: 'lookup', hdr: hdr });
$('#lookup-modal').on('hidden.bs.modal', function () {
// Restore search settings
if (listScope.searchCleanup) {
listScope.searchCleanup();
}
// If user clicks cancel without making a selection, restore original values
if (Empty(scope[field])) {
scope[field] = master[field];
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField];
}
});
listScope.selectAction = function () {
var i, found = false;
for (i = 0; i < listScope[list.name].length; i++) {
if (listScope[list.name][i].checked === '1') {
found = true;
scope[field] = listScope[list.name][i].id;
if (scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) {
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
listScope[list.name][i][form.fields[field].sourceField];
if (scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) {
scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]
.$setValidity('awlookup', true);
}
}
if (scope[form.name + '_form']) {
scope[form.name + '_form'].$setDirty();
}
listGenerator.hide();
}
}
if (found === false) {
Alert('Missing Selection', 'Oops, you failed to make a selection. Click on a row to make your selection, ' +
'and then click the Select button.');
} else {
if (postAction) {
postAction();
}
}
};
listScope['toggle_' + list.iterator] = function (id) {
var i;
for (i = 0; i < listScope[list.name].length; i++) {
if (listScope[list.name][i].id === id) {
listScope[list.name][i].checked = '1';
listScope[list.name][i].success_class = 'success';
} else {
listScope[list.name][i].checked = '0';
listScope[list.name][i].success_class = '';
}
}
};
SearchInit({
scope: listScope,
set: list.name,
list: list,
url: defaultUrl
});
PaginateInit({
scope: listScope,
list: list,
url: defaultUrl,
mode: 'lookup'
});
// If user made a selection previously, mark it as selected when modal loads
if (listScope.lookupPostRefreshRemove) {
listScope.lookupPostRefreshRemove();
// Show pop-up
buttons = [{
label: "Cancel",
icon: "fa-times",
"class": "btn btn-default",
"id": "lookup-cancel-button",
onClick: function() {
$('#lookup-modal-dialog').dialog('close');
}
listScope.lookupPostRefreshRemove = scope.$on('PostRefresh', function () {
var fld, i;
for (fld in list.fields) {
if (list.fields[fld].type && list.fields[fld].type === 'date') {
//convert dates to our standard format
for (i = 0; i < scope[list.name].length; i++) {
scope[list.name][i][fld] = FormatDate(new Date(scope[list.name][i][fld]));
},{
label: "Select",
onClick: function() {
scope.selectAction();
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "lookup-save-button"
}];
if (scope.removeModalReady) {
scope.removeModalReady();
}
scope.removeModalReady = scope.$on('ModalReady', function() {
$('#lookup-modal-dialog').dialog('open');
});
CreateDialog({
scope: scope,
buttons: buttons,
width: 600,
height: (instructions) ? 625 : 500,
minWidth: 500,
title: hdr,
id: 'lookup-modal-dialog',
onClose: function() {
setTimeout( function() {
scope.$apply( function() {
if (Empty(scope[field])) {
scope[field] = master[field];
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField];
}
});
}, 300);
},
callback: 'ModalReady'
});
SearchInit({
scope: scope,
set: list.name,
list: list,
url: defaultUrl
});
PaginateInit({
scope: scope,
list: list,
url: defaultUrl,
mode: 'lookup'
});
if (scope.lookupPostRefreshRemove) {
scope.lookupPostRefreshRemove();
}
scope.lookupPostRefreshRemove = scope.$on('PostRefresh', function () {
var fld, i;
for (fld in list.fields) {
if (list.fields[fld].type && list.fields[fld].type === 'date') {
//convert dates to our standard format
for (i = 0; i < scope[list.name].length; i++) {
scope[list.name][i][fld] = FormatDate(new Date(scope[list.name][i][fld]));
}
}
}
// List generator creates the form, resetting it and losing the previously selected value.
// If it's in the current set, find it and marke it as selected.
if (scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] !== '' &&
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] !== null) {
for (i = 0; i < listScope[list.name].length; i++) {
if (listScope[list.name][i][form.fields[field].sourceField] ===
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) {
scope[field] = listScope[list.name][i].id;
break;
// List generator creates the list, resetting it and losing the previously selected value.
// If the selected value is in the current set, find it and mark selected.
if (!Empty(parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField])) {
scope[list.name].forEach(function(elem) {
if (elem[form.fields[field].sourceField] ===
parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) {
scope[field] = elem.id;
}
});
}
if (!Empty(scope[field])) {
scope['toggle_' + list.iterator](scope[field]);
}
});
scope.search(list.iterator);
scope.selectAction = function () {
var i, found = false;
for (i = 0; i < scope[list.name].length; i++) {
if (scope[list.name][i].checked === '1') {
found = true;
parent_scope[field] = scope[list.name][i].id;
if (parent_scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) {
parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
scope[list.name][i][form.fields[field].sourceField];
if (parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) {
parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]
.$setValidity('awlookup', true);
}
}
if (parent_scope[form.name + '_form']) {
parent_scope[form.name + '_form'].$setDirty();
}
}
if (!Empty(scope[field])) {
listScope['toggle_' + list.iterator](scope[field]);
}
if (found === false) {
Alert('Missing Selection', 'Oops, you failed to make a selection. Click on a row to make your selection, ' +
'and then click the Select button.');
} else {
$('#lookup-modal-dialog').dialog('close');
if (postAction) {
postAction();
}
}
};
});
listScope.search(list.iterator);
scope['toggle_' + list.iterator] = function (id) {
var i;
for (i = 0; i < scope[list.name].length; i++) {
if (scope[list.name][i].id === id) {
scope[list.name][i].checked = '1';
scope[list.name][i].success_class = 'success';
} else {
scope[list.name][i].checked = '0';
scope[list.name][i].success_class = '';
}
}
};
};
}
]);
};
}]);

View File

@ -29,7 +29,7 @@ angular.module('CredentialsListDefinition', [])
},
description: {
label: 'Description',
excludeModal: false
excludeModal: true
},
kind: {
label: 'Type',

View File

@ -106,8 +106,9 @@ angular.module('ProjectsListDefinition', [])
},
schedule: {
mode: 'all',
ngHref: '#/projects/{{ project.id }}/schedules',
awToolTip: 'Schedule future SCM updates',
ngClick: "editSchedules(project.id)",
awToolTip: "{{ project.scm_schedule_tooltip }}",
ngClass: "project.scm_type_class",
dataPlacement: 'top'
},
edit: {

View File

@ -657,6 +657,11 @@ legend {
}
#lookup-modal-dialog .instructions {
margin-top: 0;
margin-bottom: 20px;
}
.related-footer {
margin: 10px 0 0 0;
}
@ -1053,15 +1058,11 @@ input[type="checkbox"].checkbox-no-label {
font-weight: normal;
line-height: 1;
}
.job-list.ui-accordion-content {
padding: 25px 15px 25px 15px;
}
.job-list {
margin-top: 20px;
.title {
margin-left: 3px;
font-weight: bold;
margin-bottom: 6px;
color: #666;
}
thead >tr >th, .page-row {
font-size: 12px;
color: #666;

View File

@ -629,4 +629,14 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
});
}
};
}]);
}])
.directive('awAccordion', function() {
return function(scope, element) {
$(element).accordion({
collapsible: true,
heightStyle: "content"
});
};
});

View File

@ -49,9 +49,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
//
var element;
if (options.mode === 'lookup') {
element = angular.element(document.getElementById('lookup-modal-body'));
} else if (options.id) {
if (options.id) {
element = angular.element(document.getElementById(options.id));
} else {
element = angular.element(document.getElementById('htmlTemplate'));
@ -97,7 +95,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
//ignore any errors should the dialog not be initialized
}
if (options.mode === 'lookup') {
/*if (options.mode === 'lookup') {
// options should include {hdr: <dialog header>, action: <function...> }
this.scope.formModalActionDisabled = false;
this.scope.lookupHeader = options.hdr;
@ -111,7 +109,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
$('#lookup-modal').modal('hide');
}
});
}
}*/
return this.scope;
},
@ -151,6 +149,13 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "</div>\n";
}
if (options.instructions) {
html += "<div class=\"instructions alert alert-info\">" + options.instructions + "</div>\n";
}
else if (list.instructions) {
html += "<div class=\"instructions alert alert-info\">" + list.instructions + "</div>\n";
}
if (options.mode !== 'lookup' && (list.well === undefined || list.well)) {
html += "<div class=\"well\">\n";
}
@ -164,7 +169,6 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "</div>\n";
}
if (options.showSearch=== undefined || options.showSearch === true) {
html += "<div class=\"row\">\n";
if (list.name !== 'groups') {

View File

@ -5,22 +5,30 @@
</div>
<div class="row">
<div class="col-md-12">
<div class="well">
<div class="job-list" id="completed-jobs-container">
<div class="title">Completed</div>
<div id="completed-jobs"></div>
<div id="jobs-page ">
<div aw-accordion>
<h3>Completed</h3>
<div class="job-list" id="completed-jobs-container">
<div id="completed-jobs"></div>
</div>
</div>
<div class="job-list" id="active-jobs-container">
<div class="title">Active</div>
<div id="active-jobs"></div>
<div aw-accordion>
<h3>Active</h3>
<div class="job-list" id="active-jobs-container">
<div id="active-jobs"></div>
</div>
</div>
<div class="job-list" id="queued-jobs-container">
<div class="title">Queued</div>
<div id="queued-jobs"></div>
<div aw-accordion>
<h3>Queued</h3>
<div class="job-list" id="queued-jobs-container">
<div id="queued-jobs"></div>
</div>
</div>
<div class="job-list" id="scheduled-jobs-container">
<div class="title">Scheduled</div>
<div id="scheduled-jobs"></div>
<div aw-accordion>
<h3>Scheduled</h3>
<div class="job-list" id="scheduled-jobs-container">
<div id="scheduled-jobs"></div>
</div>
</div>
</div>
</div>

View File

@ -299,24 +299,6 @@
</div><!-- modal-dialog -->
</div><!-- modal -->
<!-- Lookup dialog. Use for attribute selection -->
<div id="lookup-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-target="#lookup-modal"
data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 ng-bind="lookupHeader"></h3>
</div>
<div class="modal-body" id="lookup-modal-body"></div>
<div class="modal-footer">
<a href="#" data-target="#lookup-modal" data-dismiss="modal" id="lookup_cancel_btn" class="btn btn-default">Cancel</a>
<a href="" ng-click="selectAction()" id="lookup_select_btn" class="btn btn-primary">Select</a>
</div>
</div><!-- modal-content -->
</div><!-- modal-dialog -->
</div><!-- modal -->
<!-- Confirmation Dialog -->
<div id="prompt-modal" class="modal fade">
<div class="modal-dialog">