diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000..2460542355 Binary files /dev/null and b/.DS_Store differ diff --git a/awx/.DS_Store b/awx/.DS_Store new file mode 100644 index 0000000000..6bade33a9b Binary files /dev/null and b/awx/.DS_Store differ diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 2d3de8ce25..719d3be86f 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -1118,8 +1118,8 @@ class JobEvent(models.Model): return None def save(self, *args, **kwargs): - self.failed = bool(self.event in self.FAILED_EVENTS) - # FIXME: Propagage failed flag to parent events. + if self.event in self.FAILED_EVENTS: + self.failed = True try: if not self.host and self.event_data.get('host', ''): self.host = self.job.inventory.hosts.get(name=self.event_data['host']) @@ -1129,9 +1129,18 @@ class JobEvent(models.Model): self.task = self.event_data.get('task', '') self.parent = self._find_parent() super(JobEvent, self).save(*args, **kwargs) + self.update_parent_failed() self.update_hosts() self.update_host_summary_from_stats() + def update_parent_failed(self): + # Propagage failed flag to parent events. + if self.failed and self.parent and not self.parent.failed: + p = self.parent + p.failed = True + p.save() + p.update_parent_failed() + def update_hosts(self, extra_hosts=None): extra_hosts = extra_hosts or [] hostnames = set() diff --git a/awx/main/serializers.py b/awx/main/serializers.py index 1366021c45..38e8e565c1 100644 --- a/awx/main/serializers.py +++ b/awx/main/serializers.py @@ -432,7 +432,7 @@ class JobEventSerializer(BaseSerializer): model = JobEvent fields = ('id', 'url', 'created', 'job', 'event', 'event_display', 'event_data', 'failed', 'host', 'related', 'summary_fields', - 'parent') + 'parent', 'play', 'task') def get_related(self, obj): res = super(JobEventSerializer, self).get_related(obj) diff --git a/awx/main/tests/tasks.py b/awx/main/tests/tasks.py index 27ba7a0b21..1ad5307ad1 100644 --- a/awx/main/tests/tasks.py +++ b/awx/main/tests/tasks.py @@ -213,6 +213,53 @@ class RunJobTest(BaseCeleryTest): 'expected no traceback, got:\n%s' % job.result_traceback) + def check_job_events(self, job, runner_status='ok', plays=1, tasks=1): + job_events = job.job_events.all() + for job_event in job_events: + unicode(job_event) # For test coverage. + job_event.save() + should_be_failed = bool(runner_status not in ('ok', 'skipped')) + host_pks = set([self.host.pk]) + qs = job_events.filter(event='playbook_on_start') + self.assertEqual(qs.count(), 1) + for evt in qs: + self.assertFalse(evt.host, evt) + self.assertFalse(evt.play, evt) + self.assertFalse(evt.task, evt) + self.assertEqual(evt.failed, should_be_failed) + self.assertEqual(set(evt.hosts.values_list('pk', flat=True)), + host_pks) + qs = job_events.filter(event='playbook_on_play_start') + self.assertEqual(qs.count(), plays) + for evt in qs: + self.assertFalse(evt.host, evt) + self.assertTrue(evt.play, evt) + self.assertFalse(evt.task, evt) + self.assertEqual(evt.failed, should_be_failed) + self.assertEqual(set(evt.hosts.values_list('pk', flat=True)), + host_pks) + qs = job_events.filter(event='playbook_on_task_start') + self.assertEqual(qs.count(), tasks) + for evt in qs: + self.assertFalse(evt.host, evt) + self.assertTrue(evt.play, evt) + self.assertTrue(evt.task, evt) + self.assertEqual(evt.failed, should_be_failed) + self.assertEqual(set(evt.hosts.values_list('pk', flat=True)), + host_pks) + qs = job_events.filter(event=('runner_on_%s' % runner_status)) + self.assertEqual(qs.count(), tasks) + for evt in qs: + self.assertEqual(evt.host, self.host) + self.assertTrue(evt.play, evt) + self.assertTrue(evt.task, evt) + self.assertEqual(evt.failed, should_be_failed) + self.assertEqual(set(evt.hosts.values_list('pk', flat=True)), + host_pks) + qs = job_events.filter(event__startswith='runner_') + qs = qs.exclude(event=('runner_on_%s' % runner_status)) + self.assertEqual(qs.count(), 0) + def test_run_job(self): self.create_test_project(TEST_PLAYBOOK) job_template = self.create_test_job_template() @@ -223,17 +270,7 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(job.status, 'pending') job = Job.objects.get(pk=job.pk) self.check_job_result(job, 'successful') - job_events = job.job_events.all() - for job_event in job_events: - unicode(job_event) # For test coverage. - job_event.save() - self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2) - self.assertEqual(job_events.filter(event='runner_on_ok').count(), 2) - for evt in job_events.filter(event='runner_on_ok'): - self.assertEqual(evt.host, self.host) - self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) + self.check_job_events(job, 'ok', 1, 2) for job_host_summary in job.job_host_summaries.all(): unicode(job_host_summary) # For test coverage. self.assertFalse(job_host_summary.failed) @@ -261,14 +298,7 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(job.status, 'pending') job = Job.objects.get(pk=job.pk) self.check_job_result(job, 'successful') - job_events = job.job_events.all() - self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2) - self.assertEqual(job_events.filter(event='runner_on_skipped').count(), 2) - for evt in job_events.filter(event='runner_on_skipped'): - self.assertEqual(evt.host, self.host) - self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) + self.check_job_events(job, 'skipped', 1, 2) for job_host_summary in job.job_host_summaries.all(): self.assertFalse(job_host_summary.failed) self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary) @@ -295,13 +325,7 @@ class RunJobTest(BaseCeleryTest): self.assertEqual(job.status, 'pending') job = Job.objects.get(pk=job.pk) self.check_job_result(job, 'failed') - job_events = job.job_events.all() - self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 1) - self.assertEqual(job_events.filter(event='runner_on_failed').count(), 1) - self.assertEqual(job_events.get(event='runner_on_failed').host, self.host) - self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) + self.check_job_events(job, 'failed', 1, 1) for job_host_summary in job.job_host_summaries.all(): self.assertTrue(job_host_summary.failed) self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary) @@ -330,13 +354,7 @@ class RunJobTest(BaseCeleryTest): # Since we don't actually run the task, the --check should indicate # everything is successful. self.check_job_result(job, 'successful') - job_events = job.job_events.all() - self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 1) - self.assertEqual(job_events.filter(event='runner_on_skipped').count(), 1) - self.assertEqual(job_events.get(event='runner_on_skipped').host, self.host) - self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) + self.check_job_events(job, 'skipped', 1, 1) for job_host_summary in job.job_host_summaries.all(): self.assertFalse(job_host_summary.failed) self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary) diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index e2696770e4..e771dadef5 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -93,14 +93,14 @@ class CallbackModule(object): auth = TokenAuth(self.auth_token) else: auth = None + port = parts.port or (443 if parts.scheme == 'https' else 80) url = urlparse.urlunsplit([parts.scheme, - '%s:%d' % (parts.hostname, parts.port), + '%s:%d' % (parts.hostname, port), parts.path, parts.query, parts.fragment]) url_path = '/api/v1/jobs/%d/job_events/' % self.job_id url = urlparse.urljoin(url, url_path) headers = {'content-type': 'application/json'} response = requests.post(url, data=data, headers=headers, auth=auth) - print response.content response.raise_for_status() def _log_event(self, event, **event_data): diff --git a/awx/scripts/inventory.py b/awx/scripts/inventory.py index dc7e117351..e297ce3f21 100755 --- a/awx/scripts/inventory.py +++ b/awx/scripts/inventory.py @@ -71,8 +71,9 @@ class InventoryScript(object): auth = TokenAuth(self.auth_token) else: auth = None + port = parts.port or (443 if parts.scheme == 'https' else 80) url = urlparse.urlunsplit([parts.scheme, - '%s:%d' % (parts.hostname, parts.port), + '%s:%d' % (parts.hostname, port), parts.path, parts.query, parts.fragment]) url_path = '/api/v1/inventories/%d/script/' % self.inventory_id if self.hostname: @@ -80,7 +81,8 @@ class InventoryScript(object): url = urlparse.urljoin(url, url_path) response = requests.get(url, auth=auth) response.raise_for_status() - sys.stdout.write(json.dumps(response.json(), indent=self.indent) + '\n') + sys.stdout.write(json.dumps(json.loads(response.content), + indent=self.indent) + '\n') def run(self): try: diff --git a/awx/settings/local_settings.py.example b/awx/settings/local_settings.py.example index 306b07ed34..4ef920b675 100644 --- a/awx/settings/local_settings.py.example +++ b/awx/settings/local_settings.py.example @@ -22,13 +22,13 @@ DATABASES = { } } -# Continue to use SQLite for unit tests instead of PostgreSQL. -if 'test' in sys.argv: +# Use SQLite for unit tests instead of PostgreSQL. +if len(sys.argv) >= 2 and sys.argv[1] == 'test': DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'awx.sqlite3'), - # Test database cannot be :memory: for celery/inventory tests to work. + # Test database cannot be :memory: for celery/inventory tests. 'TEST_NAME': os.path.join(BASE_DIR, 'awx_test.sqlite3'), } } diff --git a/awx/ui/.DS_Store b/awx/ui/.DS_Store new file mode 100644 index 0000000000..2a43b86b96 Binary files /dev/null and b/awx/ui/.DS_Store differ diff --git a/awx/ui/static/.DS_Store b/awx/ui/static/.DS_Store new file mode 100644 index 0000000000..f10092a748 Binary files /dev/null and b/awx/ui/static/.DS_Store differ diff --git a/awx/ui/static/css/ansible-ui.css b/awx/ui/static/css/ansible-ui.css index 5fb806c749..5044e64d1e 100644 --- a/awx/ui/static/css/ansible-ui.css +++ b/awx/ui/static/css/ansible-ui.css @@ -13,7 +13,7 @@ body { color: #36454F; - padding-top: 40px; + padding-top: 60px; } .text-center { @@ -42,6 +42,7 @@ background-repeat: repeat-x; border-color: #36454F; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#36454F', endColorstr='#36454F', GradientType=0); + padding-right: 15px; } .navbar-inverse .nav > li > a { @@ -55,10 +56,15 @@ .navbar .brand { margin-left: 15px; + padding: 0; } .navbar .brand img { - width: 130px; + width: 260px; + } + + .navbar .nav { + margin-top: 15px; } a:hover { diff --git a/awx/ui/static/img/logo.png b/awx/ui/static/img/logo.png new file mode 100644 index 0000000000..87a93bff65 Binary files /dev/null and b/awx/ui/static/img/logo.png differ diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 76a3545305..7e28960b19 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -175,7 +175,7 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa var form = InventoryForm; var generator = GenerateForm; var scope = generator.inject(form, {mode: 'add', related: false}); - scope.parseType = 'json'; + scope.parseType = 'yaml'; generator.reset(); LoadBreadCrumbs(); @@ -239,7 +239,7 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa }); } catch(err) { - Alert("Error", "Error parsing inventory variables. Parser returned " + err); + Alert("Error", "Error parsing inventory variables. Parser returned: " + err); } }; @@ -275,7 +275,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP ParseTypeChange(scope); - scope.parseType = 'json'; + scope.parseType = 'yaml'; scope['inventory_id'] = id; // Retrieve each related set and any lookups @@ -300,10 +300,10 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP Rest.get() .success( function(data, status, headers, config) { if ($.isEmptyObject(data)) { - scope.variables = "\{\}"; + scope.variables = "---"; } else { - scope.variables = JSON.stringify(data, null, " "); + scope.variables = jsyaml.safeDump(data); } }) .error( function(data, status, headers, config) { @@ -313,7 +313,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP }); } else { - scope.variables = "\{\}"; + scope.variables = "---"; } }); @@ -371,7 +371,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP }); } catch(err) { - Alert("Error", "Error parsing inventory variables. Parser returned " + err); + Alert("Error", "Error parsing inventory variables. Parser returned: " + err); } }; diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 9b2e56e9d3..46aafcc894 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -159,7 +159,7 @@ JobTemplatesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$rout function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, - GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup) + GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -170,6 +170,9 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP var generator = GenerateForm; var scope = generator.inject(form, {mode: 'add', related: false}); var master = {}; + + scope.parseType = 'yaml'; + ParseTypeChange(scope); scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }]; scope.verbosity_options = [ @@ -243,25 +246,43 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP // Save scope.formSave = function() { - Rest.setUrl(defaultUrl); var data = {} - for (var fld in form.fields) { - if (form.fields[fld].type == 'select' && fld != 'playbook') { - data[fld] = scope[fld].value; + try { + // Make sure we have valid variable data + if (scope.parseType == 'json') { + var myjson = JSON.parse(scope.variables); //make sure JSON parses + var json_data = scope.variables; } else { - data[fld] = scope[fld]; - } + var json_data = jsyaml.load(scope.variables); //parse yaml + } + + for (var fld in form.fields) { + if (form.fields[fld].type == 'select' && fld != 'playbook') { + data[fld] = scope[fld].value; + } + else { + if (fld != 'variables') { + data[fld] = scope[fld]; + } + } + } + data.extra_vars = json_data; + Rest.setUrl(defaultUrl); + Rest.post(data) + .success( function(data, status, headers, config) { + var base = $location.path().replace(/^\//,'').split('/')[0]; + (base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to add new job template. POST returned status: ' + status }); + }); + + } + catch(err) { + Alert("Error", "Error parsing extra variables. Parser returned: " + err); } - Rest.post(data) - .success( function(data, status, headers, config) { - var base = $location.path().replace(/^\//,'').split('/')[0]; - (base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1); - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to add new project. POST returned status: ' + status }); - }); }; // Reset @@ -277,13 +298,13 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP JobTemplatesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', - 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'md5Setup' ]; + 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'md5Setup', 'ParseTypeChange' ]; 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) + ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, ParseTypeChange) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -293,6 +314,9 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route var form = JobTemplateForm; var scope = generator.inject(form, {mode: 'edit', related: true}); + scope.parseType = 'yaml'; + ParseTypeChange(scope); + // Our job type options scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }]; scope.verbosity_options = [ @@ -365,7 +389,7 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route .success( function(data, status, headers, config) { LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name }); for (var fld in form.fields) { - if (data[fld] !== null && data[fld] !== undefined) { + if (fld != 'variables' && data[fld] !== null && data[fld] !== undefined) { if (form.fields[fld].type == 'select') { if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) { for (var i=0; i < scope[fld + '_options'].length; i++) { @@ -383,6 +407,16 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route } master[fld] = scope[fld]; } + if (fld == 'variables') { + // Parse extra_vars, converting to YAML. + if ($.isEmptyObject(data.extra_vars) || data.extra_vars == "\{\}") { + scope.variables = "---"; + } + else { + scope.variables = jsyaml.safeDump(JSON.parse(data.extra_vars)); + } + master.variables = scope.variables; + } if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) { scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; @@ -435,24 +469,42 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route // Save changes to the parent scope.formSave = function() { var data = {} - for (var fld in form.fields) { - if (form.fields[fld].type == 'select' && fld != 'playbook') { - data[fld] = scope[fld].value; + try { + // Make sure we have valid variable data + if (scope.parseType == 'json') { + var myjson = JSON.parse(scope.variables); //make sure JSON parses + var json_data = scope.variables; } else { - data[fld] = scope[fld]; - } - } - Rest.setUrl(defaultUrl + $routeParams.id + '/'); - Rest.put(data) - .success( function(data, status, headers, config) { - var base = $location.path().replace(/^\//,'').split('/')[0]; - (base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1); - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to update team: ' + $routeParams.id + '. PUT status: ' + status }); - }); + var json_data = jsyaml.load(scope.variables); //parse yaml + } + + for (var fld in form.fields) { + if (form.fields[fld].type == 'select' && fld != 'playbook') { + data[fld] = scope[fld].value; + } + else { + if (fld != 'variables') { + data[fld] = scope[fld]; + } + } + } + data.extra_vars = json_data; + Rest.setUrl(defaultUrl + id + '/'); + Rest.put(data) + .success( function(data, status, headers, config) { + var base = $location.path().replace(/^\//,'').split('/')[0]; + (base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to update job template. PUT returned status: ' + status }); + }); + + } + catch(err) { + Alert("Error", "Error parsing extra variables. Parser returned: " + err); + } }; // Cancel @@ -461,6 +513,7 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route for (var fld in master) { scope[fld] = master[fld]; } + scope.parseType = 'yaml'; $('#forks-slider').slider("option", "value", scope.forks); }; @@ -506,5 +559,5 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route JobTemplatesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', - 'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath', 'md5Setup' + 'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath', 'md5Setup', 'ParseTypeChange' ]; diff --git a/awx/ui/static/js/controllers/Permissions.js b/awx/ui/static/js/controllers/Permissions.js index aefc176b38..e194a76c72 100644 --- a/awx/ui/static/js/controllers/Permissions.js +++ b/awx/ui/static/js/controllers/Permissions.js @@ -1,7 +1,7 @@ function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, PermissionList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, - ClearScope, ProcessErrors, GetBasePath) + ClearScope, ProcessErrors, GetBasePath, CheckAccess) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -22,13 +22,13 @@ function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Res LoadBreadCrumbs(); scope.addPermission = function() { - if (checkAccess()) { + if (CheckAccess()) { $location.path($location.path() + '/add'); } } scope.editPermission = function(id) { - if (checkAccess()) { + if (CheckAccess()) { $location.path($location.path() + '/' + id); } } @@ -60,7 +60,7 @@ function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Res PermissionsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'PermissionList', 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', - 'ClearScope', 'ProcessErrors', 'GetBasePath' + 'ClearScope', 'ProcessErrors', 'GetBasePath', 'CheckAccess' ]; diff --git a/awx/ui/static/js/forms/Groups.js b/awx/ui/static/js/forms/Groups.js index 92a01195c5..5b260623fb 100644 --- a/awx/ui/static/js/forms/Groups.js +++ b/awx/ui/static/js/forms/Groups.js @@ -36,7 +36,7 @@ angular.module('GroupFormDefinition', []) editRequird: false, rows: 10, "class": 'modal-input-xlarge', - "default": "\{\}", + "default": "---", dataTitle: 'Group Variables', dataPlacement: 'right', awPopOver: "
Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + diff --git a/awx/ui/static/js/forms/Hosts.js b/awx/ui/static/js/forms/Hosts.js index 4b35f18de7..15edca75d7 100644 --- a/awx/ui/static/js/forms/Hosts.js +++ b/awx/ui/static/js/forms/Hosts.js @@ -41,7 +41,7 @@ angular.module('HostFormDefinition', []) editRequird: false, rows: 10, "class": "modal-input-xlarge", - "default": "\{\}", + "default": "---", awPopOver: "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + 'View JSON examples at www.json.org
' + 'View YAML examples at ansibleworks.com
', diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index d2716b2475..e928630e59 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -63,7 +63,7 @@ angular.module('InventoryFormDefinition', []) editRequird: false, rows: 10, "class": "modal-input-xlarge", - "default": "\{\}", + "default": "---", awPopOver: "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + 'View JSON examples at www.json.org
' + 'View YAML examples at ansibleworks.com
', diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index f51fe001f0..98f09a21bc 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -127,17 +127,17 @@ angular.module('JobTemplateFormDefinition', []) dataTitle: 'Verbosity', dataPlacement: 'left' }, - extra_vars: { + variables: { label: 'Extra Variables', type: 'textarea', rows: 6, "class": 'span12', addRequired: false, editRequired: false, - "default": "\{\}", + "default": "---", column: 2, awPopOver: "Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter " + - "for ansible-playbook. Provide key=value pairs or JSON.
Click here to view documentation and examples.
", dataTitle: 'Extra Variables', dataPlacement: 'left' diff --git a/awx/ui/static/js/forms/Teams.js b/awx/ui/static/js/forms/Teams.js index 5cf23f998e..747ae1767b 100644 --- a/awx/ui/static/js/forms/Teams.js +++ b/awx/ui/static/js/forms/Teams.js @@ -126,17 +126,20 @@ angular.module('TeamFormDefinition', []) label: 'Name', ngClick: "edit('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}')" }, + inventory: { + label: 'Inventory', + sourceModel: 'inventory', + sourceField: 'name', + ngBind: 'permission.summary_fields.inventory.name', + }, project: { label: 'Project', sourceModel: 'project', sourceField: 'name', ngBind: 'permission.summary_fields.project.name', }, - inventory: { - label: 'Inventory', - sourceModel: 'inventory', - sourceField: 'name', - ngBind: 'permission.summary_fields.inventory.name', + permission_type: { + label: 'Permission' } }, diff --git a/awx/ui/static/js/forms/Users.js b/awx/ui/static/js/forms/Users.js index 44885becb9..c2ed938b9d 100644 --- a/awx/ui/static/js/forms/Users.js +++ b/awx/ui/static/js/forms/Users.js @@ -163,18 +163,22 @@ angular.module('UserFormDefinition', []) label: 'Name', ngClick: "edit('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}')" }, + inventory: { + label: 'Inventory', + sourceModel: 'inventory', + sourceField: 'name', + ngBind: 'permission.summary_fields.inventory.name', + }, project: { label: 'Project', sourceModel: 'project', sourceField: 'name', ngBind: 'permission.summary_fields.project.name', }, - inventory: { - label: 'Inventory', - sourceModel: 'inventory', - sourceField: 'name', - ngBind: 'permission.summary_fields.inventory.name', + permission_type: { + label: 'Permission' } + }, fieldActions: { diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 21c393fc4e..9aa26dcf1a 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -175,7 +175,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' scope.formModalActionLabel = 'Save'; scope.formModalHeader = 'Create Group'; scope.formModalCancelShow = true; - scope.parseType = 'json'; + scope.parseType = 'yaml'; ParseTypeChange(scope); $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); @@ -237,7 +237,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' }); } catch(err) { - Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err); + Alert("Error", "Error parsing group variables. Parser returned: " + err); } } @@ -270,7 +270,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' scope.formModalActionLabel = 'Save'; scope.formModalHeader = 'Edit Group'; scope.formModalCancelShow = true; - scope.parseType = 'json'; + scope.parseType = 'yaml'; ParseTypeChange(scope); $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); @@ -288,10 +288,10 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' Rest.get() .success( function(data, status, headers, config) { if ($.isEmptyObject(data)) { - scope.variables = "\{\}"; + scope.variables = "---"; } else { - scope.variables = JSON.stringify(data, null, " "); + scope.variables = jsyaml.safeDump(data); } }) .error( function(data, status, headers, config) { @@ -301,8 +301,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' }); } else { - scope.variables = "\{\}"; + scope.variables = "---"; } + master.variables = scope.variables; }); // Retrieve detail record and prepopulate the form @@ -378,7 +379,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' }); } catch(err) { - Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err); + Alert("Error", "Error parsing group variables. Parser returned: " + err); } }; @@ -388,6 +389,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' for (var fld in master) { scope[fld] = master[fld]; } + scope.parseType = 'yaml'; } } }]) diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js index 251bd8f98f..642868c450 100644 --- a/awx/ui/static/js/helpers/Hosts.js +++ b/awx/ui/static/js/helpers/Hosts.js @@ -165,7 +165,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H scope.formModalActionLabel = 'Save'; scope.formModalHeader = 'Create Host'; scope.formModalCancelShow = true; - scope.parseType = 'json'; + scope.parseType = 'yaml'; ParseTypeChange(scope); $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); @@ -223,7 +223,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }); } catch(err) { - Alert("Error", "Error parsing host variables. Parser returned " + err); + Alert("Error", "Error parsing host variables. Parser returned: " + err); } } @@ -258,7 +258,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H scope.formModalActionLabel = 'Save'; scope.formModalHeader = 'Edit Host'; scope.formModalCancelShow = true; - scope.parseType = 'json'; + scope.parseType = 'yaml'; ParseTypeChange(scope); $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); @@ -275,10 +275,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H Rest.get() .success( function(data, status, headers, config) { if ($.isEmptyObject(data)) { - scope.variables = "\{\}"; + scope.variables = "---"; } else { - scope.variables = JSON.stringify(data, null, " "); + scope.variables = jsyaml.safeDump(data); } }) .error( function(data, status, headers, config) { @@ -288,8 +288,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }); } else { - scope.variables = "\{\}"; + scope.variables = "---"; } + master.variables = scope.variables; }); // Retrieve detail record and prepopulate the form @@ -363,7 +364,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }); } catch(err) { - Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err); + Alert("Error", "Error parsing group variables. Parser returned: " + err); } }; @@ -373,6 +374,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H for (var fld in master) { scope[fld] = master[fld]; } + scope.parseType = 'yaml'; } } }]) diff --git a/awx/ui/static/js/helpers/related-search.js b/awx/ui/static/js/helpers/related-search.js index 82ad4ab5ab..3e2744ffbf 100644 --- a/awx/ui/static/js/helpers/related-search.js +++ b/awx/ui/static/js/helpers/related-search.js @@ -60,7 +60,7 @@ angular.module('RelatedSearchHelper', ['RestServices', 'Utilities','RefreshRelat for (var related in form.related) { if ( form.related[related].iterator == iterator ) { - var f = form.related[set].fields[fld]; + var f = form.related[related].fields[fld]; } } @@ -71,15 +71,15 @@ angular.module('RelatedSearchHelper', ['RestServices', 'Utilities','RefreshRelat scope[iterator + 'HideSearchType'] = false; scope[iterator + 'InputHide'] = false; - if (f.searchType && f.searchType == 'gtzero') { + if (f.searchType !== undefined && f.searchType == 'gtzero') { scope[iterator + "InputHide"] = true; } - if (f.searchType && (f.searchType == 'boolean' + if (f.searchType !== undefined && (f.searchType == 'boolean' || f.searchType == 'select')) { scope[iterator + 'SelectShow'] = true; scope[iterator + 'SearchSelectOpts'] = f.searchOptions; } - if (f.searchType && f.searchType == 'int') { + if (f.searchType !== undefined && f.searchType == 'int') { scope[iterator + 'HideSearchType'] = true; } diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js index 75cff9306f..1f18858eac 100644 --- a/awx/ui/static/js/helpers/search.js +++ b/awx/ui/static/js/helpers/search.js @@ -37,7 +37,7 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) else { sort_order = (list.fields[fld].desc) ? '-' + fld : fld; } - if (list.fields[fld].notSearchable == undefined || list.fields[fld].notSearchable == false) { + if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) { scope[iterator + 'SearchField'] = fld; scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label; } @@ -46,9 +46,9 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) } if (!scope[iterator + 'SearchField']) { - // A field marked as key may also be notSearchable + // A field marked as key may not be 'searchable' for (fld in list.fields) { - if (list.fields[fld].notSearchable == undefined || list.fields[fld].notSearchable == false) { + if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) { scope[iterator + 'SearchField'] = fld; scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label; break; diff --git a/awx/ui/static/js/lists/JobEvents.js b/awx/ui/static/js/lists/JobEvents.js index b75d9db971..e7b53749e7 100644 --- a/awx/ui/static/js/lists/JobEvents.js +++ b/awx/ui/static/js/lists/JobEvents.js @@ -19,10 +19,10 @@ angular.module('JobEventsListDefinition', []) fields: { created: { - label: 'Creation Date', + label: 'Date', key: true, nosort: true, - notSearchable: true, + searchable: false, ngClick: "viewJobEvent(\{\{ jobevent.id \}\})", }, event_display: { @@ -30,7 +30,7 @@ angular.module('JobEventsListDefinition', []) hasChildren: true, ngClick: "viewJobEvent(\{\{ jobevent.id \}\})", nosort: true, - notSearchable: true + searchable: false }, host: { label: 'Host', @@ -47,7 +47,8 @@ angular.module('JobEventsListDefinition', []) searchField: 'failed', searchType: 'boolean', searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }], - nosort: true + nosort: true, + searchable: false, } }, diff --git a/awx/ui/static/js/lists/JobHosts.js b/awx/ui/static/js/lists/JobHosts.js index c8125e1063..4ce7ff5a01 100644 --- a/awx/ui/static/js/lists/JobHosts.js +++ b/awx/ui/static/js/lists/JobHosts.js @@ -27,11 +27,11 @@ angular.module('JobHostDefinition', []) }, ok: { label: 'Success', - notSearchable: true + searchable: false }, changed: { label: 'Changed', - notSearchable: true + searchable: false }, failures: { label: 'Failure', @@ -39,11 +39,11 @@ angular.module('JobHostDefinition', []) }, dark: { label: 'Unreachable', - notSearchable: true + searchable: false }, skipped: { label: 'Skipped', - notSearchable: true + searchable: false } }, diff --git a/awx/ui/static/js/lists/Jobs.js b/awx/ui/static/js/lists/Jobs.js index b4e46bd075..e013c08c7a 100644 --- a/awx/ui/static/js/lists/Jobs.js +++ b/awx/ui/static/js/lists/Jobs.js @@ -34,7 +34,7 @@ angular.module('JobsListDefinition', []) link: true }, created: { - label: 'Creation Date', + label: 'Date', link: true }, status: { diff --git a/awx/ui/static/js/lists/Permissions.js b/awx/ui/static/js/lists/Permissions.js index fbb0df7afa..4fd6513d83 100644 --- a/awx/ui/static/js/lists/Permissions.js +++ b/awx/ui/static/js/lists/Permissions.js @@ -24,8 +24,11 @@ angular.module('PermissionListDefinition', []) label: 'Name', ngClick: 'editPermission(\{\{ permission.id \}\})' }, - description: { - label: 'Description' + inventory: { + label: 'Inventory', + sourceModel: 'inventory', + sourceField: 'name', + ngBind: 'permission.summary_fields.inventory.name' }, project: { label: 'Project', @@ -33,12 +36,9 @@ angular.module('PermissionListDefinition', []) sourceField: 'name', ngBind: 'permission.summary_fields.project.name' }, - inventory: { - label: 'Inventory', - sourceModel: 'inventory', - sourceField: 'name', - ngBind: 'permission.summary_fields.inventory.name' - }, + permission_type: { + label: 'Permission' + } }, actions: { diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js index 317863aa6a..b588e752b9 100644 --- a/awx/ui/static/lib/ansible/form-generator.js +++ b/awx/ui/static/lib/ansible/form-generator.js @@ -233,8 +233,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies']) if (field.genMD5) { html += " \n\n"; - html += " \n\n"; + /*html += " \n\n";*/ html += "\n"; } if (field.ask) { @@ -278,10 +278,14 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies']) } html += field.label + '' + "\n"; html += "