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