diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index b3e832413b..9dece6bb7c 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -114,7 +114,9 @@ angular.module('Tower', [ 'lrInfiniteScroll', 'LoadConfigHelper', 'SocketHelper', - 'AboutAnsibleHelpModal' + 'AboutAnsibleHelpModal', + 'SurveyMakerFormDefinition', + 'SurveyQuestionFormDefinition' ]) .constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/') @@ -132,9 +134,10 @@ angular.module('Tower', [ controller: 'JobsListController' }). // when('/portal', { + // templateUrl: urlPrefix + 'partials/portal.html' // controller: 'Portal' + // }). - // }) when('/jobs/:id', { templateUrl: urlPrefix + 'partials/job_detail.html', controller: 'JobDetailController' @@ -160,6 +163,16 @@ angular.module('Tower', [ controller: 'JobTemplatesEdit' }). + when('/job_templates/add/survey', { + templateUrl: urlPrefix + 'partials/survey_maker.html', + controller: 'SurveyMakerAdd' + }). + + when('/job_templates/:template_id/survey', { + templateUrl: urlPrefix + 'partials/survey_maker.html', + controller: 'SurveyMakerAdd' + }). + when('/job_templates/:id/schedules', { templateUrl: urlPrefix + 'partials/schedule_detail.html', controller: 'ScheduleEditController' diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 482a1b2bf4..a0f5a77aa3 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -335,6 +335,11 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa $scope[fld] = master[fld]; } }; + + //navigate to the survey maker + $scope.navigateToSurvey = function() { + $location.path($location.path() + '/survey'); + }; } JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', @@ -813,6 +818,11 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP }); }; + //navigate to the survey maker + $scope.navigateToSurvey = function() { + $location.path($location.path() + '/survey'); + }; + // Related set: Delete button $scope['delete'] = function (set, itm_id, name, title) { $rootScope.flashMessage = null; diff --git a/awx/ui/static/js/controllers/Portal.js b/awx/ui/static/js/controllers/Portal.js new file mode 100644 index 0000000000..0175acdaa2 --- /dev/null +++ b/awx/ui/static/js/controllers/Portal.js @@ -0,0 +1,191 @@ +// /************************************ +// * Copyright (c) 2014 AnsibleWorks, Inc. +// * +// * +// * Portal.js +// * +// * Controller functions for portal mode +// * +// */ + + +// /** +// * @ngdoc function +// * @name controllers.function:Portal +// * @description This controller's for portal mode +// */ +// 'use strict'; + +// /** +// * @ngdoc method +// * @name controllers.function:Portal#Portal +// * @methodOf controllers.function:Portal +// * @description portal mode woohoo +// * +// * +// */ +// function Portal($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, +// ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button){ + +// ClearScope('portal'); + +// // var buttons, html, e, waitCount, loadedCount,borderStyles, jobs_scope, schedule_scope; + +// // // Add buttons to the top of the Home page. We're using lib/ansible/generator_helpers.js-> Buttons() +// // // to build buttons dynamically and insure all styling and icons match the rest of the application. +// // buttons = { +// // refresh: { +// // mode: 'all', +// // awToolTip: "Refresh the page", +// // ngClick: "refresh()", +// // ngShow:"socketStatus == 'error'" +// // }, +// // stream: { +// // ngClick: "showActivity()", +// // awToolTip: "View Activity Stream", +// // mode: 'all' +// // } +// // }; + +// // html = Button({ +// // btn: buttons.refresh, +// // action: 'refresh', +// // toolbar: true +// // }); + +// // html += Button({ +// // btn: buttons.stream, +// // action: 'stream', +// // toolbar: true +// // }); + +// // e = angular.element(document.getElementById('home-list-actions')); +// // e.html(html); +// // $compile(e)($scope); + +// // waitCount = 4; +// // loadedCount = 0; + +// // if (!$routeParams.login) { +// // // If we're not logging in, start the Wait widget. Otherwise, it's already running. +// // //Wait('start'); +// // } + +// // if ($scope.removeWidgetLoaded) { +// // $scope.removeWidgetLoaded(); +// // } +// // $scope.removeWidgetLoaded = $scope.$on('WidgetLoaded', function (e, label, jobscope, schedulescope) { +// // // Once all the widgets report back 'loaded', turn off Wait widget +// // if(label==="dashboard_jobs"){ +// // jobs_scope = jobscope; +// // schedule_scope = schedulescope; +// // } +// // loadedCount++; +// // if (loadedCount === waitCount) { +// // $(window).resize(_.debounce(function() { +// // $scope.$emit('ResizeJobGraph'); +// // $scope.$emit('ResizeHostGraph'); +// // $scope.$emit('ResizeHostPieGraph'); +// // Wait('stop'); +// // }, 500)); +// // $(window).resize(); +// // } +// // }); + +// // if ($scope.removeDashboardReady) { +// // $scope.removeDashboardReady(); +// // } +// // $scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) { +// // nv.dev=false; + + +// // borderStyles = {"border": "1px solid #A9A9A9", +// // "border-radius": "4px", +// // "padding": "5px", +// // "margin-bottom": "15px"}; +// // $('.graph-container').css(borderStyles); + +// // var winHeight = $(window).height(), +// // available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; +// // $('.graph-container').height(available_height/2); +// // // // chart.update(); + +// // DashboardCounts({ +// // scope: $scope, +// // target: 'dash-counts', +// // dashboard: data +// // }); + +// // JobStatusGraph({ +// // scope: $scope, +// // target: 'dash-job-status-graph', +// // dashboard: data +// // }); + +// // if ($rootScope.user_is_superuser === true) { +// // waitCount = 5; +// // HostGraph({ +// // scope: $scope, +// // target: 'dash-host-count-graph', +// // dashboard: data +// // }); +// // } +// // else{ +// // $('#dash-host-count-graph').remove(); //replaceWith("
"); +// // } +// // DashboardJobs({ +// // scope: $scope, +// // target: 'dash-jobs-list', +// // dashboard: data +// // }); +// // HostPieChart({ +// // scope: $scope, +// // target: 'dash-host-status-graph', +// // dashboard: data +// // }); + +// // }); + +// // if ($rootScope.removeJobStatusChange) { +// // $rootScope.removeJobStatusChange(); +// // } +// // $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function() { +// // jobs_scope.refreshJobs(); +// // $scope.$emit('ReloadJobStatusGraph'); + +// // }); + +// // if ($rootScope.removeScheduleChange) { +// // $rootScope.removeScheduleChange(); +// // } +// // $rootScope.removeScheduleChange = $rootScope.$on('ScheduleChange', function() { +// // schedule_scope.refreshSchedules(); +// // $scope.$emit('ReloadJobStatusGraph'); +// // }); + +// // $scope.showActivity = function () { +// // Stream({ +// // scope: $scope +// // }); +// // }; + +// // $scope.refresh = function () { +// // Wait('start'); +// // loadedCount = 0; +// // Rest.setUrl(GetBasePath('dashboard')); +// // Rest.get() +// // .success(function (data) { +// // $scope.$emit('dashboardReady', data); +// // }) +// // .error(function (data, status) { +// // ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); +// // }); +// // }; + +// // $scope.refresh(); + +// } + +// Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', +// 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button' +// ]; diff --git a/awx/ui/static/js/controllers/SurveyMaker.js b/awx/ui/static/js/controllers/SurveyMaker.js new file mode 100644 index 0000000000..d625ba4fdb --- /dev/null +++ b/awx/ui/static/js/controllers/SurveyMaker.js @@ -0,0 +1,320 @@ +/************************************ + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * + * SurveyMaker.js + * + * Controller functions for the survey maker + * + */ +/** + * @ngdoc function + * @name controllers.function:SurveyMaker + * @description This controller's for the survey maker page +*/ +'use strict'; + +function SurveyMakerAdd($scope, $rootScope, $compile, $location, $log, $routeParams, SurveyMakerForm, + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath, + ReturnToCaller, Wait, SurveyQuestionForm) { + + ClearScope(); + + // Inject dynamic view + var generator = GenerateForm, + form = SurveyMakerForm, + base = $location.path().replace(/^\//, '').split('/')[0]; + + $scope.survey_questions=[]; + + $scope.answer_types=[ + {name: 'Text' , type: 'text'}, + {name: 'Textarea', type: 'text'}, + {name: 'Multiple Choice (single select)', type: 'mc'}, + {name: 'Multiple Choice (multiple select)', type: 'mc'}, + {name: 'JSON', type: 'json'}, + {name: 'Integer', type: 'number'}, + {name: 'Float', type: 'number'} + ]; + + + generator.inject(form, { mode: 'add', related: false, scope: $scope}); + generator.reset(); + + // LoadBreadCrumbs(); + + $scope.addQuestion = function(){ + GenerateForm.inject(SurveyQuestionForm, {mode:'add', id:'new_question', scope:$scope, breadCrumbs: false}); + }; + $scope.addQuestion(); + +// $('#question_shadow').mouseenter(function(){ +// $('#question_shadow').css({ +// "opacity": "1", +// "border": "1px solid", +// "border-color": "rgb(204,204,204)", +// "border-radius": "4px" +// }); +// $('#question_add_btn').show(); +// }); + +// $('#question_shadow').mouseleave(function(){ +// $('#question_shadow').css({ +// "opacity": ".4", +// "border": "1px dashed", +// "border-color": "rgb(204,204,204)", +// "border-radius": "4px" +// }); +// $('#question_add_btn').hide(); +// }) + + + // $('#question_shadow').on("click" , function(){ + // // var survey_width = $('#survey_maker_question_area').width()-10, + // // html = ""; + + // // $('#add_question_btn').attr('disabled', 'disabled') + // // $('#survey_maker_question_area').append(html); + // addQuestion(); + // $('#question_shadow').hide(); + // $('#question_shadow').css({ + // "opacity": ".4", + // "border": "1px dashed", + // "border-color": "rgb(204,204,204)", + // "border-radius": "4px" + // }); + // }); + $scope.finalizeQuestion= function(data){ + var html = '
'; + // angular.forEach(data, function(value, key) { + // html+=''+data.question_text; + // }); + html+=''+data.question_text; + html+=''+data.question_description; + html+=''+data.response_variable_name; + html+=''+data.answer_type; + html+=''+data.answer_option_text; + html+=''+data.answer_option_number; + html+=''+data.answer_option_multiple_choice; + html+=''+data.default_answer; + html+=''+data.is_required; + html+='
'; + + $('#finalized_questions').before(html); + $('#add_question_btn').show(); + $('#add_question_btn').removeAttr('disabled'); + $('#add_question_btn').on("click" , function(){ + $scope.addQuestion(); + $('#add_question_btn').attr('disabled', 'disabled'); + }); + }; + $scope.submitQuestion = function(){ + var form = SurveyQuestionForm, + data = {}, labels={}, fld; + //generator.clearApiErrors(); + Wait('start'); + + try { + for (fld in form.fields) { + if($scope[fld]){ + data[fld] = $scope[fld]; + // labels[fld] = form.fields[fld].label; + } + // if (form.fields[fld].type === 'select' && fld !== 'playbook') { + // data[fld] = $scope[fld].value; + // } else { + // if (fld !== 'variables') { + // data[fld] = $scope[fld]; + // } + // } + } + Wait('stop'); + $scope.survey_questions.push(data); + $('#new_question .aw-form-well').remove(); + // for(fld in form.fields){ + // $scope[fld] = ''; + // } + $scope.finalizeQuestion(data , labels); + + } catch (err) { + Wait('stop'); + Alert("Error", "Error parsing extra variables. Parser returned: " + err); + } + }; + // Save + $scope.formSave = function () { + generator.clearApiErrors(); + Wait('start'); + var url = GetBasePath(base); + url += (base !== 'organizations') ? $routeParams.project_id + '/organizations/' : ''; + Rest.setUrl(url); + Rest.post({ name: $scope.name, description: $scope.description }) + .success(function (data) { + Wait('stop'); + if (base === 'organizations') { + $rootScope.flashMessage = "New organization successfully created!"; + $location.path('/organizations/' + data.id); + } else { + ReturnToCaller(1); + } + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new organization. Post returned status: ' + status }); + }); + }; + + // Cancel + $scope.formReset = function () { + $rootScope.flashMessage = null; + generator.reset(); + }; +} + +SurveyMakerAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'SurveyMakerForm', + 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ClearScope', 'GetBasePath', 'ReturnToCaller', 'Wait', 'SurveyQuestionForm' +]; + + +// function OrganizationsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, OrganizationForm, GenerateForm, Rest, +// Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, Prompt, ClearScope, GetBasePath, Wait, Stream) { + +// ClearScope(); + +// // Inject dynamic view +// var form = OrganizationForm, +// generator = GenerateForm, +// defaultUrl = GetBasePath('organizations'), +// base = $location.path().replace(/^\//, '').split('/')[0], +// master = {}, +// id = $routeParams.organization_id, +// relatedSets = {}; + +// generator.inject(form, { mode: 'edit', related: true, scope: $scope}); +// generator.reset(); + +// // After the Organization is loaded, retrieve each related set +// if ($scope.organizationLoadedRemove) { +// $scope.organizationLoadedRemove(); +// } +// $scope.organizationLoadedRemove = $scope.$on('organizationLoaded', function () { +// for (var set in relatedSets) { +// $scope.search(relatedSets[set].iterator); +// } +// Wait('stop'); +// }); + +// // Retrieve detail record and prepopulate the form +// Wait('start'); +// Rest.setUrl(defaultUrl + id + '/'); +// Rest.get() +// .success(function (data) { +// var fld, related, set; +// LoadBreadCrumbs({ path: '/organizations/' + id, title: data.name }); +// for (fld in form.fields) { +// if (data[fld]) { +// $scope[fld] = data[fld]; +// master[fld] = data[fld]; +// } +// } +// related = data.related; +// for (set in form.related) { +// if (related[set]) { +// relatedSets[set] = { +// url: related[set], +// iterator: form.related[set].iterator +// }; +// } +// } +// // 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 }); +// $scope.$emit('organizationLoaded'); +// }) +// .error(function (data, status) { +// ProcessErrors($scope, data, status, form, { hdr: 'Error!', +// msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); +// }); + + +// // Save changes to the parent +// $scope.formSave = function () { +// var fld, params = {}; +// generator.clearApiErrors(); +// Wait('start'); +// for (fld in form.fields) { +// params[fld] = $scope[fld]; +// } +// Rest.setUrl(defaultUrl + id + '/'); +// Rest.put(params) +// .success(function () { +// Wait('stop'); +// master = params; +// $rootScope.flashMessage = "Your changes were successfully saved!"; +// }) +// .error(function (data, status) { +// ProcessErrors($scope, data, status, OrganizationForm, { hdr: 'Error!', +// msg: 'Failed to update organization: ' + id + '. PUT status: ' + status }); +// }); +// }; + +// $scope.showActivity = function () { +// Stream({ +// scope: $scope +// }); +// }; + +// // Reset the form +// $scope.formReset = function () { +// $rootScope.flashMessage = null; +// generator.reset(); +// for (var fld in master) { +// $scope[fld] = master[fld]; +// } +// }; + +// // Related set: Add button +// $scope.add = function (set) { +// $rootScope.flashMessage = null; +// $location.path('/' + base + '/' + $routeParams.organization_id + '/' + set); +// }; + +// // Related set: Edit button +// $scope.edit = function (set, id) { +// $rootScope.flashMessage = null; +// $location.path('/' + set + '/' + id); +// }; + +// // Related set: Delete button +// $scope['delete'] = function (set, itm_id, name, title) { +// $rootScope.flashMessage = null; + +// var action = function () { +// Wait('start'); +// var url = defaultUrl + $routeParams.organization_id + '/' + set + '/'; +// Rest.setUrl(url); +// Rest.post({ id: itm_id, disassociate: 1 }) +// .success(function () { +// $('#prompt-modal').modal('hide'); +// $scope.search(form.related[set].iterator); +// }) +// .error(function (data, status) { +// $('#prompt-modal').modal('hide'); +// ProcessErrors($scope, data, status, null, { hdr: 'Error!', +// msg: 'Call to ' + url + ' failed. POST returned status: ' + status }); +// }); +// }; + +// Prompt({ +// hdr: 'Delete', +// body: 'Are you sure you want to remove ' + name + ' from ' + $scope.name + ' ' + title + '?', +// action: action +// }); + +// }; +// } + +// OrganizationsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'OrganizationForm', 'GenerateForm', +// 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', 'ClearScope', 'GetBasePath', +// 'Wait', 'Stream' +// ]; \ No newline at end of file diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index f7f3c1658d..e45cf061dc 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -240,6 +240,20 @@ angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'Complet dataTitle: 'Prompt for Extra Variables', dataContainer: "body" }, + enable_survey: { + type: 'custom', + column: 2, + control: '
'+ + '
'+ + ''+ + '
'+ + ''+ + '
'+ + '
' + }, allow_callbacks: { label: 'Allow Provisioning Callbacks', type: 'checkbox', diff --git a/awx/ui/static/js/forms/SurveyMaker.js b/awx/ui/static/js/forms/SurveyMaker.js new file mode 100644 index 0000000000..a9af2e0967 --- /dev/null +++ b/awx/ui/static/js/forms/SurveyMaker.js @@ -0,0 +1,73 @@ +/********************************************* + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * SurveyMaker.js + * Form definition for survey maker model + * + * + */ + /** + * @ngdoc function + * @name forms.function:SurveyMaker + * @description This form is for adding/editing a survey +*/ +angular.module('SurveyMakerFormDefinition', []) + .value('SurveyMakerForm', { + + addTitle: 'Add Survey', //Title in add mode + editTitle: 'Edit Survey', //Title in edit mode + name: 'survey_maker', //entity or model name in singular form + well: true, + collapse: true, + collapseTitle: "Properties", + collapseMode: 'edit', + collapseOpen: true, + + actions: { + stream: { + 'class': "btn-primary btn-xs activity-btn", + ngClick: "showActivity()", + awToolTip: "View Activity Stream", + dataPlacement: "top", + icon: "icon-comments-alt", + mode: 'edit', + iconSize: 'large' + } + }, + + fields: { + name: { + label: 'Survey Name', + type: 'text', + addRequired: true, + editRequired: true, + capitalize: false + }, + description: { + label: 'Survey Description', + type: 'text', + addRequired: false, + editRequired: false + }, + questions: { + type: 'custom', + control: ''+ + '
'+ + ''+ + '
' + + } + }, + + buttons: { //for now always generates