AC-352 First pass at SCM integration

This commit is contained in:
chouseknecht 2013-08-28 21:38:45 -04:00
parent 51033abe87
commit ee220ba928
9 changed files with 339 additions and 55 deletions

View File

@ -65,7 +65,8 @@ angular.module('ansible', [
'SelectionHelper',
'LicenseFormDefinition',
'License',
'HostGroupsFormDefinition'
'HostGroupsFormDefinition',
'SCMUpdateHelper'
])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.

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)
ClearScope, ProcessErrors, GetBasePath, SelectionInit, SCMUpdate)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -63,11 +63,25 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
action: action
});
}
scope.scmUpdate = function(project_id) {
for (var i=0; i < scope.projects.length; i++) {
if (scope.projects[i].id == project_id) {
if (scope.projects[i].scm_type == "") {
Alert('Missing SCM Setup', 'Before running an SCM update, edit the project and provide the SCM access information.', 'alert-info');
}
else {
SCMUpdate({ scope: scope, project: scope.projects[i]});
}
break;
}
}
}
}
ProjectsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'ProjectList', 'GenerateList',
'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'SelectionInit'];
'GetBasePath', 'SelectionInit', 'SCMUpdate'];
function ProjectsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, ProjectsForm,
@ -90,6 +104,10 @@ function ProjectsAdd ($scope, $rootScope, $compile, $location, $log, $routeParam
LoadBreadCrumbs();
GetProjectPath({ scope: scope, master: master });
scope.scm_type_options = [
{ label: 'GitHub', value: 'git' },
{ label: 'SVN', value: 'svn' }];
LookUpInit({
scope: scope,
form: form,
@ -163,10 +181,29 @@ function ProjectsEdit ($scope, $rootScope, $compile, $location, $log, $routePara
var master = {};
var id = $routeParams.id;
var relatedSets = {};
scope.scm_type_options = [
{ label: 'GitHub', value: 'git' },
{ label: 'SVN', value: 'svn' }];
scope.project_local_paths = [];
scope.base_dir = '';
function setAskCheckboxes() {
for (var fld in form.fields) {
if (form.fields[fld].type == 'password' && form.fields[fld].ask && scope[fld] == 'ASK') {
// turn on 'ask' checkbox for password fields with value of 'ASK'
$("#" + fld + "-clear-btn").attr("disabled","disabled");
scope[fld + '_ask'] = true;
}
else {
scope[fld + '_ask'] = false;
$("#" + fld + "-clear-btn").removeAttr("disabled");
}
}
}
// After the project is loaded, retrieve each related set
if (scope.projectLoadedRemove) {
scope.projectLoadedRemove();
@ -203,6 +240,9 @@ function ProjectsEdit ($scope, $rootScope, $compile, $location, $log, $routePara
relatedSets[set] = { url: related[set], iterator: form.related[set].iterator };
}
}
setAskCheckboxes();
// Initialize related search functions. Doing it here to make sure relatedSets object is populated.
RelatedSearchInit({ scope: scope, form: form, relatedSets: relatedSets });
RelatedPaginateInit({ scope: scope, relatedSets: relatedSets });
@ -221,6 +261,9 @@ function ProjectsEdit ($scope, $rootScope, $compile, $location, $log, $routePara
for (var fld in form.fields) {
params[fld] = scope[fld];
}
if (scope.scm_type) {
params.scm_type = scope.scm_type.value;
}
Rest.setUrl(defaultUrl);
Rest.put(params)
.success( function(data, status, headers, config) {
@ -276,6 +319,35 @@ function ProjectsEdit ($scope, $rootScope, $compile, $location, $log, $routePara
action: action
});
}
// Password change
scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
scope[fld] = '';
scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
}
// Respond to 'Ask at runtime?' checkbox
scope.ask = function(fld, associated) {
if (scope[fld + '_ask']) {
$("#" + fld + "-clear-btn").attr("disabled","disabled");
scope[fld] = 'ASK';
scope[associated] = '';
scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
else {
$("#" + fld + "-clear-btn").removeAttr("disabled");
scope[fld] = '';
scope[associated] = '';
scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
}
scope.clear = function(fld, associated) {
scope[fld] = '';
scope[associated] = '';
scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
}
ProjectsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'ProjectsForm',

View File

@ -11,10 +11,10 @@ angular.module('ProjectFormDefinition', [])
.value(
'ProjectsForm', {
addTitle: 'Create Project', //Title in add mode
editTitle: '{{ name }}', //Title in edit mode
name: 'project', //entity or model name in singular form
well: true, //Wrap the form with TB well/
addTitle: 'Create Project', // Title in add mode
editTitle: '{{ name }}', // Title in edit mode
name: 'project', // entity or model name in singular form
well: true, // Wrap the form with TB well
fields: {
name: {
@ -30,7 +30,7 @@ angular.module('ProjectFormDefinition', [])
addRequired: false,
editRequired: false
},
organization: {
organization: {
label: 'Organization',
type: 'lookup',
sourceModel: 'organization',
@ -43,6 +43,7 @@ angular.module('ProjectFormDefinition', [])
awPopOver: '<p>A project must have at least one organization. Pick one organization now to create the project, and then after ' +
'the project is created you can add additional organizations.' ,
dataTitle: 'Organization',
dataContainer: 'body',
dataPlacement: 'right'
},
base_dir: {
@ -54,6 +55,7 @@ angular.module('ProjectFormDefinition', [])
'Together the base path and selected playbook directory provide the full path used to locate playbooks.</p>' +
'<p>Use PROJECTS_ROOT in your environment settings file to determine the base path value.</p>',
dataTitle: 'Project Base Path',
dataContainer: 'body',
dataPlacement: 'right'
},
local_path: {
@ -67,7 +69,136 @@ angular.module('ProjectFormDefinition', [])
'Together the base path and the playbook directory provide the full path used to locate playbooks.</p>' +
'<p>Use PROJECTS_ROOT in your environment settings file to determine the base path value.</p>',
dataTitle: 'Project Path',
dataContainer: 'body',
dataPlacement: 'right'
},
scm_type: {
label: 'SCM Type',
type: 'select',
ngOptions: 'type.label for type in scm_type_options',
"default": '',
addRequired: false,
editRequired: false
},
scm_url: {
label: 'SCM URL',
type: 'text',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
awRequiredWhen: {variable: "scm_type", init: "true" }
},
scm_branch: {
label: 'SCM Branch',
type: 'text',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false
},
scm_username: {
label: 'SCM Username',
type: 'text',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false
},
"scm_password": {
label: 'SCM Password',
type: 'password',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
ngChange: "clearPWConfirm('scm_password_confirm')",
ask: true,
clear: true,
associated: 'scm_password_confirm',
autocomplete: false
},
"scm_password_confirm": {
label: 'Confirm Password',
type: 'password',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
awPassMatch: true,
associated: 'scm_password',
autocomplete: false
},
"scm_key_data": {
label: 'SCM Private Key',
type: 'textarea',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
rows: 10
},
"scm_key_unlock": {
label: 'SCM Key Password',
type: 'password',
ngShow: "scm_type !== '' && scm_type !== null && scm_key_data",
addRequired: false,
editRequired: false,
ngChange: "clearPWConfirm('scm_key_unlock_confirm')",
associated: 'scm_key_unlock_confirm',
ask: true,
clear: true
},
"scm_key_unlock_confirm": {
label: 'Confirm Key Password',
type: 'password',
ngShow: "scm_type !== '' && scm_type !== null && scm_key_data",
addRequired: false,
editRequired: false,
awPassMatch: true,
associated: 'scm_key_unlock'
},
checkbox_group: {
label: 'SCM Options',
type: 'checkbox_group',
ngShow: "scm_type !== '' && scm_type !== null",
fields: [
{
name: 'scm_clean',
label: 'Clean',
type: 'checkbox',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
awPopOver: '<p>Remove any local modifications prior to performing an update.</p>',
dataTitle: 'SCM Clean',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options'
},
{
name: 'scm_delete_on_update',
label: 'Delete on Update',
type: 'checkbox',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
awPopOver: '<p>Delete the local repository in its entirety prior to performing an update.</p><p>Depending on the size of the ' +
'repository this may significantly increase the amount of time required to complete an update.</p>',
dataTitle: 'SCM Delete',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options'
},
{
name: 'scm_update_on_launch',
label: 'Update on Launch',
type: 'checkbox',
ngShow: "scm_type !== '' && scm_type !== null",
addRequired: false,
editRequired: false,
awPopOver: '<p>Each time a job runs using this project, perform an update to the local repository prior to starting the job.</p>',
dataTitle: 'SCM Update',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options'
}
]
}
},

View File

@ -0,0 +1,28 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* SCMUpdate.js
*
* Use to kick off an update the project
*
*/
angular.module('SCMUpdateHelper', ['RestServices', 'Utilities'])
.factory('SCMUpdate', ['Alert', 'Rest', 'GetBasePath','ProcessErrors',
function(Alert, Rest, GetBasePath, ProcessErrors) {
return function(params) {
var scope = params.scope;
var project = params.project;
Rest.setUrl(project.related.update);
Rest.get()
.success( function(data, status, headers, config) {
console.log(data);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to access ' + project.related.update + '. GET status: ' + status });
});
}
}]);

View File

@ -52,7 +52,7 @@ angular.module('InventoriesListDefinition', [])
dropdown: {
type: 'DropDown',
label: 'View Jobs',
label: 'View',
'class': 'btn-xs',
options: [
{ ngClick: 'viewJobs(\{\{ inventory.id \}\})', label: 'Jobs' },

View File

@ -41,6 +41,7 @@ angular.module('ProjectsListDefinition', [])
},
fieldActions: {
edit: {
label: 'Edit',
ngClick: "editProject(\{\{ project.id \}\})",
@ -48,6 +49,14 @@ angular.module('ProjectsListDefinition', [])
"class": 'btn-xs btn-default',
awToolTip: 'View/edit project'
},
scm_update: {
label: 'Update',
icon: 'icon-cloud-download',
"class": 'btn-xs btn-success',
ngClick: 'scmUpdate(\{\{ project.id \}\})',
awToolTip: 'Perform an SCM update on this project'
},
"delete": {
label: 'Delete',

View File

@ -231,6 +231,10 @@ a:hover {
max-width: 100px;
}
.form-horizontal .buttons {
margin-top: 25px;
}
/* Outline required fields in Red when focused */
.form-control[required]:focus {
@ -411,6 +415,11 @@ select.field-mini-height {
margin: 0;
}
.checkbox-options {
padding-left: 40px;
}
/* Display list actions next to search widget */
.list-actions {

View File

@ -265,41 +265,63 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
buildField: function(fld, field, options, form) {
function getFieldWidth() {
var x;
if (form.formFieldSize) {
x = form.formFieldSize;
}
else if (field.xtraWide) {
x = "col-lg-10";
}
else if (field.column) {
x = "col-lg-8";
}
else if (!form.formFieldSize && options.modal) {
x = "col-lg-10";
}
else {
x = "col-lg-6";
}
return x;
var x;
if (form.formFieldSize) {
x = form.formFieldSize;
}
else if (field.xtraWide) {
x = "col-lg-10";
}
else if (field.column) {
x = "col-lg-8";
}
else if (!form.formFieldSize && options.modal) {
x = "col-lg-10";
}
else {
x = "col-lg-6";
}
return x;
}
function getLabelWidth() {
var x;
if (form.formLabelSize) {
x = form.formLabelSize;
}
else if (field.column) {
x = "col-lg-4";
}
else if (!form.formLabelSize && options.modal) {
x = "col-lg-2";
}
else {
x = "col-lg-2";
}
return x;
var x;
if (form.formLabelSize) {
x = form.formLabelSize;
}
else if (field.column) {
x = "col-lg-4";
}
else if (!form.formLabelSize && options.modal) {
x = "col-lg-2";
}
else {
x = "col-lg-2";
}
return x;
}
function buildCheckbox(field, fld) {
var html='';
html += "<label class=\"checkbox-inline"
html += (field.labelClass) ? " " + field.labelClass : "";
html += "\">";
html += "<input type=\"checkbox\" ";
html += Attr(field,'type');
html += "ng-model=\"" + fld + '" ';
html += "name=\"" + fld + '" ';
html += (field.ngChange) ? Attr(field,'ngChange') : "";
html += (field.id) ? Attr(field,'id') : "";
html += (field['class']) ? Attr(field,'class') : "";
html += Attr(field,'trueValue');
html += Attr(field,'falseValue');
html += (field.checked) ? "checked " : "";
html += (field.readonly) ? "disabled " : "";
html += " > " + field.label + "\n";
html += (field.awPopOver) ? Attr(field, 'awPopOver', fld) : "";
html += "</label>\n";
return html;
}
var html = '';
@ -516,6 +538,29 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
}
//checkbox group
if (field.type == 'checkbox_group') {
html += "<label class=\"control-label " + getLabelWidth() + "\">" +
field.label + "</label>\n";
for (var i=0; i < field.fields.length; i++) {
html += buildCheckbox(field.fields[i], field.fields[i].name);
}
// Add error messages
if ( (options.mode == 'add' && field.addRequired) || (options.mode == 'edit' && field.editRequired) ) {
html += "<div class=\"error\" ng-show=\"" + this.form.name + '_form.' + fld + ".$dirty && " +
this.form.name + '_form.' + fld + ".$error.required\">A value is required!</div>\n";
}
if (field.integer) {
html += "<div class=\"error\" ng-show=\"" + this.form.name + '_form.' + fld + ".$error.integer\">Must be an integer value</div>\n";
}
if (field.min || field.max) {
html += "<div class=\"error\" ng-show=\"" + this.form.name + '_form.' + fld + ".$error.min || " +
this.form.name + '_form.' + fld + ".$error.max\">Must be in range " + field.min + " to " +
field.max + "</div>\n";
}
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
}
//checkbox
if (field.type == 'checkbox') {
@ -523,22 +568,9 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "<div ";
html += (field.controlNGClass) ? "ng-class=\"" + field.controlNGClass + "\" " : "";
html += "class=\"" + getFieldWidth() + "\">\n";
html += "<label class=\"checkbox-inline\">";
html += "<input type=\"checkbox\" ";
html += this.attr(field,'type');
html += "ng-model=\"" + fld + '" ';
html += "name=\"" + fld + '" ';
html += (field.ngChange) ? this.attr(field,'ngChange') : "";
html += (field.id) ? this.attr(field,'id') : "";
html += this.attr(field,'trueValue');
html += this.attr(field,'falseValue');
html += (field.checked) ? "checked " : "";
html += (field.readonly) ? "disabled " : "";
html += " > " + field.label + "\n";
html += (field.awPopOver) ? this.attr(field, 'awPopOver', fld) : "";
html += "</label>\n";
html += buildCheckbox(field, fld);
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
html += "</div>\n"
}
//radio
@ -782,6 +814,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "<div" + sectionShow + ">\n";
section = field.section;
}
html += this.buildField(fld, field, options, this.form);
}
if (section !== '') {

View File

@ -100,6 +100,7 @@
<script src="{{ STATIC_URL }}js/helpers/md5.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Access.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Selection.js"></script>
<script src="{{ STATIC_URL }}js/helpers/SCMUpdate.js"></script>
<script src="{{ STATIC_URL }}lib/less/less-1.4.1.min.js"></script>