From 8217544376bc4f57ad393f873a03dd049db90b3f Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Sat, 29 Mar 2014 17:46:39 -0400 Subject: [PATCH] Form generator can now call the list generator when creating related lists. Job Template page is now using this method. We can now inject CompletedJobs and Schedules list objects on any page with full jobs page or schedule page functionality. --- awx/ui/static/js/controllers/JobTemplates.js | 26 +- awx/ui/static/js/controllers/Jobs.js | 6 +- awx/ui/static/js/forms/JobTemplates.js | 162 +++-------- awx/ui/static/js/helpers/Jobs.js | 3 - awx/ui/static/lib/ansible/form-generator.js | 286 ++++++++++--------- awx/ui/static/lib/ansible/list-generator.js | 7 +- 6 files changed, 213 insertions(+), 277 deletions(-) diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index ae200f1c4a..e8ff6012e4 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -114,7 +114,7 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa // Inject dynamic view var defaultUrl = GetBasePath('job_templates'), - form = JobTemplateForm, + form = JobTemplateForm(), generator = GenerateForm, master = {}, CloudCredentialList = {}, @@ -336,14 +336,14 @@ JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$lo function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, - CredentialList, ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, + CredentialList, ProjectList, LookUpInit, GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, Wait, Stream, Empty, Prompt, ParseVariableString, ToJSON) { ClearScope(); var defaultUrl = GetBasePath('job_templates'), generator = GenerateForm, - form = JobTemplateForm, + form = JobTemplateForm(), loadingFinishedCount = 0, base = $location.path().replace(/^\//, '').split('/')[0], master = {}, @@ -553,7 +553,7 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP Rest.setUrl(defaultUrl + ':id/'); Rest.get({ params: { id: id } }) .success(function (data) { - var fld, i, related, set; + var fld, i; LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name }); for (fld in form.fields) { if (fld !== 'variables' && data[fld] !== null && data[fld] !== undefined) { @@ -586,16 +586,9 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP } $scope.url = data.url; - related = data.related; - for (set in form.related) { - if (related[set]) { - relatedSets[set] = { - url: related[set], - iterator: form.related[set].iterator - }; - } - } - + + relatedSets = form.relatedSets(data.related); + $scope.callback_url = data.related.callback; master.callback_url = $scope.callback_url; @@ -625,16 +618,17 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP field: 'project' }); - // 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('jobTemplateLoaded', data.related.cloud_credential); }) .error(function (data, status) { @@ -757,7 +751,7 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP JobTemplatesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', - 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'PromptPasswords', + 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt', 'ParseVariableString', 'ToJSON' ]; \ No newline at end of file diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index 051a4d476b..77902099c6 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -95,7 +95,7 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea scope: queued_scope, list: QueuedJobsList, id: 'queued-jobs', - url: GetBasePath('unified_jobs') + '?or__status=pending&or__status=waiting$or__status=new' + url: GetBasePath('unified_jobs') + '?or__status=pending&or__status=waiting&or__status=new' }); scheduled_scope = $scope.$new(); LoadScope({ @@ -179,7 +179,7 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea GetChoices({ scope: $scope, - url: GetBasePath('jobs'), + url: GetBasePath('unified_jobs'), field: 'status', variable: 'status_choices', callback: 'choicesReady' @@ -187,7 +187,7 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea GetChoices({ scope: $scope, - url: '/static/sample/data/types/data.json', //GetBasePath('jobs') + url: GetBasePath('unified_jobs'), field: 'type', variable: 'type_choices', callback: 'choicesReady' diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index eac75d070e..97782b1cf2 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -4,11 +4,16 @@ * JobTemplates.js * Form definition for Job Template model * + * To get the JobTemplateForm object: JobTemplateForm(); * */ -angular.module('JobTemplateFormDefinition', []) - .value('JobTemplateForm', { - + +'use strict'; + +angular.module('JobTemplateFormDefinition', ['SchedulesListDefinition', 'CompletedJobsDefinition']) + + .value ('JobTemplateFormObject', { + addTitle: 'Create Job Templates', editTitle: '{{ name }}', name: 'job_templates', @@ -283,119 +288,44 @@ angular.module('JobTemplateFormDefinition', []) related: { - jobs: { - type: 'collection', - title: 'Jobs', - iterator: 'job', - index: false, - open: false, - - actions: { - reset: { - dataPlacement: 'top', - icon: "icon-undo", - mode: 'all', - 'class': 'btn-xs btn-primary', - awToolTip: "Reset the search filter", - ngClick: "resetSearch('job')", - iconSize: 'large' - } - }, - - fields: { - id: { - label: 'Job ID', - key: true, - desc: true, - searchType: 'int' - }, - created: { - label: 'Date', - link: false, - searchable: false - }, - status: { - label: 'Status', - "class": 'job-{{ job.status }}', - searchType: 'select', - linkTo: "{{}} job.statusLinkTo }}", - searchOptions: [ - { name: "new", value: "new" }, - { name: "waiting", value: "waiting" }, - { name: "pending", value: "pending" }, - { name: "running", value: "running" }, - { name: "successful", value: "successful" }, - { name: "error", value: "error" }, - { name: "failed", value: "failed" }, - { name: "canceled", value: "canceled" } - ], - badgeIcon: 'fa icon-job-{{ job.status }}', - badgePlacement: 'left', - badgeToolTip: "{{ job.statusBadgeToolTip }}", - badgeTipPlacement: 'top', - badgeNgHref: "{{ job.statusLinkTo }}", - awToolTip: "{{ job.statusBadgeToolTip }}", - dataPlacement: 'top' - } - }, - - fieldActions: { - edit: { - label: 'View', - ngClick: "edit('jobs', job.id, job.name)", - icon: 'icon-zoom-in' - } - } + schedules: { + include: "SchedulesList" }, - - schedules: { - type: 'collection', - title: 'Schedules', - iterator: 'schedule', - index: true, - open: false, - - fields: { - name: { - key: true, - label: 'Name' - }, - dtstart: { - label: 'Start' - }, - dtend: { - label: 'End' - } - }, - - actions: { - add: { - mode: 'all', - ngClick: 'addSchedule()', - awToolTip: 'Add a new schedule' - } - }, - - fieldActions: { - edit: { - label: 'Edit', - ngClick: "editSchedule(schedule.id)", - icon: 'icon-edit', - awToolTip: 'Edit schedule', - dataPlacement: 'top' - }, - - "delete": { - label: 'Delete', - ngClick: "deleteSchedule(schedule.id)", - icon: 'icon-trash', - awToolTip: 'Delete schedule', - dataPlacement: 'top' - } - } - + completed_jobs: { + include: "CompletedJobsList" } - } - - }); //InventoryForm + }, + relatedSets: function(urls) { + return { + completed_jobs: { + iterator: 'completed_job', + url: urls.jobs + }, + schedules: { + iterator: 'schedule', + url: urls.schedules + } + }; + } + }) + + .factory('JobTemplateForm', ['JobTemplateFormObject', 'SchedulesList', 'CompletedJobsList', + function(JobTemplateFormObject, SchedulesList, CompletedJobsList) { + return function() { + var itm; + + for (itm in JobTemplateFormObject.related) { + if (JobTemplateFormObject.related[itm].include === "SchedulesList") { + JobTemplateFormObject.related[itm] = SchedulesList; + JobTemplateFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list + } + if (JobTemplateFormObject.related[itm].include === "CompletedJobsList") { + JobTemplateFormObject.related[itm] = CompletedJobsList; + JobTemplateFormObject.related[itm].generateList = true; + } + } + + return JobTemplateFormObject; + }; + }]); diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index 0bc829f527..9daafff92e 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -204,9 +204,6 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job scope.iterator = list.iterator; - - // The following bits probably don't belong here once the API is available. - if (scope.removePostRefresh) { scope.removePostRefresh(); } diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js index 53547b6a78..01b8a521ec 100644 --- a/awx/ui/static/lib/ansible/form-generator.js +++ b/awx/ui/static/lib/ansible/form-generator.js @@ -10,11 +10,11 @@ 'use strict'; -angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities']) +angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator']) -.factory('GenerateForm', ['$rootScope', '$location', '$cookieStore', '$compile', 'SearchWidget', 'PaginateWidget', 'Attr', +.factory('GenerateForm', ['$rootScope', '$location', '$compile', 'GenerateList', 'SearchWidget', 'PaginateWidget', 'Attr', 'Icon', 'Column', 'NavigationLink', 'HelpCollapse', 'Button', 'DropDown', 'Empty', 'SelectIcon', 'Store', - function ($rootScope, $location, $cookieStore, $compile, SearchWidget, PaginateWidget, Attr, Icon, Column, NavigationLink, + function ($rootScope, $location, $compile, GenerateList, SearchWidget, PaginateWidget, Attr, Icon, Column, NavigationLink, HelpCollapse, Button, DropDown, Empty, SelectIcon, Store) { return { @@ -1338,148 +1338,158 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities']) // Create TB accordians with imbedded lists for related collections // Should not be called directly. Called internally by build(). // - var idx = 1, - form = this.form, - html, act, fAction, fld, itm, action, cnt, base; + var form = this.form, + html = '', + itm, collection; - if (options.collapseAlreadyStarted) { - // A collapse is already started for 'Properties' - html = ''; - } - else { - html = "
\n"; + if (!options.collapseAlreadyStarted) { + html = "
\n"; } for (itm in form.related) { - if (form.related[itm].type === 'collection') { - html += "

" + form.related[itm].title + "

\n"; - html += "
\n"; - - if (form.related[itm].instructions) { - html += "
\n"; - html += "\n"; - html += "Hint: " + form.related[itm].instructions + "\n"; - html += "
\n"; - } - - //html += "
\n"; - html += "
\n"; - - html += SearchWidget({ - iterator: form.related[itm].iterator, - template: form.related[itm], - mini: true - }); - - html += "
\n"; - html += "
\n"; - - for (act in form.related[itm].actions) { - action = form.related[itm].actions[act]; - html += this.button({ - btn: action, - action: act, - toolbar: true - }); - } - - html += "
\n"; - html += "
\n"; - html += "
\n"; - - // Start the list - html += "
\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += (form.related[itm].index === undefined || form.related[itm].index !== false) ? "\n" : ""; - for (fld in form.related[itm].fields) { - html += "\n"; - } - html += "\n"; - html += "\n"; - html += ""; - html += "\n"; - - html += "\n"; - if (form.related[itm].index === undefined || form.related[itm].index !== false) { - html += "\n"; - } - cnt = 1; - base = (form.related[itm].base) ? form.related[itm].base : itm; - base = base.replace(/^\//, ''); - for (fld in form.related[itm].fields) { - cnt++; - html += Column({ - list: form.related[itm], - fld: fld, - options: options, - base: base - }); - } - - // Row level actions - html += ""; - html += "\n"; - - // Message for when a related collection is empty - html += "\n"; - html += "\n"; - html += "\n"; - - // Message for loading - html += "\n"; - html += "\n"; - html += "\n"; - - // End List - html += "\n"; - html += "
#" + - form.related[itm].fields[fld].label; - html += " Actions
{{ $index + ((" + form.related[itm].iterator + "_page - 1) * " + - form.related[itm].iterator + "_page_size) + 1 }}."; - for (act in form.related[itm].fieldActions) { - fAction = form.related[itm].fieldActions[act]; - html += " " + fAction.label + "": ""; - html += ""; - } - html += "
No records matched your search.
Loading...
\n"; - //html += "
\n"; // close well - html += "
\n"; // close list-wrapper div - - html += PaginateWidget({ - set: itm, - iterator: form.related[itm].iterator, - mini: true - }); - - // End Accordion - html += "
\n"; // accordion inner - - idx++; + collection = form.related[itm]; + html += "

" + (collection.title || collection.editTitle) + "

\n"; + html += "
\n"; + if (collection.generateList) { + html += GenerateList.buildHTML(collection, { mode: 'edit', breadCrumbs: false }); } + else { + html += this.GenerateColleciton({ form: form, related: itm }, options); + } + html += "
\n"; // accordion inner + } + + if (!options.collapseAlreadyStarted) { + html += "
\n"; // accordion body } - html += "
\n"; // accordion body - html += "\n"; + //console.log(html); + + return html; + }, + + GenerateColleciton: function(params, options) { + var html = '', + form = params.form, + itm = params.related, + collection = form.related[itm], + act, action, fld, cnt, base, fAction; + + if (collection.instructions) { + html += "
\n"; + html += "\n"; + html += "Hint: " + collection.instructions + "\n"; + html += "
\n"; + } + + //html += "
\n"; + html += "
\n"; + + html += SearchWidget({ + iterator: collection.iterator, + template: collection, + mini: true + }); + + html += "
\n"; + html += "
\n"; + + for (act in collection.actions) { + action = collection.actions[act]; + html += this.button({ + btn: action, + action: act, + toolbar: true + }); + } + + html += "
\n"; + html += "
\n"; + html += "
\n"; + + // Start the list + html += "
\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += (collection.index === undefined || collection.index !== false) ? "\n" : ""; + for (fld in collection.fields) { + html += "\n"; + } + html += "\n"; + html += "\n"; + html += ""; + html += "\n"; + + html += "\n"; + if (collection.index === undefined || collection.index !== false) { + html += "\n"; + } + cnt = 1; + base = (collection.base) ? collection.base : itm; + base = base.replace(/^\//, ''); + for (fld in collection.fields) { + cnt++; + html += Column({ + list: collection, + fld: fld, + options: options, + base: base + }); + } + + // Row level actions + html += ""; + html += "\n"; + + // Message for when a related collection is empty + html += "\n"; + html += "\n"; + html += "\n"; + + // Message for loading + html += "\n"; + html += "\n"; + html += "\n"; + + // End List + html += "\n"; + html += "
#" + + collection.fields[fld].label; + html += " Actions
{{ $index + ((" + collection.iterator + "_page - 1) * " + + collection.iterator + "_page_size) + 1 }}."; + for (act in collection.fieldActions) { + fAction = collection.fieldActions[act]; + html += " " + fAction.label + "": ""; + html += ""; + } + html += "
No records matched your search.
Loading...
\n"; + //html += "
\n"; // close well + html += "
\n"; // close list-wrapper div + + html += PaginateWidget({ + set: itm, + iterator: collection.iterator, + mini: true + }); return html; } }; diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index 728fac30f5..bef9b98a66 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -34,6 +34,11 @@ angular.module('ListGenerator', ['GeneratorHelpers']) button: Button, + buildHTML: function(list, options) { + this.setList(list); + return this.build(options); + }, + inject: function (list, options) { // options.mode = one of edit, select or lookup // @@ -44,7 +49,7 @@ angular.module('ListGenerator', ['GeneratorHelpers']) // // hdr: // - // Inject into a custom element using options.id: <'.selector'> + // Inject into a custom element using options.id: // Control breadcrumb creation with options.breadCrumbs: // var element;