diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index f5b6f42c9a..f201b7352a 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -80,12 +80,6 @@ a.red-txt:active { text-overflow: ellipsis; } -.name-column { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - blockquote { font-size: 14px; } diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index b77d3738a2..9fa16dc0fb 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -44,9 +44,11 @@ color: @list-header-txt; font-size: 14px; font-weight: bold; - white-space: nowrap; padding-bottom: 25px; min-height: 45px; + word-break: break-all; + max-width: 90%; + word-wrap: break-word; } .Form-secondaryTitle{ @@ -55,7 +57,10 @@ min-height: 40px; } -.Form-title--is_superuser, .Form-title--is_system_auditor, .Form-title--is_ldap_user{ +.Form-title--is_superuser, +.Form-title--is_system_auditor, +.Form-title--is_ldap_user, +.Form-title--is_external_account{ height:15px; color: @default-interface-txt; background-color: @default-list-header-bg; @@ -353,6 +358,11 @@ border-color: transparent transparent @field-dropdown-icon transparent!important; } +.select2-container--default.select2-container--open.select2-container--below .select2-selection--single { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + .select2-dropdown{ border:1px solid @field-border; diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index 6282ab340e..cd486376f9 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -78,6 +78,7 @@ table, tbody { padding-left: 15px; padding-right: 15px; border-top:0px!important; + word-wrap: break-word; } .List-tableCell.description-column { @@ -383,6 +384,7 @@ table, tbody { .List-action--showTooltipOnDisabled { display: inline-block; + cursor: not-allowed; } .List-action--showTooltipOnDisabled .btn[disabled] { diff --git a/awx/ui/client/lib/angular-codemirror/.bower.json b/awx/ui/client/lib/angular-codemirror/.bower.json index 194a61b513..c88cb405db 100644 --- a/awx/ui/client/lib/angular-codemirror/.bower.json +++ b/awx/ui/client/lib/angular-codemirror/.bower.json @@ -1,6 +1,6 @@ { "name": "angular-codemirror", - "version": "1.0.2", + "version": "1.0.3", "dependencies": { "angular": "latest", "angular-route": "latest", @@ -13,14 +13,13 @@ "codemirror": "latest" }, "homepage": "https://github.com/chouseknecht/angular-codemirror", - "_release": "1.0.2", + "_release": "1.0.3", "_resolution": { "type": "version", - "tag": "v1.0.2", - "commit": "94b7aac548b036f4fbd94e56129ed9574e472616" + "tag": "1.0.3", + "commit": "b94dc86fde8f60a50b324054806d29d742177d21" }, - "_source": "git://github.com/chouseknecht/angular-codemirror.git", - "_target": "~1.0.2", - "_originalSource": "angular-codemirror", - "_direct": true + "_source": "https://github.com/chouseknecht/angular-codemirror.git", + "_target": "~1.0.3", + "_originalSource": "angular-codemirror" } \ No newline at end of file diff --git a/awx/ui/client/lib/angular-codemirror/bower.json b/awx/ui/client/lib/angular-codemirror/bower.json index 943b83b3e7..df7644686c 100644 --- a/awx/ui/client/lib/angular-codemirror/bower.json +++ b/awx/ui/client/lib/angular-codemirror/bower.json @@ -1,6 +1,6 @@ { "name": "angular-codemirror", - "version": "0.0.3", + "version": "1.0.2", "dependencies": { "angular": "latest", "angular-route": "latest", diff --git a/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.css b/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.css index a81ff6dff4..f233cc2941 100644 --- a/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.css +++ b/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.css @@ -1,6 +1,6 @@ /********************************************** * AngularCodeMirror.css - * + * * CodeMirror.css overrides * * Copyright (c) 2014 Chris Houseknecht @@ -30,14 +30,14 @@ .CodeMirror { height: auto; } - + .CodeMirror-activeline-background { background-color: #f7f7f7; } + - -/* Modal dialog overrides to make jqueryui dialog blend in with Twitter. +/* Modal dialog overrides to make jqueryui dialog blend in with Twitter. Why? Twitter's modal is not draggable or resizable, which is not very useful for a code editor */ @@ -71,7 +71,7 @@ border-color: #ffffff; color: #A9A9A9; } - + .ui-dialog .ui-resizable-se { right: 5px; bottom: 5px; @@ -108,3 +108,4 @@ .CodeMirror-lint-tooltip { z-index: 2060; } + diff --git a/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.js b/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.js index e4b9e5e02e..2ef0ad4fe7 100644 --- a/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.js +++ b/awx/ui/client/lib/angular-codemirror/lib/AngularCodeMirror.js @@ -30,7 +30,7 @@ angular.module('AngularCodeMirrorModule', []) .factory('AngularCodeMirror', [ function() { - return function() { + return function(readOnly) { var fn = function() { this.myCodeMirror = null; @@ -43,7 +43,6 @@ angular.module('AngularCodeMirrorModule', []) model = params.model, mode = params.mode, onReady = params.onReady, - onChange = params.onChange, height = 0; self.element = $(element); @@ -69,6 +68,15 @@ angular.module('AngularCodeMirrorModule', []) // Initialize CodeMirror self.modes[mode].value = scope[model]; + + // if readOnly is passed to AngularCodeMirror, set the + // options for all modes to be readOnly + if (readOnly) { + Object.keys(self.modes).forEach(function(val) { + self.modes[val].readOnly = true; + }); + } + self.myCodeMirror = CodeMirror(document.getElementById('cm-' + model + '-container'), self.modes[mode]); // Adjust the height @@ -85,14 +93,7 @@ angular.module('AngularCodeMirrorModule', []) // Update the model on change self.myCodeMirror.on('change', function() { - setTimeout(function() { - scope.$apply(function(){ - scope[model] = self.myCodeMirror.getValue(); - if (onChange) { - onChange(); - } - }); - }, 500); + setTimeout(function() { scope.$apply(function(){ scope[model] = self.myCodeMirror.getValue(); }); }, 500); }); }; diff --git a/awx/ui/client/lib/ngToast/.bower.json b/awx/ui/client/lib/ngtoast/.bower.json similarity index 91% rename from awx/ui/client/lib/ngToast/.bower.json rename to awx/ui/client/lib/ngtoast/.bower.json index 446800a71d..27c51cca55 100644 --- a/awx/ui/client/lib/ngToast/.bower.json +++ b/awx/ui/client/lib/ngtoast/.bower.json @@ -52,8 +52,7 @@ "tag": "2.0.0", "commit": "8a1951c54a956c33964c99b338f3a4830e652689" }, - "_source": "git://github.com/tameraydin/ngToast.git", + "_source": "https://github.com/tameraydin/ngToast.git", "_target": "~2.0.0", - "_originalSource": "ngtoast", - "_direct": true + "_originalSource": "ngtoast" } \ No newline at end of file diff --git a/awx/ui/client/lib/ngToast/README.md b/awx/ui/client/lib/ngtoast/README.md similarity index 100% rename from awx/ui/client/lib/ngToast/README.md rename to awx/ui/client/lib/ngtoast/README.md diff --git a/awx/ui/client/lib/ngToast/bower.json b/awx/ui/client/lib/ngtoast/bower.json similarity index 100% rename from awx/ui/client/lib/ngToast/bower.json rename to awx/ui/client/lib/ngtoast/bower.json diff --git a/awx/ui/client/lib/ngToast/dist/ngToast-animations.css b/awx/ui/client/lib/ngtoast/dist/ngToast-animations.css similarity index 100% rename from awx/ui/client/lib/ngToast/dist/ngToast-animations.css rename to awx/ui/client/lib/ngtoast/dist/ngToast-animations.css diff --git a/awx/ui/client/lib/ngToast/dist/ngToast-animations.min.css b/awx/ui/client/lib/ngtoast/dist/ngToast-animations.min.css similarity index 100% rename from awx/ui/client/lib/ngToast/dist/ngToast-animations.min.css rename to awx/ui/client/lib/ngtoast/dist/ngToast-animations.min.css diff --git a/awx/ui/client/lib/ngToast/dist/ngToast.css b/awx/ui/client/lib/ngtoast/dist/ngToast.css similarity index 100% rename from awx/ui/client/lib/ngToast/dist/ngToast.css rename to awx/ui/client/lib/ngtoast/dist/ngToast.css diff --git a/awx/ui/client/lib/ngToast/dist/ngToast.js b/awx/ui/client/lib/ngtoast/dist/ngToast.js similarity index 100% rename from awx/ui/client/lib/ngToast/dist/ngToast.js rename to awx/ui/client/lib/ngtoast/dist/ngToast.js diff --git a/awx/ui/client/lib/ngToast/dist/ngToast.min.css b/awx/ui/client/lib/ngtoast/dist/ngToast.min.css similarity index 100% rename from awx/ui/client/lib/ngToast/dist/ngToast.min.css rename to awx/ui/client/lib/ngtoast/dist/ngToast.min.css diff --git a/awx/ui/client/lib/ngToast/dist/ngToast.min.js b/awx/ui/client/lib/ngtoast/dist/ngToast.min.js similarity index 100% rename from awx/ui/client/lib/ngToast/dist/ngToast.min.js rename to awx/ui/client/lib/ngtoast/dist/ngToast.min.js diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.block.less b/awx/ui/client/src/bread-crumb/bread-crumb.block.less index 08abee34a7..ac8e0792c9 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.block.less +++ b/awx/ui/client/src/bread-crumb/bread-crumb.block.less @@ -66,6 +66,12 @@ display: inline-block; color: @default-interface-txt; text-transform: uppercase; + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align: bottom; } .BreadCrumb-item + .BreadCrumb-item:before { diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index eafbfbb981..3f0e3bdcfd 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -20,7 +20,8 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa var jobs_scope, scheduled_scope, choicesCount = 0, listCount = 0, - api_complete = false; + api_complete = false, + scheduledJobsList = _.cloneDeep(ScheduledJobsList); $scope.jobsSelected = true; @@ -66,22 +67,23 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa scope: jobs_scope, list: AllJobsList, id: 'active-jobs', - pageSize: 20, url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled,new&order_by=-finished', + pageSize: 20, searchParams: search_params, spinner: false }); scheduled_scope = $scope.$new(true); + scheduledJobsList.basePath = GetBasePath('schedules') + '?next_run__isnull=false'; LoadSchedulesScope({ parent_scope: $scope, scope: scheduled_scope, - list: ScheduledJobsList, + list: scheduledJobsList, pageSize: 20, id: 'scheduled-jobs-tab', searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12', - url: GetBasePath('schedules') + '?next_run__isnull=false' + url: scheduledJobsList.basePath }); $scope.refreshJobs = function() { diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 1b641ab97a..60692d581e 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -358,11 +358,8 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams, $scope.editSchedules = function(id) { var project = Find({ list: $scope.projects, key: 'id', val: id }); - if (project.scm_type === "Manual" || Empty(project.scm_type)) { - // Nothing to do - } - else { - $location.path('/projects/' + id + '/schedules'); + if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) { + $state.go('projectSchedules', {id: id}); } }; } diff --git a/awx/ui/client/src/controllers/Users.js b/awx/ui/client/src/controllers/Users.js index 7c9560f7ad..103e1d1c84 100644 --- a/awx/ui/client/src/controllers/Users.js +++ b/awx/ui/client/src/controllers/Users.js @@ -162,6 +162,7 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log, $scope.not_ldap_user = !$scope.ldap_user; $scope.ldap_dn = null; $scope.socialAuthUser = false; + $scope.external_account = null; generator.reset(); @@ -334,6 +335,7 @@ export function UsersEdit($scope, $rootScope, $location, $scope.not_ldap_user = !$scope.ldap_user; master.ldap_user = $scope.ldap_user; $scope.socialAuthUser = (data.auth.length > 0) ? true : false; + $scope.external_account = data.external_account; $scope.user_type = $scope.user_type_options[0]; $scope.is_system_auditor = false; diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts-list.controller.js b/awx/ui/client/src/dashboard/hosts/dashboard-hosts-list.controller.js index bbc57dc275..9009efe609 100644 --- a/awx/ui/client/src/dashboard/hosts/dashboard-hosts-list.controller.js +++ b/awx/ui/client/src/dashboard/hosts/dashboard-hosts-list.controller.js @@ -6,8 +6,8 @@ export default ['$scope', '$state', '$stateParams', 'PageRangeSetup', 'GetBasePath', 'DashboardHostsList', - 'generateList', 'PaginateInit', 'SetStatus', 'DashboardHostService', 'hosts', '$rootScope', - function($scope, $state, $stateParams, PageRangeSetup, GetBasePath, DashboardHostsList, GenerateList, PaginateInit, SetStatus, DashboardHostService, hosts, $rootScope){ + 'generateList', 'PaginateInit', 'SetStatus', 'DashboardHostService', 'hosts', '$rootScope', 'SearchInit', + function($scope, $state, $stateParams, PageRangeSetup, GetBasePath, DashboardHostsList, GenerateList, PaginateInit, SetStatus, DashboardHostService, hosts, $rootScope, SearchInit){ var setJobStatus = function(){ _.forEach($scope.hosts, function(value){ SetStatus({ @@ -59,6 +59,12 @@ export default $scope.hosts = hosts.results; setJobStatus(); generator.inject(list, {mode: 'edit', scope: $scope}); + SearchInit({ + scope: $scope, + set: 'hosts', + list: list, + url: defaultUrl + }); PaginateInit({ scope: $scope, list: list, @@ -77,6 +83,7 @@ export default $scope.rowBeingEdited = $state.params.id; $scope.listBeingEdited = "hosts"; } + $scope.search(list.iterator); }; init(); }]; diff --git a/awx/ui/client/src/dashboard/lists/dashboard-list.block.less b/awx/ui/client/src/dashboard/lists/dashboard-list.block.less index 589f6c292d..102188034d 100644 --- a/awx/ui/client/src/dashboard/lists/dashboard-list.block.less +++ b/awx/ui/client/src/dashboard/lists/dashboard-list.block.less @@ -101,10 +101,8 @@ .DashboardList-nameCell { padding-left: 15px; - text-overflow: ellipsis; - overflow:hidden; - white-space: nowrap; width: 100%; + word-wrap: break-word; } .DashboardList-nameContainer { diff --git a/awx/ui/client/src/forms/Inventories.js b/awx/ui/client/src/forms/Inventories.js index dd9fd44e51..6467bf28d3 100644 --- a/awx/ui/client/src/forms/Inventories.js +++ b/awx/ui/client/src/forms/Inventories.js @@ -81,7 +81,7 @@ export default permissions: { awToolTip: 'Please save before assigning permissions', dataPlacement: 'top', - basePath: 'projects/:id/access_list/', + basePath: 'inventories/:id/access_list/', type: 'collection', title: 'Permissions', iterator: 'permission', diff --git a/awx/ui/client/src/forms/JobTemplates.js b/awx/ui/client/src/forms/JobTemplates.js index 90c92f9f57..6cb6f833a2 100644 --- a/awx/ui/client/src/forms/JobTemplates.js +++ b/awx/ui/client/src/forms/JobTemplates.js @@ -281,7 +281,7 @@ export default column: 2, awPopOver: "callback_help", awPopOverWatch: "callback_help", - dataPlacement: 'right', + dataPlacement: 'top', dataTitle: 'Provisioning Callback URL', dataContainer: "body" }, diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js index 6ce19602b4..ddcda6e296 100644 --- a/awx/ui/client/src/forms/Users.js +++ b/awx/ui/client/src/forms/Users.js @@ -46,7 +46,7 @@ export default label: 'Username', type: 'text', awRequiredWhen: { - reqExpression: "not_ldap_user", + reqExpression: "not_ldap_user && external_account === null", init: true }, autocomplete: false @@ -69,7 +69,7 @@ export default label: 'Password', type: 'sensitive', hasShowInputButton: true, - ngShow: 'ldap_user == false && socialAuthUser === false', + ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null', addRequired: true, editRequired: false, ngChange: "clearPWConfirm('password_confirm')", @@ -80,7 +80,7 @@ export default label: 'Confirm Password', type: 'sensitive', hasShowInputButton: true, - ngShow: 'ldap_user == false && socialAuthUser === false', + ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null', addRequired: true, editRequired: false, awPassMatch: true, diff --git a/awx/ui/client/src/helpers/Adhoc.js b/awx/ui/client/src/helpers/Adhoc.js index 6754a60611..04ff4b2189 100644 --- a/awx/ui/client/src/helpers/Adhoc.js +++ b/awx/ui/client/src/helpers/Adhoc.js @@ -55,9 +55,9 @@ export default // Submit request to run an adhoc comamand .factory('AdhocRun', ['$location','$stateParams', 'LaunchJob', 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', - 'Wait', 'Empty', 'CreateLaunchDialog', + 'Wait', 'Empty', 'CreateLaunchDialog', '$state', function ($location, $stateParams, LaunchJob, PromptForPasswords, - Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog) { + Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) { return function (params) { var id = params.project_id, scope = params.scope.$new(), @@ -87,25 +87,31 @@ export default }); }); - if (scope.removeAdhocLaunchFinished) { - scope.removeAdhocLaunchFinished(); - } - scope.removeAdhocLaunchFinished = scope.$on('AdhocLaunchFinished', - function(e, data) { - $location.path('/ad_hoc_commands/' + data.id); - }); - if (scope.removeStartAdhocRun) { scope.removeStartAdhocRun(); } scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() { - LaunchJob({ - scope: scope, - url: url, - callback: 'AdhocLaunchFinished' // send to the adhoc - // standard out page - }); + var password, + postData={}; + for (password in scope.passwords) { + postData[scope.passwords[password]] = scope[ + scope.passwords[password] + ]; + } + // Re-launch the adhoc job + Rest.setUrl(url); + Rest.post(postData) + .success(function (data) { + Wait('stop'); + $state.go('adHocJobStdout', {id: data.id}); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, { + hdr: 'Error!', + msg: 'Failed to launch adhoc command. POST ' + + 'returned status: ' + status }); + }); }); // start routine only if passwords need to be prompted diff --git a/awx/ui/client/src/helpers/Groups.js b/awx/ui/client/src/helpers/Groups.js index fb8ff47f40..a1b0f44041 100644 --- a/awx/ui/client/src/helpers/Groups.js +++ b/awx/ui/client/src/helpers/Groups.js @@ -165,6 +165,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name has_inventory_sources = params.has_inventory_sources, launch_class = '', launch_tip = 'Start sync process', + schedule_tip = 'Schedule future inventory syncs', stat, stat_class, status_tip; stat = status; @@ -225,7 +226,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name "tooltip": status_tip, "status": stat, "launch_class": launch_class, - "launch_tip": launch_tip + "launch_tip": launch_tip, + "schedule_tip": schedule_tip }; }; } diff --git a/awx/ui/client/src/helpers/Parse.js b/awx/ui/client/src/helpers/Parse.js index c484963162..a81f8c8194 100644 --- a/awx/ui/client/src/helpers/Parse.js +++ b/awx/ui/client/src/helpers/Parse.js @@ -25,7 +25,8 @@ export default fld = (params.variable) ? params.variable : 'variables', pfld = (params.parse_variable) ? params.parse_variable : 'parseType', onReady = params.onReady, - onChange = params.onChange; + onChange = params.onChange, + readOnly = params.readOnly; function removeField(fld) { //set our model to the last change in CodeMirror and then destroy CodeMirror @@ -35,8 +36,7 @@ export default function createField(onChange, onReady, fld) { //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) - - scope[fld + 'codeMirror'] = AngularCodeMirror(); + scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly); scope[fld + 'codeMirror'].addModes($AnsibleConfig.variable_edit_modes); scope[fld + 'codeMirror'].showTextArea({ scope: scope, diff --git a/awx/ui/client/src/helpers/Projects.js b/awx/ui/client/src/helpers/Projects.js index 816fd73ba3..e284bc07d1 100644 --- a/awx/ui/client/src/helpers/Projects.js +++ b/awx/ui/client/src/helpers/Projects.js @@ -75,6 +75,9 @@ export default case 'missing': result = 'Missing. Click for details'; break; + case 'canceled': + result = 'Canceled. Click for details'; + break; } return result; }; diff --git a/awx/ui/client/src/inventories/manage/adhoc/adhoc.controller.js b/awx/ui/client/src/inventories/manage/adhoc/adhoc.controller.js index 7f67d81936..0281a9b0f2 100644 --- a/awx/ui/client/src/inventories/manage/adhoc/adhoc.controller.js +++ b/awx/ui/client/src/inventories/manage/adhoc/adhoc.controller.js @@ -242,7 +242,7 @@ function adhocController($q, $scope, $location, $stateParams, Rest.post(data) .success(function (data) { Wait('stop'); - $location.path("/ad_hoc_commands/" + data.id); + $state.go('adHocJobStdout', {id: data.id}); }) .error(function (data, status) { ProcessErrors($scope, data, status, adhocForm, { diff --git a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js index 9fe6d9809a..ca56eeddd4 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js @@ -151,6 +151,7 @@ {status_tooltip: group_status.tooltip}, {launch_tooltip: group_status.launch_tip}, {launch_class: group_status.launch_class}, + {group_schedule_tooltip: group_status.schedule_tip}, {hosts_status_tip: hosts_status.tooltip}, {hosts_status_class: hosts_status.class}, {source: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.source : null}, diff --git a/awx/ui/client/src/job-detail/job-detail.controller.js b/awx/ui/client/src/job-detail/job-detail.controller.js index 02cb21a5f1..9181b21b20 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -641,7 +641,7 @@ export default return true; }); //scope.setSearchAll('host'); - ParseTypeChange({ scope: scope, field_id: 'pre-formatted-variables' }); + ParseTypeChange({ scope: scope, field_id: 'pre-formatted-variables', readOnly: true }); scope.$emit('LoadPlays', data.related.job_events); }) .error(function(data, status) { diff --git a/awx/ui/client/src/job-templates/labels/labelsList.block.less b/awx/ui/client/src/job-templates/labels/labelsList.block.less index d851bd545e..2693ee99ee 100644 --- a/awx/ui/client/src/job-templates/labels/labelsList.block.less +++ b/awx/ui/client/src/job-templates/labels/labelsList.block.less @@ -8,7 +8,7 @@ } .LabelList-tagContainer, -.LabelList-seeMore { +.LabelList-seeMoreLess { display: flex; max-width: 100%; } @@ -27,7 +27,7 @@ overflow: hidden; } -.LabelList-seeMore { +.LabelList-seeMoreLess { color: @default-link; margin: 4px 0px; text-transform: uppercase; @@ -37,7 +37,7 @@ font-size: 11px; } -.LabelList-seeMore:hover { +.LabelList-seeMoreLess:hover { color: @default-link-hov; } diff --git a/awx/ui/client/src/job-templates/labels/labelsList.directive.js b/awx/ui/client/src/job-templates/labels/labelsList.directive.js index a5f055bb6e..212bd8bb64 100644 --- a/awx/ui/client/src/job-templates/labels/labelsList.directive.js +++ b/awx/ui/client/src/job-templates/labels/labelsList.directive.js @@ -47,6 +47,13 @@ export default }); }; + scope.seeLess = function() { + // Trim the labels array back down to 10 items + scope.labels = scope.labels.slice(0, 10); + // Re-set the seeMoreInteractive flag so that the "See More" will be displayed + scope.seeMoreInactive = true; + }; + scope.deleteLabel = function(templateId, templateName, labelId, labelName) { var action = function () { $('#prompt-modal').modal('hide'); @@ -56,13 +63,13 @@ export default Rest.setUrl(url); Rest.post({"disassociate": true, "id": labelId}) .success(function () { - scope.search("job_template"); + scope.search("job_template", scope.$parent.job_template_page); Wait('stop'); }) .error(function (data, status) { Wait('stop'); ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Could not disacssociate label from JT. Call to ' + url + ' failed. DELETE returned status: ' + status }); + msg: 'Could not disassociate label from JT. Call to ' + url + ' failed. DELETE returned status: ' + status }); }); }; @@ -86,6 +93,7 @@ export default scope.count = null; } }); + } }; } diff --git a/awx/ui/client/src/job-templates/labels/labelsList.partial.html b/awx/ui/client/src/job-templates/labels/labelsList.partial.html index 0bcc5f795a..b14ec6deb9 100644 --- a/awx/ui/client/src/job-templates/labels/labelsList.partial.html +++ b/awx/ui/client/src/job-templates/labels/labelsList.partial.html @@ -8,5 +8,7 @@ {{ label.name }} -
View More
+
View Less
diff --git a/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js b/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js index 56a3903f69..685476432a 100644 --- a/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js +++ b/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js @@ -314,7 +314,7 @@ export default ngClick: 'submitQuestion($event)', ngDisabled: true, 'class': 'btn btn-sm Form-saveButton', - label: '{{editQuestionIndex === null ? "ADD" : "UPDATE"}}' + label: '{{editQuestionIndex === null ? "+ ADD" : "UPDATE"}}' } } diff --git a/awx/ui/client/src/lists/AllJobs.js b/awx/ui/client/src/lists/AllJobs.js index ee28221c1d..cc9c03c752 100644 --- a/awx/ui/client/src/lists/AllJobs.js +++ b/awx/ui/client/src/lists/AllJobs.js @@ -48,9 +48,6 @@ export default columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6', ngClick: "viewJobDetails(all_job)", defaultSearchField: true, - awToolTip: "{{ all_job.name | sanitize }}", - dataTipWatch: 'all_job.name', - dataPlacement: 'top' }, type: { label: 'Type', diff --git a/awx/ui/client/src/lists/CompletedJobs.js b/awx/ui/client/src/lists/CompletedJobs.js index ca0a24e454..23e4f325a8 100644 --- a/awx/ui/client/src/lists/CompletedJobs.js +++ b/awx/ui/client/src/lists/CompletedJobs.js @@ -35,6 +35,7 @@ export default ngClick:"viewJobDetails(completed_job)", searchable: true, searchType: 'select', + defaultSearchField: true, nosort: true, searchOptions: [ { label: "Success", value: "successful" }, @@ -54,8 +55,8 @@ export default name: { label: 'Name', columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6', + searchable: false, ngClick: "viewJobDetails(completed_job)", - defaultSearchField: true, awToolTip: "{{ completed_job.name | sanitize }}", dataPlacement: 'top' }, @@ -64,7 +65,7 @@ export default ngBind: 'completed_job.type_label', link: false, columnClass: "col-lg-2 col-md-2 hidden-sm hidden-xs", - searchable: true, + searchable: false, searchType: 'select', searchOptions: [] // populated via GetChoices() in controller }, diff --git a/awx/ui/client/src/notifications/shared/toggle-notification.factory.js b/awx/ui/client/src/notifications/shared/toggle-notification.factory.js index 2bbe8e2ff5..3a530606a4 100644 --- a/awx/ui/client/src/notifications/shared/toggle-notification.factory.js +++ b/awx/ui/client/src/notifications/shared/toggle-notification.factory.js @@ -36,6 +36,8 @@ export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', disassociate: 1 }; } + // Show the working spinner + Wait('start'); Rest.setUrl(url); Rest.post(params) .success( function(data) { @@ -43,9 +45,8 @@ export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', scope.$emit(callback, data.id); notifier[column] = !notifier[column]; } - else { - Wait('stop'); - } + // Hide the working spinner + Wait('stop'); }) .error( function(data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index 406a1d1b78..a718b423c2 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -334,11 +334,8 @@ export default ['$scope', '$rootScope', '$location', '$log', $scope.editSchedules = function(id) { var project = Find({ list: $scope.projects, key: 'id', val: id }); - if (project.scm_type === "Manual" || Empty(project.scm_type)) { - // Nothing to do - } - else { - $location.path('/projects/' + id + '/schedules'); + if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) { + $state.go('projectSchedules', {id: id}); } }; diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index f7ccaf0be4..bdb7ddf03c 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -134,6 +134,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) }); $(document).bind('keydown', function (e) { if (e.keyCode === 27 || e.keyCode === 13) { + e.preventDefault(); $('#alert-modal2').modal('hide'); } }); @@ -161,6 +162,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) }); $(document).bind('keydown', function (e) { if (e.keyCode === 27 || e.keyCode === 13) { + e.preventDefault(); $('#alert-modal').modal('hide'); } }); diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 52cbe156b9..1d724b315a 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -390,46 +390,57 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) // lookup Validate lookup value against API // - .directive('awlookup', ['Rest', function(Rest) { + .directive('awlookup', ['Rest', '$timeout', function(Rest, $timeout) { return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { + + var restTimeout; + ctrl.$parsers.unshift( function(viewValue) { if (viewValue !== '' && viewValue !== null) { var url = elm.attr('data-url'); url = url.replace(/\:value/, encodeURI(viewValue)); scope[elm.attr('data-source')] = null; - Rest.setUrl(url); - Rest.get().then( function(data) { - var results = data.data.results; - if (results.length > 0) { - scope[elm.attr('data-source')] = results[0].id; + if(restTimeout) { + $timeout.cancel(restTimeout); + } + restTimeout = $timeout( function(){ + Rest.setUrl(url); + Rest.get().then( function(data) { + var results = data.data.results; + if (results.length > 0) { + scope[elm.attr('data-source')] = results[0].id; - // For user lookups the API endpoint doesn't - // have a `name` property, so this is `undefined` - // which causes the input to clear after typing - // a valid value O_o - // - // Only assign if there is a value, so that we avoid - // this situation. - // - // TODO: Evaluate if assigning name on the scope is - // even necessary at all. - // - if (!_.isEmpty(results[0].name)) { - scope[elm.attr('name')] = results[0].name; + // For user lookups the API endpoint doesn't + // have a `name` property, so this is `undefined` + // which causes the input to clear after typing + // a valid value O_o + // + // Only assign if there is a value, so that we avoid + // this situation. + // + // TODO: Evaluate if assigning name on the scope is + // even necessary at all. + // + if (!_.isEmpty(results[0].name)) { + scope[elm.attr('name')] = results[0].name; + } + + ctrl.$setValidity('required', true); + ctrl.$setValidity('awlookup', true); + return viewValue; } - ctrl.$setValidity('required', true); - ctrl.$setValidity('awlookup', true); - return viewValue; - } - ctrl.$setValidity('required', true); - ctrl.$setValidity('awlookup', false); - return undefined; - }); + ctrl.$setValidity('awlookup', false); + return undefined; + }); + }, 750); } else { + if(restTimeout) { + $timeout.cancel(restTimeout); + } ctrl.$setValidity('awlookup', true); scope[elm.attr('data-source')] = null; } @@ -477,7 +488,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) return { link: function(scope, element, attrs) { var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100}, - placement; + placement, + stateChangeWatcher; if (attrs.awTipPlacement) { placement = attrs.awTipPlacement; } @@ -493,6 +505,22 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) template = ''; } + // This block helps clean up tooltips that may get orphaned by a click event + $(element).on('mouseenter', function() { + if(stateChangeWatcher) { + // Un-bind - we don't want a bunch of listeners firing + stateChangeWatcher(); + } + stateChangeWatcher = scope.$on('$stateChangeStart', function() { + // Go ahead and force the tooltip setTimeout to expire (if it hasn't already fired) + $(element).tooltip('hide'); + // Clean up any existing tooltips including this one + $('.tooltip').each(function() { + $(this).remove(); + }); + }); + }); + $(element).on('hidden.bs.tooltip', function( ) { // TB3RC1 is leaving behind tooltip
elements. This will remove them // after a tooltip fades away. If not, they lay overtop of other elements and diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 4c8382db38..ac90c89b7b 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1487,6 +1487,8 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat "ng-show='is_system_auditor'>Auditor"; html+= "LDAP"; + html+= "{{external_account}}"; } html += "
\n"; html += "
"; diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index 9fc63e0969..dd7a6beb41 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -466,7 +466,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate innerTable += "ng-class-odd=\"'List-tableRow--oddRow'\" "; innerTable += "ng-class-even=\"'List-tableRow--evenRow'\" "; innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name; - innerTable += (list.trackBy) ? " track by " + list.trackBy : " track by $index"; + innerTable += (list.trackBy) ? " track by " + list.trackBy : ""; innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : ""; innerTable += (list.filterBy) ? " | filter: " + list.filterBy : ""; innerTable += "\">\n"; 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 f43d6c4ea5..acdd5815a2 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -74,6 +74,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, $scope.limit = data.limit; $scope.verbosity = data.verbosity; $scope.job_tags = data.job_tags; + $scope.job.module_name = data.module_name; if (data.extra_vars) { $scope.variables = ParseVariableString(data.extra_vars); }