From 5759db1229541f2755b548ff1d8baf4c103cd72c Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 4 Mar 2016 14:14:05 -0500 Subject: [PATCH 01/14] flake8 fix --- awx/main/management/commands/run_fact_cache_receiver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/management/commands/run_fact_cache_receiver.py b/awx/main/management/commands/run_fact_cache_receiver.py index 062cd39693..02a3b2e66c 100644 --- a/awx/main/management/commands/run_fact_cache_receiver.py +++ b/awx/main/management/commands/run_fact_cache_receiver.py @@ -67,7 +67,7 @@ class FactCacheReceiver(object): self.timestamp = datetime.fromtimestamp(date_key, None) # Update existing Fact entry - fact_obj = Fact.objects.filter(host__id=host_obj.id, module=module_name, timestamp=self.timestamp) + fact_obj = Fact.objects.filter(host__id=host_obj.id, module=module_name, timestamp=self.timestamp) if fact_obj: fact_obj.facts = facts fact_obj.save() From 08c33a8c6bbe6b91c6d67b6bb5d992963b62e220 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 4 Mar 2016 11:36:34 -0500 Subject: [PATCH 02/14] Refresh the job details when the job finishes running. This pulls in details not available at the beginning of a job. One plus two styling tweaks --- awx/ui/client/src/license/license.block.less | 26 +- .../client/src/license/license.partial.html | 87 +++---- .../src/shared/layouts/one-plus-two.less | 18 +- .../adhoc/standard-out-adhoc.partial.html | 2 +- .../standard-out-inventory-sync.partial.html | 2 +- .../standard-out-management-jobs.partial.html | 2 +- .../standard-out-scm-update.partial.html | 2 +- .../src/standard-out/standard-out.block.less | 48 ++-- .../standard-out/standard-out.controller.js | 222 +++++++++--------- 9 files changed, 206 insertions(+), 203 deletions(-) diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index 58ce83542a..bf8c005d08 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -33,6 +33,9 @@ .License-field{ .OnePlusTwo-left--detailsRow; } +.License-field + .License-field { + margin-top: 20px; +} .License-greenText{ color: @submit-button-bg; } @@ -40,16 +43,16 @@ color: #d9534f; } .License-fields{ - .OnePlusTwo-left--details; + .OnePlusTwo-left--details; } .License-details { - .OnePlusTwo-left--panel(600px); + .OnePlusTwo-left--panel(650px); } .License-titleText { .OnePlusTwo-panelHeader; } .License-management{ - .OnePlusTwo-right--panel(600px); + .OnePlusTwo-right--panel(650px); } .License-submit--container{ height: 33px; @@ -59,8 +62,21 @@ margin: 0 10px 0 0; } .License-file--container { - margin: 20px 0 20px 0; input[type=file] { display: none; } -} \ No newline at end of file +} +.License-upgradeText { + margin: 20px 0px; +} +.License-body { + margin-top: 25px; +} +.License-subTitleText { + text-transform: uppercase; + margin: 20px 0px 5px 0px; + color: @default-interface-txt; +} +.License-helperText { + color: @default-interface-txt; +} diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index dcbde280e7..38eeede726 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -5,95 +5,98 @@
License
-
+
Valid Invalid -
+
Version
-
+
{{license.version}}
-
License Type
+
License Type
{{license.license_info.license_type}} -
+
Subscription
-
+
{{license.license_info.subscription_name}} -
+
-
License Key
-
+
License Key
+
{{license.license_info.license_key}} -
+
Expires On
-
+
{{time.expiresOn}} -
+
Time Remaining
-
+
{{time.remaining}} Day -
+
-
Hosts Available
+
Hosts Available
{{license.license_info.available_instances}} -
+
Hosts Used
-
+
{{license.license_info.current_instances}} -
+
Hosts Remaining
-
+
{{license.license_info.free_instances}} -
+
-

If you are ready to upgrade, please contact us by clicking the button below

+
If you are ready to upgrade, please contact us by clicking the button below
License Management
-

Choose your license file, agree to the End User License Agreement, and click submit.

-
-
- Browse... - - -
-
End User License Agreement
-
- -
-
-
-
I agree to the End User License Agreement
-
- Save successful! - +
+

Choose your license file, agree to the End User License Agreement, and click submit.

+ +
License File
+
+ Browse... + + +
+
End User License Agreement
+
+ +
+
+
+
I agree to the End User License Agreement
+
+ Save successful! + +
-
- + +
-
\ No newline at end of file +
diff --git a/awx/ui/client/src/shared/layouts/one-plus-two.less b/awx/ui/client/src/shared/layouts/one-plus-two.less index fe43e40b65..87c44fe056 100644 --- a/awx/ui/client/src/shared/layouts/one-plus-two.less +++ b/awx/ui/client/src/shared/layouts/one-plus-two.less @@ -22,25 +22,24 @@ flex: 0 0; height: @height; width: 100%; + margin-right: 20px; .Panel{ height: 100%; } - @media screen and (min-width: @breakpoint){ - max-width: 400px; - } + @media screen and (max-width: @breakpoint){ + margin-right: 0px; + height: inherit; + } } .OnePlusTwo-right--panel(@height: 100%; @breakpoint: 900px) { height: @height; flex: 1 0; - margin-left: 20px; .Panel{ height: 100%; } @media screen and (max-width: @breakpoint){ flex-direction: column; - margin-left: 0px; - margin-top: 25px; } } @@ -50,6 +49,7 @@ font-weight: bold; margin-right: 10px; text-transform: uppercase; + display: flex; } .OnePlusTwo-left--details { @@ -58,9 +58,6 @@ .OnePlusTwo-left--detailsRow { display: flex; - :not(:last-child){ - margin-bottom: 20px; - } } .OnePlusTwo-left--detailsLabel { @@ -73,7 +70,6 @@ .OnePlusTwo-left--detailsContent { display: inline-block; - max-width: 220px; + width: 220px; word-wrap: break-word; } - diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html index 92e4d5e762..e2a321bfbb 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html @@ -1,6 +1,6 @@
-
+
diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html index d16efeb007..1fb14f2131 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html @@ -1,6 +1,6 @@
-
+
diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html index 3e035ddab5..27864d2ca2 100644 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html +++ b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html @@ -1,6 +1,6 @@
-
+
diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html index 74e7e63925..ed14989c9a 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html @@ -1,6 +1,6 @@
-
+
diff --git a/awx/ui/client/src/standard-out/standard-out.block.less b/awx/ui/client/src/standard-out/standard-out.block.less index 8d0e8a0219..ffdb158705 100644 --- a/awx/ui/client/src/standard-out/standard-out.block.less +++ b/awx/ui/client/src/standard-out/standard-out.block.less @@ -1,28 +1,22 @@ @import "../shared/branding/colors.default.less"; +@import "awx/ui/client/src/shared/layouts/one-plus-two.less"; /** @define StandardOut */ -.StandardOut { - height: 100%; - display: flex; - flex-direction: row; +.StandardOut-container { + .OnePlusTwo-container; } .StandardOut-leftPanel { - flex: 0 0 400px; - margin-right: 20px; + .OnePlusTwo-left--panel(590px); } .StandardOut-rightPanel { - flex: 1 0; + .OnePlusTwo-right--panel(590px); } .StandardOut-panelHeader { - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - text-transform: uppercase; - display: flex; + .OnePlusTwo-panelHeader } .StandardOut-consoleOutput { @@ -30,31 +24,28 @@ min-height: 200px; background-color: @default-secondary-bg; border-radius: 5px; - height: 300px; + height: ~"calc(100% - 74px)"; overflow: scroll; } .StandardOut-details { - margin-top: 25px; + .OnePlusTwo-left--details; } .StandardOut-detailsRow { - display: flex; + .OnePlusTwo-left--detailsRow; } -.StandardOut-detailsRow:not(:last-child) { - margin-bottom: 20px; +.StandardOut-detailsRow + .StandardOut-detailsRow { + margin-top: 20px; } .StandardOut-detailsLabel { - width: 130px; - flex: 0 0 130px; - color: @default-interface-txt; - text-transform: uppercase; + .OnePlusTwo-left--detailsLabel; } .StandardOut-detailsContent { - flex: 1 0; + .OnePlusTwo-left--detailsContent; } .StandardOut-statusText { @@ -66,7 +57,7 @@ } .StandardOut-preContainer { - height: 300px; + height: 100%; } .StandardOut-panelHeaderText { @@ -105,14 +96,3 @@ .StandardOut-actionButton + a { margin-left: 15px; } - -@standardout-breakpoint: 900px; - -@media screen and (max-width: @standardout-breakpoint) { - .StandardOut { - flex-direction: column; - } - .StandardOut-leftPanel { - margin-right: 0px; - } -} diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 152d2a8a5d..b3b363e064 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -32,118 +32,124 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, C $scope.job.status = data.status; } - // TODO: when the job completes we should refresh the job data so that we pull in the finish - // timestamp as well as the run time. + if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { + // Go out and refresh the job details + getJobDetails(); + } }); - // Go out and get the job details based on the job type. jobType gets defined - // in the data block of the route declaration for each of the different types - // of stdout jobs. - Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/'); - Rest.get() - .success(function(data) { - $scope.job = data; - $scope.job_template_name = data.name; - $scope.created_by = data.summary_fields.created_by; - $scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; - $scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; - $scope.job_template_url = '/#/job_templates/' + data.unified_job_template; - $scope.inventory_url = ($scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : ''; - $scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : ''; - $scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : ''; - $scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : ''; - $scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : ''; - $scope.playbook = data.playbook; - $scope.credential = data.credential; - $scope.cloud_credential = data.cloud_credential; - $scope.forks = data.forks; - $scope.limit = data.limit; - $scope.verbosity = data.verbosity; - $scope.job_tags = data.job_tags; + function getJobDetails() { - // If we have a source then we have to go get the source choices from the server - if (!Empty(data.source)) { - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('ChoicesReady', function() { - $scope.source_choices.every(function(e) { - if (e.value === data.source) { - $scope.source = e.label; - return false; - } - return true; + // Go out and get the job details based on the job type. jobType gets defined + // in the data block of the route declaration for each of the different types + // of stdout jobs. + Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/'); + Rest.get() + .success(function(data) { + $scope.job = data; + $scope.job_template_name = data.name; + $scope.created_by = data.summary_fields.created_by; + $scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; + $scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; + $scope.job_template_url = '/#/job_templates/' + data.unified_job_template; + $scope.inventory_url = ($scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : ''; + $scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : ''; + $scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : ''; + $scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : ''; + $scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : ''; + $scope.playbook = data.playbook; + $scope.credential = data.credential; + $scope.cloud_credential = data.cloud_credential; + $scope.forks = data.forks; + $scope.limit = data.limit; + $scope.verbosity = data.verbosity; + $scope.job_tags = data.job_tags; + + // If we have a source then we have to go get the source choices from the server + if (!Empty(data.source)) { + if ($scope.removeChoicesReady) { + $scope.removeChoicesReady(); + } + $scope.removeChoicesReady = $scope.$on('ChoicesReady', function() { + $scope.source_choices.every(function(e) { + if (e.value === data.source) { + $scope.source = e.label; + return false; + } + return true; + }); + }); + // GetChoices can be found in the helper: StandardOut.js + // It attaches the source choices to $scope.source_choices. + // Then, when the callback is fired, $scope.source is bound + // to the corresponding label. + GetChoices({ + scope: $scope, + url: GetBasePath('inventory_sources'), + field: 'source', + variable: 'source_choices', + choice_name: 'choices', + callback: 'ChoicesReady' }); - }); - // GetChoices can be found in the helper: StandardOut.js - // It attaches the source choices to $scope.source_choices. - // Then, when the callback is fired, $scope.source is bound - // to the corresponding label. - GetChoices({ - scope: $scope, - url: GetBasePath('inventory_sources'), - field: 'source', - variable: 'source_choices', - choice_name: 'choices', - callback: 'ChoicesReady' - }); - } - - // LookUpName can be found in the helper: StandardOut.js - // It attaches the name that it gets (based on the url) - // to the $scope variable defined by the attribute scope_var. - if (!Empty(data.credential)) { - LookUpName({ - scope: $scope, - scope_var: 'credential', - url: GetBasePath('credentials') + data.credential + '/' - }); - } - - if (!Empty(data.inventory)) { - LookUpName({ - scope: $scope, - scope_var: 'inventory', - url: GetBasePath('inventory') + data.inventory + '/' - }); - } - - if (!Empty(data.project)) { - LookUpName({ - scope: $scope, - scope_var: 'project', - url: GetBasePath('projects') + data.project + '/' - }); - } - - if (!Empty(data.cloud_credential)) { - LookUpName({ - scope: $scope, - scope_var: 'cloud_credential', - url: GetBasePath('credentials') + data.cloud_credential + '/' - }); - } - - if (!Empty(data.inventory_source)) { - LookUpName({ - scope: $scope, - scope_var: 'inventory_source', - url: GetBasePath('inventory_sources') + data.inventory_source + '/' - }); - } - - // If the job isn't running we want to clear out the interval that goes out and checks for stdout updates. - // This interval is defined in the standard out log directive controller. - if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') { - if ($rootScope.jobStdOutInterval) { - window.clearInterval($rootScope.jobStdOutInterval); } - } - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve job: ' + job_id + '. GET returned: ' + status }); - }); + + // LookUpName can be found in the helper: StandardOut.js + // It attaches the name that it gets (based on the url) + // to the $scope variable defined by the attribute scope_var. + if (!Empty(data.credential)) { + LookUpName({ + scope: $scope, + scope_var: 'credential', + url: GetBasePath('credentials') + data.credential + '/' + }); + } + + if (!Empty(data.inventory)) { + LookUpName({ + scope: $scope, + scope_var: 'inventory', + url: GetBasePath('inventory') + data.inventory + '/' + }); + } + + if (!Empty(data.project)) { + LookUpName({ + scope: $scope, + scope_var: 'project', + url: GetBasePath('projects') + data.project + '/' + }); + } + + if (!Empty(data.cloud_credential)) { + LookUpName({ + scope: $scope, + scope_var: 'cloud_credential', + url: GetBasePath('credentials') + data.cloud_credential + '/' + }); + } + + if (!Empty(data.inventory_source)) { + LookUpName({ + scope: $scope, + scope_var: 'inventory_source', + url: GetBasePath('inventory_sources') + data.inventory_source + '/' + }); + } + + // If the job isn't running we want to clear out the interval that goes out and checks for stdout updates. + // This interval is defined in the standard out log directive controller. + if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') { + if ($rootScope.jobStdOutInterval) { + window.clearInterval($rootScope.jobStdOutInterval); + } + } + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve job: ' + job_id + '. GET returned: ' + status }); + }); + + } // TODO: this is currently not used but is necessary for cases where sockets // are not available and a manual refresh trigger is needed. @@ -156,6 +162,8 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, C $scope.stdoutFullScreen = !$scope.stdoutFullScreen; } + getJobDetails(); + } JobStdoutController.$inject = [ '$rootScope', '$scope', '$state', '$stateParams', 'ClearScope', 'GetBasePath', 'Rest', 'ProcessErrors', 'Empty', 'GetChoices', 'LookUpName']; From f1c13e0837bcfe04b0725dacd7c30203317ad930 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Fri, 4 Mar 2016 15:41:40 -0500 Subject: [PATCH 03/14] Add migration to create system job templates for a new database. --- awx/main/migrations/0005_v300_changes.py | 115 +++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 awx/main/migrations/0005_v300_changes.py diff --git a/awx/main/migrations/0005_v300_changes.py b/awx/main/migrations/0005_v300_changes.py new file mode 100644 index 0000000000..e350c881f2 --- /dev/null +++ b/awx/main/migrations/0005_v300_changes.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.timezone import now + +from awx.api.license import feature_enabled + + +def create_system_job_templates(apps, schema_editor): + ''' + Create default system job templates if not present. Create default schedules + only if new system job templates were created (i.e. new database). + ''' + + SystemJobTemplate = apps.get_model('main', 'SystemJobTemplate') + ContentType = apps.get_model('contenttypes', 'ContentType') + sjt_ct = ContentType.objects.get_for_model(SystemJobTemplate) + now_dt = now() + now_str = now_dt.strftime('%Y%m%dT%H%M%SZ') + + sjt, created = SystemJobTemplate.objects.get_or_create( + job_type='cleanup_jobs', + defaults=dict( + name='Cleanup Job Details', + description='Remove job history older than X days', + created=now_dt, + modified=now_dt, + polymorphic_ctype=sjt_ct, + ), + ) + if created: + sjt.schedules.create( + name='Cleanup Job Schedule', + rrule='DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU' % now_str, + description='Automatically Generated Schedule', + enabled=True, + extra_data={'days': '120'}, + created=now_dt, + modified=now_dt, + ) + + sjt, created = SystemJobTemplate.objects.get_or_create( + job_type='cleanup_deleted', + defaults=dict( + name='Cleanup Deleted Data', + description='Remove deleted object history older than X days', + created=now_dt, + modified=now_dt, + polymorphic_ctype=sjt_ct, + ), + ) + if created: + sjt.schedules.create( + name='Cleanup Deleted Data Schedule', + rrule='DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO' % now_str, + description='Automatically Generated Schedule', + enabled=True, + extra_data={'days': '30'}, + created=now_dt, + modified=now_dt, + ) + + sjt, created = SystemJobTemplate.objects.get_or_create( + job_type='cleanup_activitystream', + defaults=dict( + name='Cleanup Activity Stream', + description='Remove activity stream history older than X days', + created=now_dt, + modified=now_dt, + polymorphic_ctype=sjt_ct, + ), + ) + if created: + sjt.schedules.create( + name='Cleanup Activity Schedule', + rrule='DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU' % now_str, + description='Automatically Generated Schedule', + enabled=True, + extra_data={'days': '355'}, + created=now_dt, + modified=now_dt, + ) + + sjt, created = SystemJobTemplate.objects.get_or_create( + job_type='cleanup_facts', + defaults=dict( + name='Cleanup Fact Details', + description='Remove system tracking history', + created=now_dt, + modified=now_dt, + polymorphic_ctype=sjt_ct, + ), + ) + if created and feature_enabled('system_tracking', bypass_database=True): + sjt.schedules.create( + name='Cleanup Fact Schedule', + rrule='DTSTART:%s RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=1' % now_str, + description='Automatically Generated Schedule', + enabled=True, + extra_data={'older_than': '120d', 'granularity': '1w'}, + created=now_dt, + modified=now_dt, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0004_v300_changes'), + ] + + operations = [ + migrations.RunPython(create_system_job_templates, migrations.RunPython.noop), + ] From ee5694b175e2e9a6aea776404269ed39c2ee3ea3 Mon Sep 17 00:00:00 2001 From: James Laska Date: Fri, 4 Mar 2016 16:19:00 -0500 Subject: [PATCH 04/14] Add ISSUE_TEMPLATE.md fixes #1106 --- ISSUE_TEMPLATE.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..c12528c389 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,31 @@ +### Summary + + + +### Environment + + + +### Steps To Reproduce: + + + +### Expected Results: + + + +### Actual Results: + + + +### Additional Information: + + From 60b6b6bfeab4a065d30babc8034aa8f9a0d31907 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Fri, 4 Mar 2016 16:36:02 -0500 Subject: [PATCH 05/14] Fix job template callback view to accept URL-encoded form data. --- awx/api/views.py | 2 ++ awx/main/tests/base.py | 3 +++ awx/main/tests/old/jobs/jobs_monolithic.py | 31 ++++++++++++++++------ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 333f2427ae..02a10451d8 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -32,6 +32,7 @@ from django.http import HttpResponse # Django REST Framework from rest_framework.exceptions import PermissionDenied, ParseError +from rest_framework.parsers import FormParser from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.settings import api_settings @@ -2005,6 +2006,7 @@ class JobTemplateCallback(GenericAPIView): model = JobTemplate permission_classes = (JobTemplateCallbackPermission,) serializer_class = EmptySerializer + parser_classes = api_settings.DEFAULT_PARSER_CLASSES + [FormParser] @csrf_exempt @transaction.non_atomic_requests diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index 93bac00948..a0387079b6 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -11,6 +11,7 @@ import shutil import sys import tempfile import time +import urllib from multiprocessing import Process from subprocess import Popen import re @@ -463,6 +464,8 @@ class BaseTestMixin(QueueTestMixin, MockCommonlySlowTestMixin): response = method(url, json.dumps(data), 'application/json') elif data_type == 'yaml': response = method(url, yaml.safe_dump(data), 'application/yaml') + elif data_type == 'form': + response = method(url, urllib.urlencode(data), 'application/x-www-form-urlencoded') else: self.fail('Unsupported data_type %s' % data_type) else: diff --git a/awx/main/tests/old/jobs/jobs_monolithic.py b/awx/main/tests/old/jobs/jobs_monolithic.py index 1d36972245..9234f57a2b 100644 --- a/awx/main/tests/old/jobs/jobs_monolithic.py +++ b/awx/main/tests/old/jobs/jobs_monolithic.py @@ -803,6 +803,21 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase): self.assertEqual(job.hosts.count(), 1) self.assertEqual(job.hosts.all()[0], host) + # Create the job itself using URL-encoded form data instead of JSON. + result = self.post(url, data, expect=202, remote_addr=host_ip, data_type='form') + + # Establish that we got back what we expect, and made the changes + # that we expect. + self.assertTrue('Location' in result.response, result.response) + self.assertEqual(jobs_qs.count(), 2) + job = jobs_qs[0] + self.assertEqual(urlparse.urlsplit(result.response['Location']).path, + job.get_absolute_url()) + self.assertEqual(job.launch_type, 'callback') + self.assertEqual(job.limit, host.name) + self.assertEqual(job.hosts.count(), 1) + self.assertEqual(job.hosts.all()[0], host) + # Run the callback job again with extra vars and verify their presence data.update(dict(extra_vars=dict(key="value"))) result = self.post(url, data, expect=202, remote_addr=host_ip) @@ -853,9 +868,9 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase): if host_ip: break self.assertTrue(host) - self.assertEqual(jobs_qs.count(), 2) - self.post(url, data, expect=202, remote_addr=host_ip) self.assertEqual(jobs_qs.count(), 3) + self.post(url, data, expect=202, remote_addr=host_ip) + self.assertEqual(jobs_qs.count(), 4) job = jobs_qs[0] self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.limit, host.name) @@ -878,9 +893,9 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase): if host_ip: break self.assertTrue(host) - self.assertEqual(jobs_qs.count(), 3) - self.post(url, data, expect=202, remote_addr=host_ip) self.assertEqual(jobs_qs.count(), 4) + self.post(url, data, expect=202, remote_addr=host_ip) + self.assertEqual(jobs_qs.count(), 5) job = jobs_qs[0] self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.limit, host.name) @@ -892,9 +907,9 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase): host_qs = host_qs.filter(variables__icontains='ansible_ssh_host') host = host_qs[0] host_ip = host.variables_dict['ansible_ssh_host'] - self.assertEqual(jobs_qs.count(), 4) - self.post(url, data, expect=202, remote_addr=host_ip) self.assertEqual(jobs_qs.count(), 5) + self.post(url, data, expect=202, remote_addr=host_ip) + self.assertEqual(jobs_qs.count(), 6) job = jobs_qs[0] self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.limit, host.name) @@ -926,9 +941,9 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase): host_ip = list(ips)[0] break self.assertTrue(host) - self.assertEqual(jobs_qs.count(), 5) - self.post(url, data, expect=202, remote_addr=host_ip) self.assertEqual(jobs_qs.count(), 6) + self.post(url, data, expect=202, remote_addr=host_ip) + self.assertEqual(jobs_qs.count(), 7) job = jobs_qs[0] self.assertEqual(job.launch_type, 'callback') self.assertEqual(job.limit, ':&'.join([job_template.limit, host.name])) From a35368f55884bfe12da98496107b949ad7d49944 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Fri, 4 Mar 2016 15:39:23 -0800 Subject: [PATCH 06/14] Adding initial notifications files --- awx/ui/client/src/app.js | 8 +- .../src/notifications/add/add.controller.js | 67 +++++++++++++ .../src/notifications/add/add.partial.html | 3 + .../client/src/notifications/add/add.route.js | 23 +++++ awx/ui/client/src/notifications/add/main.js | 15 +++ .../src/notifications/edit/edit.controller.js | 97 +++++++++++++++++++ .../src/notifications/edit/edit.partial.html | 3 + .../src/notifications/edit/edit.route.js | 23 +++++ awx/ui/client/src/notifications/edit/main.js | 15 +++ .../src/notifications/list/list.controller.js | 83 ++++++++++++++++ .../src/notifications/list/list.partial.html | 4 + .../src/notifications/list/list.route.js | 19 ++++ awx/ui/client/src/notifications/list/main.js | 15 +++ awx/ui/client/src/notifications/main.js | 22 +++++ .../src/notifications/notifications.form.js | 47 +++++++++ .../src/notifications/notifications.list.js | 63 ++++++++++++ .../src/setup-menu/setup-menu.partial.html | 6 ++ 17 files changed, 510 insertions(+), 3 deletions(-) create mode 100644 awx/ui/client/src/notifications/add/add.controller.js create mode 100644 awx/ui/client/src/notifications/add/add.partial.html create mode 100644 awx/ui/client/src/notifications/add/add.route.js create mode 100644 awx/ui/client/src/notifications/add/main.js create mode 100644 awx/ui/client/src/notifications/edit/edit.controller.js create mode 100644 awx/ui/client/src/notifications/edit/edit.partial.html create mode 100644 awx/ui/client/src/notifications/edit/edit.route.js create mode 100644 awx/ui/client/src/notifications/edit/main.js create mode 100644 awx/ui/client/src/notifications/list/list.controller.js create mode 100644 awx/ui/client/src/notifications/list/list.partial.html create mode 100644 awx/ui/client/src/notifications/list/list.route.js create mode 100644 awx/ui/client/src/notifications/list/main.js create mode 100644 awx/ui/client/src/notifications/main.js create mode 100644 awx/ui/client/src/notifications/notifications.form.js create mode 100644 awx/ui/client/src/notifications/notifications.list.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 3c12eadfb1..a37db8cbc4 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -30,6 +30,7 @@ import inventoryScripts from './inventory-scripts/main'; import permissions from './permissions/main'; import managementJobs from './management-jobs/main'; import jobDetail from './job-detail/main'; +import notifications from './notifications/main'; // modules import about from './about/main'; @@ -98,6 +99,7 @@ var tower = angular.module('Tower', [ activityStream.name, footer.name, jobDetail.name, + notifications.name, standardOut.name, 'templates', 'Utilities', @@ -882,13 +884,13 @@ var tower = angular.module('Tower', [ }]); }]) - .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', '$state', 'CheckLicense', + .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', '$state', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'Socket', 'LoadConfig', 'Store', 'ShowSocketHelp', 'pendoService', function ( - $q, $compile, $cookieStore, $rootScope, $log, $state, CheckLicense, + $q, $compile, $cookieStore, $rootScope, $log, $state, CheckLicense, $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket, - LoadConfig, Store, ShowSocketHelp, pendoService) + LoadConfig, Store, ShowSocketHelp, pendoService) { var sock; diff --git a/awx/ui/client/src/notifications/add/add.controller.js b/awx/ui/client/src/notifications/add/add.controller.js new file mode 100644 index 0000000000..88048941ff --- /dev/null +++ b/awx/ui/client/src/notifications/add/add.controller.js @@ -0,0 +1,67 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default + [ '$rootScope', 'pagination', '$compile','SchedulerInit', 'Rest', 'Wait', + 'notificationsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty', + 'GenerateForm', 'SearchInit' , 'PaginateInit', + 'LookUpInit', 'OrganizationList', '$scope', '$state', + function( + $rootScope, pagination, $compile, SchedulerInit, Rest, Wait, + notificationsFormObject, ProcessErrors, GetBasePath, Empty, + GenerateForm, SearchInit, PaginateInit, + LookUpInit, OrganizationList, $scope, $state + ) { + var scope = $scope, + generator = GenerateForm, + form = notificationsFormObject, + url = GetBasePath('notifications'); + + generator.inject(form, { + mode: 'add' , + scope:scope, + related: false + }); + generator.reset(); + + LookUpInit({ + url: GetBasePath('organization'), + scope: scope, + form: form, + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + + // Save + scope.formSave = function () { + + generator.clearApiErrors(); + Wait('start'); + Rest.setUrl(url); + Rest.post({ + name: scope.name, + description: scope.description, + organization: scope.organization, + script: scope.script + }) + .success(function (data) { + $rootScope.addedItem = data.id; + $state.go('inventoryScripts', {}, {reload: true}); + Wait('stop'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new inventory script. POST returned status: ' + status }); + }); + }; + + scope.formCancel = function () { + $state.transitionTo('inventoryScripts'); + }; + + } + ]; diff --git a/awx/ui/client/src/notifications/add/add.partial.html b/awx/ui/client/src/notifications/add/add.partial.html new file mode 100644 index 0000000000..65bfebbbe6 --- /dev/null +++ b/awx/ui/client/src/notifications/add/add.partial.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/awx/ui/client/src/notifications/add/add.route.js b/awx/ui/client/src/notifications/add/add.route.js new file mode 100644 index 0000000000..6bad062844 --- /dev/null +++ b/awx/ui/client/src/notifications/add/add.route.js @@ -0,0 +1,23 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; + +export default { + name: 'notifications.add', + route: '/add', + templateUrl: templateUrl('notifications/add/add'), + controller: 'notificationsAddController', + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + }, + ncyBreadcrumb: { + parent: 'notifications', + label: 'Create Notification' + } +}; diff --git a/awx/ui/client/src/notifications/add/main.js b/awx/ui/client/src/notifications/add/main.js new file mode 100644 index 0000000000..f3101e402f --- /dev/null +++ b/awx/ui/client/src/notifications/add/main.js @@ -0,0 +1,15 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './add.route'; +import controller from './add.controller'; + +export default + angular.module('notificationsAdd', []) + .controller('notificationsAddController', controller) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/notifications/edit/edit.controller.js b/awx/ui/client/src/notifications/edit/edit.controller.js new file mode 100644 index 0000000000..a14b3334af --- /dev/null +++ b/awx/ui/client/src/notifications/edit/edit.controller.js @@ -0,0 +1,97 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default + [ 'Rest', 'Wait', + 'notificationsFormObject', 'ProcessErrors', 'GetBasePath', + 'GenerateForm', 'SearchInit' , 'PaginateInit', + 'LookUpInit', 'OrganizationList', 'inventory_script', + '$scope', '$state', + function( + Rest, Wait, + notificationsFormObject, ProcessErrors, GetBasePath, + GenerateForm, SearchInit, PaginateInit, + LookUpInit, OrganizationList, inventory_script, + $scope, $state + ) { + var generator = GenerateForm, + id = inventory_script.id, + form = notificationsFormObject, + master = {}, + url = GetBasePath('notifications'); + + $scope.inventory_script = inventory_script; + generator.inject(form, { + mode: 'edit' , + scope:$scope, + related: false, + activityStream: false + }); + generator.reset(); + LookUpInit({ + url: GetBasePath('organization'), + scope: $scope, + form: form, + // hdr: "Select Custom Inventory", + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + + // Retrieve detail record and prepopulate the form + Wait('start'); + Rest.setUrl(url + id+'/'); + Rest.get() + .success(function (data) { + var fld; + for (fld in form.fields) { + if (data[fld]) { + $scope[fld] = data[fld]; + master[fld] = data[fld]; + } + + if (form.fields[fld].sourceModel && data.summary_fields && + 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]; + master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; + } + } + Wait('stop'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to retrieve inventory script: ' + id + '. GET status: ' + status }); + }); + + $scope.formSave = function () { + generator.clearApiErrors(); + Wait('start'); + Rest.setUrl(url+ id+'/'); + Rest.put({ + name: $scope.name, + description: $scope.description, + organization: $scope.organization, + script: $scope.script + }) + .success(function () { + $state.transitionTo('inventoryScriptsList'); + Wait('stop'); + + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new inventory script. PUT returned status: ' + status }); + }); + }; + + $scope.formCancel = function () { + $state.transitionTo('inventoryScripts'); + }; + + } + ]; diff --git a/awx/ui/client/src/notifications/edit/edit.partial.html b/awx/ui/client/src/notifications/edit/edit.partial.html new file mode 100644 index 0000000000..ebd7e80e80 --- /dev/null +++ b/awx/ui/client/src/notifications/edit/edit.partial.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/awx/ui/client/src/notifications/edit/edit.route.js b/awx/ui/client/src/notifications/edit/edit.route.js new file mode 100644 index 0000000000..1987b32cad --- /dev/null +++ b/awx/ui/client/src/notifications/edit/edit.route.js @@ -0,0 +1,23 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; + +export default { + name: 'notifications.edit', + route: '/edit', + templateUrl: templateUrl('notifications/edit/edit'), + controller: 'notificationsEditController', + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + }, + ncyBreadcrumb: { + parent: 'notifications', + label: 'Edit Notification' + } +}; diff --git a/awx/ui/client/src/notifications/edit/main.js b/awx/ui/client/src/notifications/edit/main.js new file mode 100644 index 0000000000..8f0c62d7a1 --- /dev/null +++ b/awx/ui/client/src/notifications/edit/main.js @@ -0,0 +1,15 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './edit.route'; +import controller from './edit.controller'; + +export default + angular.module('notificationsEdit', []) + .controller('notificationsEditController', controller) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/notifications/list/list.controller.js b/awx/ui/client/src/notifications/list/list.controller.js new file mode 100644 index 0000000000..df0ef9f7d2 --- /dev/null +++ b/awx/ui/client/src/notifications/list/list.controller.js @@ -0,0 +1,83 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default + [ '$rootScope','Wait', 'generateList', 'notificationsListObject', + 'GetBasePath' , 'SearchInit' , 'PaginateInit', + 'Rest' , 'ProcessErrors', 'Prompt', '$state', + function( + $rootScope,Wait, GenerateList, notificationsListObject, + GetBasePath, SearchInit, PaginateInit, + Rest, ProcessErrors, Prompt, $state + ) { + var scope = $rootScope.$new(), + defaultUrl = GetBasePath('notifications'), + list = notificationsListObject, + view = GenerateList; + + view.inject( list, { + mode: 'edit', + scope: scope + }); + + // SearchInit({ + // scope: scope, + // set: 'notifications', + // list: list, + // url: defaultUrl + // }); + // + // if ($rootScope.addedItem) { + // scope.addedItem = $rootScope.addedItem; + // delete $rootScope.addedItem; + // } + // PaginateInit({ + // scope: scope, + // list: list, + // url: defaultUrl + // }); + // + // scope.search(list.iterator); + + scope.editNotification = function(){ + $state.transitionTo('notifications.edit',{ + inventory_script_id: this.inventory_script.id, + inventory_script: this.inventory_script + }); + }; + + scope.deleteNotification = function(id, name){ + + var action = function () { + $('#prompt-modal').modal('hide'); + Wait('start'); + var url = defaultUrl + id + '/'; + Rest.setUrl(url); + Rest.destroy() + .success(function () { + scope.search(list.iterator); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }; + + var bodyHtml = '
Are you sure you want to delete the inventory script below?
' + name + '
'; + Prompt({ + hdr: 'Delete', + body: bodyHtml, + action: action, + actionText: 'DELETE' + }); + }; + + scope.addNotification = function(){ + $state.transitionTo('notifications.add'); + }; + + } + ]; diff --git a/awx/ui/client/src/notifications/list/list.partial.html b/awx/ui/client/src/notifications/list/list.partial.html new file mode 100644 index 0000000000..bad2fa18bb --- /dev/null +++ b/awx/ui/client/src/notifications/list/list.partial.html @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/awx/ui/client/src/notifications/list/list.route.js b/awx/ui/client/src/notifications/list/list.route.js new file mode 100644 index 0000000000..da76880791 --- /dev/null +++ b/awx/ui/client/src/notifications/list/list.route.js @@ -0,0 +1,19 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; + +export default { + name: 'notifications', + route: '/notifications', + templateUrl: templateUrl('notifications/list/list'), + controller: 'notificationsListController', + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } +}; diff --git a/awx/ui/client/src/notifications/list/main.js b/awx/ui/client/src/notifications/list/main.js new file mode 100644 index 0000000000..35cab03cef --- /dev/null +++ b/awx/ui/client/src/notifications/list/main.js @@ -0,0 +1,15 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './list.route'; +import controller from './list.controller'; + +export default + angular.module('notificationsList', []) + .controller('notificationsListController', controller) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/notifications/main.js b/awx/ui/client/src/notifications/main.js new file mode 100644 index 0000000000..147b3b8479 --- /dev/null +++ b/awx/ui/client/src/notifications/main.js @@ -0,0 +1,22 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +import notificationsList from './list/main'; +import notificationsAdd from './add/main'; +import notificationsEdit from './edit/main'; + +import list from './notifications.list'; +import form from './notifications.form'; + +export default + angular.module('notifications', [ + notificationsList.name, + notificationsAdd.name, + notificationsEdit.name + ]) + .factory('notificationsListObject', list) + .factory('notificationsFormObject', form); diff --git a/awx/ui/client/src/notifications/notifications.form.js b/awx/ui/client/src/notifications/notifications.form.js new file mode 100644 index 0000000000..d8c49d00ba --- /dev/null +++ b/awx/ui/client/src/notifications/notifications.form.js @@ -0,0 +1,47 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + /** + * @ngdoc function + * @name forms.function:CustomInventory + * @description This form is for adding/editing an organization +*/ + +export default function() { + return { + + addTitle: 'New Notification', + editTitle: '{{ name }}', + name: 'notification', + showActions: true, + + fields: { + name: { + label: 'Name', + type: 'text', + addRequired: true, + editRequired: true, + capitalize: false + }, + description: { + label: 'Description', + type: 'text', + addRequired: false, + editRequired: false + } + }, + + buttons: { //for now always generates
- -
EXTRA VARS
-
- {{ job.extra_vars }} -
+
+ +
+
diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 152d2a8a5d..83345d7132 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -11,7 +11,7 @@ */ -export function JobStdoutController ($rootScope, $scope, $state, $stateParams, ClearScope, GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName) { +export function JobStdoutController ($rootScope, $scope, $state, $stateParams, ClearScope, GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName, ParseTypeChange, ParseVariableString) { ClearScope(); @@ -36,6 +36,9 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, C // timestamp as well as the run time. }); + // Set the parse type so that CodeMirror knows how to display extra params YAML/JSON + $scope.parseType = 'yaml'; + // Go out and get the job details based on the job type. jobType gets defined // in the data block of the route declaration for each of the different types // of stdout jobs. @@ -132,6 +135,11 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, C }); } + if (!Empty(data.extra_vars)) { + $scope.variables = ParseVariableString(data.extra_vars); + ParseTypeChange({ scope: $scope, field_id: 'pre-formatted-variables' }); + } + // If the job isn't running we want to clear out the interval that goes out and checks for stdout updates. // This interval is defined in the standard out log directive controller. if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') { @@ -158,4 +166,4 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, C } -JobStdoutController.$inject = [ '$rootScope', '$scope', '$state', '$stateParams', 'ClearScope', 'GetBasePath', 'Rest', 'ProcessErrors', 'Empty', 'GetChoices', 'LookUpName']; +JobStdoutController.$inject = [ '$rootScope', '$scope', '$state', '$stateParams', 'ClearScope', 'GetBasePath', 'Rest', 'ProcessErrors', 'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange', 'ParseVariableString']; From 1bc3657fe7cce16e00f7529ba756dceef25b6576 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Mon, 7 Mar 2016 10:46:11 -0500 Subject: [PATCH 08/14] fixes demo feedback bugs in #1112 --- awx/ui/client/legacy-styles/ansible-ui.less | 3 +- awx/ui/client/src/about/about.controller.js | 34 +++++++++---------- awx/ui/client/src/about/about.partial.html | 2 +- awx/ui/client/src/app.js | 2 +- awx/ui/client/src/footer/footer.block.less | 3 +- awx/ui/client/src/license/license.block.less | 13 +++++++ .../client/src/license/license.controller.js | 24 ++++++++++--- .../client/src/license/license.partial.html | 22 ++++++------ .../client/src/shared/bootstrap-settings.less | 22 ++++++++++++ .../src/shared/layouts/one-plus-two.less | 1 + awx/ui/templates/ui/index.html | 2 -- 11 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 awx/ui/client/src/shared/bootstrap-settings.less diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 4adaeb09eb..717ab7bd30 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -44,7 +44,8 @@ @import "text-label.less"; @import "./bootstrap-datepicker.less"; @import "awx/ui/client/src/shared/branding/colors.default.less"; - +// Bootstrap default overrides +@import "awx/ui/client/src/shared/bootstrap-settings.less"; /* Bootstrap fix that's causing a right margin to appear whenver a modal is opened */ body.modal-open { diff --git a/awx/ui/client/src/about/about.controller.js b/awx/ui/client/src/about/about.controller.js index c35388e8ae..07bad2dfd6 100644 --- a/awx/ui/client/src/about/about.controller.js +++ b/awx/ui/client/src/about/about.controller.js @@ -1,20 +1,20 @@ export default ['$scope', '$state', 'CheckLicense', function($scope, $state, CheckLicense){ var processVersion = function(version){ - // prettify version & calculate padding - // e,g 3.0.0-0.git201602191743/ -> 3.0.0 - var split = version.split('-')[0] - var spaces = Math.floor((16-split.length)/2), - paddedStr = ""; - for(var i=0; i<=spaces; i++){ - paddedStr = paddedStr +" "; - } - paddedStr = paddedStr + split; - for(var j = paddedStr.length; j<16; j++){ - paddedStr = paddedStr + " "; - } - return paddedStr - } + // prettify version & calculate padding + // e,g 3.0.0-0.git201602191743/ -> 3.0.0 + var split = version.split('-')[0] + var spaces = Math.floor((16-split.length)/2), + paddedStr = ""; + for(var i=0; i<=spaces; i++){ + paddedStr = paddedStr +" "; + } + paddedStr = paddedStr + split; + for(var j = paddedStr.length; j<16; j++){ + paddedStr = paddedStr + " "; + } + return paddedStr + }; var init = function(){ CheckLicense.get() .then(function(res){ @@ -23,9 +23,9 @@ export default $('#about-modal').modal('show'); }); }; - var back = function(){ - $state.go('setup'); - } + $('#about-modal').on('hidden.bs.modal', function () { + $state.go('setup'); + }); init(); } ]; \ No newline at end of file diff --git a/awx/ui/client/src/about/about.partial.html b/awx/ui/client/src/about/about.partial.html index afc66724f4..44d4914402 100644 --- a/awx/ui/client/src/about/about.partial.html +++ b/awx/ui/client/src/about/about.partial.html @@ -3,7 +3,7 @@
Version
- {{license.version}} + {{license.version || "No result found"}}
License Type
- {{license.license_info.license_type}} + {{license.license_info.license_type || "No result found"}}
Subscription
- {{license.license_info.subscription_name}} + {{license.license_info.subscription_name || "No result found"}}
License Key
- {{license.license_info.license_key}} + {{license.license_info.license_key || "No result found"}}
@@ -43,25 +43,25 @@
Time Remaining
- {{time.remaining}} Day + {{time.remaining}} Days
Hosts Available
- {{license.license_info.available_instances}} + {{license.license_info.available_instances || "No result found"}}
Hosts Used
- {{license.license_info.current_instances}} + {{license.license_info.current_instances || "No result found"}}
Hosts Remaining
- {{license.license_info.free_instances}} + {{license.license_info.free_instances || "No result found"}}
@@ -76,7 +76,7 @@
Browse... - +
End User License Agreement
@@ -88,7 +88,7 @@
I agree to the End User License Agreement
- Save successful! + Save successful!
diff --git a/awx/ui/client/src/shared/bootstrap-settings.less b/awx/ui/client/src/shared/bootstrap-settings.less new file mode 100644 index 0000000000..c0c300c74a --- /dev/null +++ b/awx/ui/client/src/shared/bootstrap-settings.less @@ -0,0 +1,22 @@ +@import "awx/ui/client/src/shared/branding/colors.default.less"; +.btn-success{ + background: @default-succ; + border-color: transparent; + :hover{ + background: @default-succ-hov; + } + :disabled{ + background: @default-succ-disabled; + } +} +.btn-default{ + background: @btn-bg; + border-color: @btn-bord; + color: @btn-txt; + :hover{ + background: @btn-bg-hov; + } + :focus{ + background: @btn-bg-sel; + } +} \ No newline at end of file diff --git a/awx/ui/client/src/shared/layouts/one-plus-two.less b/awx/ui/client/src/shared/layouts/one-plus-two.less index fe43e40b65..e1e1653e8b 100644 --- a/awx/ui/client/src/shared/layouts/one-plus-two.less +++ b/awx/ui/client/src/shared/layouts/one-plus-two.less @@ -74,6 +74,7 @@ .OnePlusTwo-left--detailsContent { display: inline-block; max-width: 220px; + width: 220px; word-wrap: break-word; } diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 79111c287d..6b5466b98d 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -154,8 +154,6 @@ - -
-
+
EXTRA VARS
diff --git a/awx/ui/client/src/standard-out/standard-out.block.less b/awx/ui/client/src/standard-out/standard-out.block.less index 8d0e8a0219..86621c3536 100644 --- a/awx/ui/client/src/standard-out/standard-out.block.less +++ b/awx/ui/client/src/standard-out/standard-out.block.less @@ -46,6 +46,10 @@ margin-bottom: 20px; } +.StandardOut-detailsRow--extraVars { + margin-bottom: 10px; +} + .StandardOut-detailsLabel { width: 130px; flex: 0 0 130px; From e5b50f182efa888c1cfac1c2543d50d00928a3aa Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Mon, 7 Mar 2016 11:36:28 -0500 Subject: [PATCH 11/14] remove ref to removed function #1112 --- awx/ui/client/src/about/about.partial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/about/about.partial.html b/awx/ui/client/src/about/about.partial.html index 44d4914402..8d0c355e5c 100644 --- a/awx/ui/client/src/about/about.partial.html +++ b/awx/ui/client/src/about/about.partial.html @@ -3,7 +3,7 @@
- +