From 989cf4d1aeccae13467840fa01260eaaeff910b1 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 14 Mar 2016 16:13:01 -0400 Subject: [PATCH 01/33] Set up inventory module and split apart controllers/inventory.js into more manageable chunks. --- awx/ui/client/src/app.js | 6 +- awx/ui/client/src/controllers/Inventories.js | 1296 ----------------- .../src/inventory/inventory-add.controller.js | 98 ++ .../inventory/inventory-edit.controller.js | 332 +++++ .../inventory/inventory-list.controller.js | 371 +++++ .../inventory/inventory-manage.controller.js | 532 +++++++ awx/ui/client/src/inventory/main.js | 10 + 7 files changed, 1348 insertions(+), 1297 deletions(-) delete mode 100644 awx/ui/client/src/controllers/Inventories.js create mode 100644 awx/ui/client/src/inventory/inventory-add.controller.js create mode 100644 awx/ui/client/src/inventory/inventory-edit.controller.js create mode 100644 awx/ui/client/src/inventory/inventory-list.controller.js create mode 100644 awx/ui/client/src/inventory/inventory-manage.controller.js create mode 100644 awx/ui/client/src/inventory/main.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e207eab59b..2a3367805c 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -26,6 +26,7 @@ import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Cr import {JobsListController} from './controllers/Jobs'; import {PortalController} from './controllers/Portal'; import systemTracking from './system-tracking/main'; +import inventory from './inventory/main'; import inventoryScripts from './inventory-scripts/main'; import organizations from './organizations/main'; import permissions from './permissions/main'; @@ -54,7 +55,10 @@ import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects'; import OrganizationsList from './organizations/list/organizations-list.controller'; import OrganizationsAdd from './organizations/add/organizations-add.controller'; import OrganizationsEdit from './organizations/edit/organizations-edit.controller'; -import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from './controllers/Inventories'; +import InventoriesAdd from './inventory/inventory-add.controller'; +import InventoriesEdit from './inventory/inventory-edit.controller'; +import InventoriesList from './inventory/inventory-list.controller'; +import InventoriesManage from './inventory/inventory-manage.controller'; import {AdminsList} from './controllers/Admins'; import {UsersList, UsersAdd, UsersEdit} from './controllers/Users'; import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams'; diff --git a/awx/ui/client/src/controllers/Inventories.js b/awx/ui/client/src/controllers/Inventories.js deleted file mode 100644 index 62dfb5b03b..0000000000 --- a/awx/ui/client/src/controllers/Inventories.js +++ /dev/null @@ -1,1296 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page -*/ - -import '../job-templates/main'; - -export function InventoriesList($scope, $rootScope, $location, $log, - $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, - generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, - ClearScope, ProcessErrors, GetBasePath, Wait, - EditInventoryProperties, Find, Empty, $state) { - - var list = InventoryList, - defaultUrl = GetBasePath('inventory'), - view = generateList, - paths = $location.path().replace(/^\//, '').split('/'), - mode = (paths[0] === 'inventories') ? 'edit' : 'select'; - - function ellipsis(a) { - if (a.length > 20) { - return a.substr(0,20) + '...'; - } - return a; - } - - function attachElem(event, html, title) { - var elem = $(event.target).parent(); - try { - elem.tooltip('hide'); - elem.popover('destroy'); - } - catch(err) { - //ignore - } - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each( function() { - // close any lingering tool tipss - $(this).hide(); - }); - elem.attr({ - "aw-pop-over": html, - "data-popover-title": title, - "data-placement": "right" }); - $compile(elem)($scope); - elem.on('shown.bs.popover', function() { - $('.popover').each(function() { - $compile($(this))($scope); //make nested directives work! - }); - $('.popover-content, .popover-title').click(function() { - elem.popover('hide'); - }); - }); - elem.popover('show'); - } - - view.inject(InventoryList, { mode: mode, scope: $scope }); - $rootScope.flashMessage = null; - - SearchInit({ - scope: $scope, - set: 'inventories', - list: list, - url: defaultUrl - }); - - PaginateInit({ - scope: $scope, - list: list, - url: defaultUrl - }); - - if ($stateParams.name) { - $scope[InventoryList.iterator + 'InputDisable'] = false; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name; - $scope[InventoryList.iterator + 'SearchField'] = 'name'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label; - $scope[InventoryList.iterator + 'SearchSelectValue'] = null; - } - - if ($stateParams.has_active_failures) { - $scope[InventoryList.iterator + 'InputDisable'] = true; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures; - $scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label; - $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? { - value: 1 - } : { - value: 0 - }; - } - - if ($stateParams.has_inventory_sources) { - $scope[InventoryList.iterator + 'InputDisable'] = true; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources; - $scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label; - $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? { - value: 1 - } : { - value: 0 - }; - } - - if ($stateParams.inventory_sources_with_failures) { - // pass a value of true, however this field actually contains an integer value - $scope[InventoryList.iterator + 'InputDisable'] = true; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures; - $scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label; - $scope[InventoryList.iterator + 'SearchType'] = 'gtzero'; - } - - $scope.search(list.iterator); - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function () { - //If we got here by deleting an inventory, stop the spinner and cleanup events - Wait('stop'); - try { - $('#prompt-modal').modal('hide'); - } - catch(e) { - // ignore - } - $scope.inventories.forEach(function(inventory, idx) { - $scope.inventories[idx].launch_class = ""; - if (inventory.has_inventory_sources) { - if (inventory.inventory_sources_with_failures > 0) { - $scope.inventories[idx].syncStatus = 'error'; - $scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details'; - } - else { - $scope.inventories[idx].syncStatus = 'successful'; - $scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.'; - } - } - else { - $scope.inventories[idx].syncStatus = 'na'; - $scope.inventories[idx].syncTip = 'Not configured for inventory sync.'; - $scope.inventories[idx].launch_class = "btn-disabled"; - } - if (inventory.has_active_failures) { - $scope.inventories[idx].hostsStatus = 'error'; - $scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.'; - } - else if (inventory.total_hosts) { - $scope.inventories[idx].hostsStatus = 'successful'; - $scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.'; - } - else { - $scope.inventories[idx].hostsStatus = 'none'; - $scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.'; - } - }); - }); - - if ($scope.removeRefreshInventories) { - $scope.removeRefreshInventories(); - } - $scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () { - // Reflect changes after inventory properties edit completes - $scope.search(list.iterator); - }); - - if ($scope.removeHostSummaryReady) { - $scope.removeHostSummaryReady(); - } - $scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) { - - var html, title = "Recent Jobs"; - Wait('stop'); - if (data.count > 0) { - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - html += "\n"; - - data.results.forEach(function(row) { - html += "\n"; - html += "\n"; - html += ""; - html += ""; - html += "\n"; - }); - html += "\n"; - html += "
StatusFinishedName
" + ($filter('longDate')(row.finished)).replace(/ /,'
') + "
" + ellipsis(row.name) + "
\n"; - } - else { - html = "

No recent job data available for this inventory.

\n"; - } - attachElem(event, html, title); - }); - - if ($scope.removeGroupSummaryReady) { - $scope.removeGroupSummaryReady(); - } - $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) { - var html, title; - - Wait('stop'); - - // Build the html for our popover - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - data.results.forEach( function(row) { - if (row.related.last_update) { - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - } - else { - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - } - }); - html += "\n"; - html += "
StatusLast SyncGroup
" + ($filter('longDate')(row.last_updated)).replace(/ /,'
') + "
" + ellipsis(row.summary_fields.group.name) + "
NA" + ellipsis(row.summary_fields.group.name) + "
\n"; - title = "Sync Status"; - attachElem(event, html, title); - }); - - $scope.showGroupSummary = function(event, id) { - var inventory; - if (!Empty(id)) { - inventory = Find({ list: $scope.inventories, key: 'id', val: id }); - if (inventory.syncStatus !== 'na') { - Wait('start'); - Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); - Rest.get() - .success(function(data) { - $scope.$emit('GroupSummaryReady', event, inventory, data); - }) - .error(function(data, status) { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status - }); - }); - } - } - }; - - $scope.showHostSummary = function(event, id) { - var url, inventory; - if (!Empty(id)) { - inventory = Find({ list: $scope.inventories, key: 'id', val: id }); - if (inventory.total_hosts > 0) { - Wait('start'); - url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed="; - url += (inventory.has_active_failures) ? 'true' : "false"; - url += "&order_by=-finished&page_size=5"; - Rest.setUrl(url); - Rest.get() - .success( function(data) { - $scope.$emit('HostSummaryReady', event, data); - }) - .error( function(data, status) { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status - }); - }); - } - } - }; - - $scope.viewJob = function(url) { - - // Pull the id out of the URL - var id = url.replace(/^\//, '').split('/')[3]; - - $state.go('inventorySyncStdout', {id: id}); - - }; - - $scope.editInventoryProperties = function (inventory_id) { - EditInventoryProperties({ scope: $scope, inventory_id: inventory_id }); - }; - - $scope.addInventory = function () { - $state.go('inventories.add'); - }; - - $scope.editInventory = function (id) { - $state.go('inventories.edit', {inventory_id: id}); - }; - - $scope.manageInventory = function(id){ - $location.path($location.path() + '/' + id + '/manage'); - }; - - $scope.deleteInventory = function (id, name) { - - var action = function () { - var url = defaultUrl + id + '/'; - Wait('start'); - $('#prompt-modal').modal('hide'); - 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 - }); - }); - }; - - Prompt({ - hdr: 'Delete', - body: '
Are you sure you want to delete the inventory below?
' + $filter('sanitize')(name) + '
', - action: action, - actionText: 'DELETE' - }); - }; - - $scope.lookupOrganization = function (organization_id) { - Rest.setUrl(GetBasePath('organizations') + organization_id + '/'); - Rest.get() - .success(function (data) { - return data.name; - }); - }; - - - // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status - $scope.viewJobs = function (id) { - $location.url('/jobs/?inventory__int=' + id); - }; - - $scope.viewFailedJobs = function (id) { - $location.url('/jobs/?inventory__int=' + id + '&status=failed'); - }; -} - -InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', 'generateList', - 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', - 'GetBasePath', 'Wait', 'EditInventoryProperties', 'Find', 'Empty', '$state' -]; - - -export function InventoriesAdd($scope, $rootScope, $compile, $location, $log, - $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, - ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, - PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, - $state) { - - ClearScope(); - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = InventoryForm(), - generator = GenerateForm; - - form.well = true; - form.formLabelSize = null; - form.formFieldSize = null; - - generator.inject(form, { mode: 'add', related: false, scope: $scope }); - - generator.reset(); - - $scope.parseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - variable: 'variables', - parse_variable: 'parseType', - field_id: 'inventory_variables' - }); - - LookUpInit({ - scope: $scope, - form: form, - current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null, - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); - - // Save - $scope.formSave = function () { - generator.clearApiErrors(); - Wait('start'); - try { - var fld, json_data, data; - - json_data = ToJSON($scope.parseType, $scope.variables, true); - - data = {}; - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - - Rest.setUrl(defaultUrl); - Rest.post(data) - .success(function (data) { - var inventory_id = data.id; - Wait('stop'); - $location.path('/inventories/' + inventory_id + '/manage'); - }) - .error(function (data, status) { - ProcessErrors( $scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to add new inventory. Post returned status: ' + status }); - }); - } catch (err) { - Wait('stop'); - Alert("Error", "Error parsing inventory variables. Parser returned: " + err); - } - - }; - - $scope.formCancel = function () { - $state.transitionTo('inventories'); - }; -} - -InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', - 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', - 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', - 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state' -]; - -export function InventoriesEdit($scope, $rootScope, $compile, $location, - $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, - ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, - PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, - ParseVariableString, RelatedSearchInit, RelatedPaginateInit, - Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) { - - ClearScope(); - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = InventoryForm(), - generator = GenerateForm, - inventory_id = $stateParams.inventory_id, - master = {}, - fld, json_data, data, - relatedSets = {}; - - form.well = true; - form.formLabelSize = null; - form.formFieldSize = null; - $scope.inventory_id = inventory_id; - generator.inject(form, { mode: 'edit', related: true, scope: $scope }); - - generator.reset(); - - - // After the project is loaded, retrieve each related set - if ($scope.inventoryLoadedRemove) { - $scope.inventoryLoadedRemove(); - } - $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () { - var set; - for (set in relatedSets) { - $scope.search(relatedSets[set].iterator); - } - }); - - Wait('start'); - Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); - Rest.get() - .success(function (data) { - var fld; - for (fld in form.fields) { - if (fld === 'variables') { - $scope.variables = ParseVariableString(data.variables); - master.variables = $scope.variables; - } else if (fld === 'inventory_name') { - $scope[fld] = data.name; - master[fld] = $scope[fld]; - } else if (fld === 'inventory_description') { - $scope[fld] = data.description; - master[fld] = $scope[fld]; - } else if (data[fld]) { - $scope[fld] = data[fld]; - master[fld] = $scope[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]; - } - } - relatedSets = form.relatedSets(data.related); - - // Initialize related search functions. Doing it here to make sure relatedSets object is populated. - RelatedSearchInit({ - scope: $scope, - form: form, - relatedSets: relatedSets - }); - RelatedPaginateInit({ - scope: $scope, - relatedSets: relatedSets - }); - - Wait('stop'); - $scope.parseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - variable: 'variables', - parse_variable: 'parseType', - field_id: 'inventory_variables' - }); - LookUpInit({ - scope: $scope, - form: form, - current_item: $scope.organization, - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); - $scope.$emit('inventoryLoaded'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status }); - }); - // Save - $scope.formSave = function () { - Wait('start'); - - // Make sure we have valid variable data - json_data = ToJSON($scope.parseType, $scope.variables); - - data = {}; - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - - Rest.setUrl(defaultUrl + inventory_id + '/'); - Rest.put(data) - .success(function () { - Wait('stop'); - $location.path('/inventories/'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to update inventory. PUT returned status: ' + status }); - }); - }; - - $scope.manageInventory = function(){ - $location.path($location.path() + '/manage'); - }; - - $scope.formCancel = function () { - $state.transitionTo('inventories'); - }; - - $scope.addScanJob = function(){ - $location.path($location.path()+'/job_templates/add'); - }; - - $scope.launchScanJob = function(){ - PlaybookRun({ scope: $scope, id: this.scan_job_template.id }); - }; - - $scope.scheduleScanJob = function(){ - $location.path('/job_templates/'+this.scan_job_template.id+'/schedules'); - }; - - $scope.editScanJob = function(){ - $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); - }; - - $scope.copyScanJobTemplate = function(){ - var id = this.scan_job_template.id, - name = this.scan_job_template.name, - element, - buttons = [{ - "label": "Cancel", - "onClick": function() { - $(this).dialog('close'); - }, - "icon": "fa-times", - "class": "btn btn-default", - "id": "copy-close-button" - },{ - "label": "Copy", - "onClick": function() { - copyAction(); - }, - "icon": "fa-copy", - "class": "btn btn-primary", - "id": "job-copy-button" - }], - copyAction = function () { - // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id - Wait('start'); - var url = GetBasePath('job_templates')+id; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - data.name = $scope.new_copy_name; - delete data.id; - $scope.$emit('GoToCopy', data); - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - }; - - - CreateDialog({ - id: 'copy-job-modal' , - title: "Copy", - scope: $scope, - buttons: buttons, - width: 500, - height: 300, - minWidth: 200, - callback: 'CopyDialogReady' - }); - - $('#job_name').text(name); - $('#copy-job-modal').show(); - - - if ($scope.removeCopyDialogReady) { - $scope.removeCopyDialogReady(); - } - $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() { - //clear any old remaining text - $scope.new_copy_name = "" ; - $scope.copy_form.$setPristine(); - $('#copy-job-modal').dialog('open'); - $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid"); - element = angular.element(document.getElementById('job-copy-button')); - $compile(element)($scope); - - }); - - if ($scope.removeGoToCopy) { - $scope.removeGoToCopy(); - } - $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) { - var url = GetBasePath('job_templates'), - old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ; - Rest.setUrl(url); - Rest.post(data) - .success(function (data) { - if(data.survey_enabled===true){ - $scope.$emit("CopySurvey", data, old_survey_url); - } - else { - $('#copy-job-modal').dialog('close'); - Wait('stop'); - $location.path($location.path() + '/job_templates/' + data.id); - } - - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - }); - - if ($scope.removeCopySurvey) { - $scope.removeCopySurvey(); - } - $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) { - // var url = data.related.survey_spec; - Rest.setUrl(old_url); - Rest.get() - .success(function (survey_data) { - - Rest.setUrl(new_data.related.survey_spec); - Rest.post(survey_data) - .success(function () { - $('#copy-job-modal').dialog('close'); - Wait('stop'); - $location.path($location.path() + '/job_templates/' + new_data.id); - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status }); - }); - - - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status }); - }); - - }); - - }; - - $scope.deleteScanJob = function () { - var id = this.scan_job_template.id , - action = function () { - $('#prompt-modal').modal('hide'); - Wait('start'); - deleteJobTemplate(id) - .success(function () { - $('#prompt-modal').modal('hide'); - $scope.search(form.related.scan_job_templates.iterator); - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'DELETE returned status: ' + status }); - }); - }; - - Prompt({ - hdr: 'Delete', - body: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
', - action: action, - actionText: 'DELETE' - }); - - }; - -} - -InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', - 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', - 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', - 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', - 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', - 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state' -]; - - - -export function InventoriesManage ($log, $scope, $rootScope, $location, - $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, - GetBasePath, ProcessErrors, InventoryGroups, - InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, - GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate, - ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, - EditInventoryProperties, ToggleHostEnabled, ShowJobSummary, - InventoryGroupsHelp, HelpDialog, - GroupsCopy, HostsCopy, $stateParams) { - - var PreviousSearchParams, - url, - hostScope = $scope.$new(); - - ClearScope(); - - // TODO: only display adhoc button if the user has permission to use it. - // TODO: figure out how to get the action-list partial to update so that - // the tooltip can be changed based off things being selected or not. - $scope.adhocButtonTipContents = "Launch adhoc command for the inventory"; - - // watcher for the group list checkbox changes - $scope.$on('multiSelectList.selectionChanged', function(e, selection) { - if (selection.length > 0) { - $scope.groupsSelected = true; - // $scope.adhocButtonTipContents = "Launch adhoc command for the " - // + "selected groups and hosts."; - } else { - $scope.groupsSelected = false; - // $scope.adhocButtonTipContents = "Launch adhoc command for the " - // + "inventory."; - } - $scope.groupsSelectedItems = selection.selectedItems; - }); - - // watcher for the host list checkbox changes - hostScope.$on('multiSelectList.selectionChanged', function(e, selection) { - // you need this so that the event doesn't bubble to the watcher above - // for the host list - e.stopPropagation(); - if (selection.length === 0) { - $scope.hostsSelected = false; - } else if (selection.length === 1) { - $scope.systemTrackingTooltip = "Compare host over time"; - $scope.hostsSelected = true; - $scope.systemTrackingDisabled = false; - } else if (selection.length === 2) { - $scope.systemTrackingTooltip = "Compare hosts against each other"; - $scope.hostsSelected = true; - $scope.systemTrackingDisabled = false; - } else { - $scope.hostsSelected = true; - $scope.systemTrackingDisabled = true; - } - $scope.hostsSelectedItems = selection.selectedItems; - }); - - $scope.systemTracking = function() { - var hostIds = _.map($scope.hostsSelectedItems, function(x){ - return x.id; - }); - $state.transitionTo('systemTracking', - { inventory: $scope.inventory, - inventoryId: $scope.inventory.id, - hosts: $scope.hostsSelectedItems, - hostIds: hostIds - }); - }; - - // populates host patterns based on selected hosts/groups - $scope.populateAdhocForm = function() { - var host_patterns = "all"; - if ($scope.hostsSelected || $scope.groupsSelected) { - var allSelectedItems = []; - if ($scope.groupsSelectedItems) { - allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems); - } - if ($scope.hostsSelectedItems) { - allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems); - } - if (allSelectedItems) { - host_patterns = _.pluck(allSelectedItems, "name").join(":"); - } - } - $rootScope.hostPatterns = host_patterns; - $state.go('inventoryManage.adhoc'); - }; - - $scope.refreshHostsOnGroupRefresh = false; - $scope.selected_group_id = null; - - Wait('start'); - - - if ($scope.removeHostReloadComplete) { - $scope.removeHostReloadComplete(); - } - $scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() { - if ($scope.initial_height) { - var host_height = $('#hosts-container .well').height(), - group_height = $('#group-list-container .well').height(), - new_height; - - if (host_height > group_height) { - new_height = host_height - (host_height - group_height); - } - else if (host_height < group_height) { - new_height = host_height + (group_height - host_height); - } - if (new_height) { - $('#hosts-container .well').height(new_height); - } - $scope.initial_height = null; - } - }); - - if ($scope.removeRowCountReady) { - $scope.removeRowCountReady(); - } - $scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) { - // Add hosts view - $scope.show_failures = false; - InjectHosts({ - group_scope: $scope, - host_scope: hostScope, - inventory_id: $scope.inventory.id, - tree_id: null, - group_id: null, - pageSize: rows - }); - - SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups }); - PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows }); - $scope.search(InventoryGroups.iterator, null, true); - }); - - if ($scope.removeInventoryLoaded) { - $scope.removeInventoryLoaded(); - } - $scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() { - var rows; - - // Add groups view - generateList.inject(InventoryGroups, { - mode: 'edit', - id: 'group-list-container', - searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12', - scope: $scope - }); - - rows = 20; - hostScope.host_page_size = rows; - $scope.group_page_size = rows; - - $scope.show_failures = false; - InjectHosts({ - group_scope: $scope, - host_scope: hostScope, - inventory_id: $scope.inventory.id, - tree_id: null, - group_id: null, - pageSize: rows - }); - - // Load data - SearchInit({ - scope: $scope, - set: 'groups', - list: InventoryGroups, - url: $scope.inventory.related.root_groups - }); - - PaginateInit({ - scope: $scope, - list: InventoryGroups , - url: $scope.inventory.related.root_groups, - pageSize: rows - }); - - $scope.search(InventoryGroups.iterator, null, true); - - $scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates - }); - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) { - if (set === 'groups') { - $scope.groups.forEach( function(group, idx) { - var stat, hosts_status; - stat = GetSyncStatusMsg({ - status: group.summary_fields.inventory_source.status, - has_inventory_sources: group.has_inventory_sources, - source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null ) - }); // from helpers/Groups.js - $scope.groups[idx].status_class = stat['class']; - $scope.groups[idx].status_tooltip = stat.tooltip; - $scope.groups[idx].launch_tooltip = stat.launch_tip; - $scope.groups[idx].launch_class = stat.launch_class; - hosts_status = GetHostsStatusMsg({ - active_failures: group.hosts_with_active_failures, - total_hosts: group.total_hosts, - inventory_id: $scope.inventory.id, - group_id: group.id - }); // from helpers/Groups.js - $scope.groups[idx].hosts_status_tip = hosts_status.tooltip; - $scope.groups[idx].show_failures = hosts_status.failures; - $scope.groups[idx].hosts_status_class = hosts_status['class']; - - $scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null; - $scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null; - - }); - if ($scope.refreshHostsOnGroupRefresh) { - $scope.refreshHostsOnGroupRefresh = false; - HostsReload({ - scope: hostScope, - group_id: $scope.selected_group_id, - inventory_id: $scope.inventory.id, - pageSize: hostScope.host_page_size - }); - } - else { - Wait('stop'); - } - } - }); - - // Load Inventory - url = GetBasePath('inventory') + $stateParams.inventory_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - $scope.inventory = data; - $scope.$emit('InventoryLoaded'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id + - ' GET returned status: ' + status }); - }); - - // start watching for real-time updates - if ($rootScope.removeWatchUpdateStatus) { - $rootScope.removeWatchUpdateStatus(); - } - $rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) { - var stat, group; - if (data.group_id) { - group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); - if (data.status === "failed" || data.status === "successful") { - if (data.group_id === $scope.selected_group_id || group) { - // job completed, fefresh all groups - $log.debug('Update completed. Refreshing the tree.'); - $scope.refreshGroups(); - } - } - else if (group) { - // incremental update, just update - $log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status); - stat = GetSyncStatusMsg({ - status: data.status, - has_inventory_sources: group.has_inventory_sources, - source: group.source - }); - $log.debug('changing tooltip to: ' + stat.tooltip); - group.status = data.status; - group.status_class = stat['class']; - group.status_tooltip = stat.tooltip; - group.launch_tooltip = stat.launch_tip; - group.launch_class = stat.launch_class; - } - } - }); - - // Load group on selection - function loadGroups(url) { - SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url }); - PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size }); - $scope.search(InventoryGroups.iterator, null, true, false, true); - } - - $scope.refreshHosts = function() { - HostsReload({ - scope: hostScope, - group_id: $scope.selected_group_id, - inventory_id: $scope.inventory.id, - pageSize: hostScope.host_page_size - }); - }; - - $scope.refreshGroups = function() { - $scope.refreshHostsOnGroupRefresh = true; - $scope.search(InventoryGroups.iterator, null, true, false, true); - }; - - $scope.restoreSearch = function() { - // Restore search params and related stuff, plus refresh - // groups and hosts lists - SearchInit({ - scope: $scope, - set: PreviousSearchParams.set, - list: PreviousSearchParams.list, - url: PreviousSearchParams.defaultUrl, - iterator: PreviousSearchParams.iterator, - sort_order: PreviousSearchParams.sort_order, - setWidgets: false - }); - $scope.refreshHostsOnGroupRefresh = true; - $scope.search(InventoryGroups.iterator, null, true, false, true); - }; - - $scope.groupSelect = function(id) { - var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id }); - if($state.params.groups){ - groups.push($state.params.groups); - } - groups.push(group.id); - groups = groups.join(); - $state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false }); - loadGroups(group.related.children, group.id); - }; - - $scope.createGroup = function () { - PreviousSearchParams = Store('group_current_search_params'); - GroupsEdit({ - scope: $scope, - inventory_id: $scope.inventory.id, - group_id: $scope.selected_group_id, - mode: 'add' - }); - }; - - $scope.editGroup = function (id) { - PreviousSearchParams = Store('group_current_search_params'); - GroupsEdit({ - scope: $scope, - inventory_id: $scope.inventory.id, - group_id: id, - mode: 'edit' - }); - }; - - // Launch inventory sync - $scope.updateGroup = function (id) { - var group = Find({ list: $scope.groups, key: 'id', val: id }); - if (group) { - if (Empty(group.source)) { - // if no source, do nothing. - } else if (group.status === 'updating') { - Alert('Update in Progress', 'The inventory update process is currently running for group ' + - group.name + ' Click the button to monitor the status.', 'alert-info', null, null, null, null, true); - } else { - Wait('start'); - Rest.setUrl(group.related.inventory_source); - Rest.get() - .success(function (data) { - InventoryUpdate({ - scope: $scope, - url: data.related.update, - group_name: data.summary_fields.group.name, - group_source: data.source, - group_id: group.id, - }); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + - group.related.inventory_source + ' GET returned status: ' + status }); - }); - } - } - }; - - $scope.cancelUpdate = function (id) { - GroupsCancelUpdate({ scope: $scope, id: id }); - }; - - $scope.viewUpdateStatus = function (id) { - ViewUpdateStatus({ - scope: $scope, - group_id: id - }); - }; - - $scope.copyGroup = function(id) { - PreviousSearchParams = Store('group_current_search_params'); - GroupsCopy({ - scope: $scope, - group_id: id - }); - }; - - $scope.deleteGroup = function (id) { - GroupsDelete({ - scope: $scope, - group_id: id, - inventory_id: $scope.inventory.id - }); - }; - - $scope.editInventoryProperties = function () { - // EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id }); - $location.path('/inventories/' + $scope.inventory.id + '/'); - }; - - hostScope.createHost = function () { - HostsEdit({ - host_scope: hostScope, - group_scope: $scope, - mode: 'add', - host_id: null, - selected_group_id: $scope.selected_group_id, - inventory_id: $scope.inventory.id - }); - }; - - hostScope.editHost = function (host_id) { - HostsEdit({ - host_scope: hostScope, - group_scope: $scope, - mode: 'edit', - host_id: host_id, - inventory_id: $scope.inventory.id - }); - }; - - hostScope.deleteHost = function (host_id, host_name) { - HostsDelete({ - parent_scope: $scope, - host_scope: hostScope, - host_id: host_id, - host_name: host_name - }); - }; - - hostScope.copyHost = function(id) { - PreviousSearchParams = Store('group_current_search_params'); - HostsCopy({ - group_scope: $scope, - host_scope: hostScope, - host_id: id - }); - }; - - /*hostScope.restoreSearch = function() { - SearchInit({ - scope: hostScope, - set: PreviousSearchParams.set, - list: PreviousSearchParams.list, - url: PreviousSearchParams.defaultUrl, - iterator: PreviousSearchParams.iterator, - sort_order: PreviousSearchParams.sort_order, - setWidgets: false - }); - hostScope.search('host'); - };*/ - - hostScope.toggleHostEnabled = function (host_id, external_source) { - ToggleHostEnabled({ - parent_scope: $scope, - host_scope: hostScope, - host_id: host_id, - external_source: external_source - }); - }; - - hostScope.showJobSummary = function (job_id) { - ShowJobSummary({ - job_id: job_id - }); - }; - - $scope.showGroupHelp = function (params) { - var opts = { - defn: InventoryGroupsHelp - }; - if (params) { - opts.autoShow = params.autoShow || false; - } - HelpDialog(opts); - } -; - $scope.showHosts = function (group_id, show_failures) { - // Clicked on group - if (group_id !== null) { - Wait('start'); - hostScope.show_failures = show_failures; - $scope.groupSelect(group_id); - hostScope.hosts = []; - $scope.show_failures = show_failures; // turn on failed hosts - // filter in hosts view - } else { - Wait('stop'); - } - }; - - if ($scope.removeGroupDeleteCompleted) { - $scope.removeGroupDeleteCompleted(); - } - $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', - function() { - $scope.refreshGroups(); - } - ); -} - - -InventoriesManage.$inject = ['$log', '$scope', '$rootScope', '$location', - '$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait', - 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', - 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', - 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', - 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', - 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', - 'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary', - 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy', - 'HostsCopy', '$stateParams' -]; diff --git a/awx/ui/client/src/inventory/inventory-add.controller.js b/awx/ui/client/src/inventory/inventory-add.controller.js new file mode 100644 index 0000000000..a0903f4fab --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-add.controller.js @@ -0,0 +1,98 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesAdd($scope, $rootScope, $compile, $location, $log, + $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, + ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, + PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, + $state) { + + ClearScope(); + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoryForm(), + generator = GenerateForm; + + form.well = true; + form.formLabelSize = null; + form.formFieldSize = null; + + generator.inject(form, { mode: 'add', related: false, scope: $scope }); + + generator.reset(); + + $scope.parseType = 'yaml'; + ParseTypeChange({ + scope: $scope, + variable: 'variables', + parse_variable: 'parseType', + field_id: 'inventory_variables' + }); + + LookUpInit({ + scope: $scope, + form: form, + current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null, + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + + // Save + $scope.formSave = function () { + generator.clearApiErrors(); + Wait('start'); + try { + var fld, json_data, data; + + json_data = ToJSON($scope.parseType, $scope.variables, true); + + data = {}; + for (fld in form.fields) { + if (form.fields[fld].realName) { + data[form.fields[fld].realName] = $scope[fld]; + } else { + data[fld] = $scope[fld]; + } + } + + Rest.setUrl(defaultUrl); + Rest.post(data) + .success(function (data) { + var inventory_id = data.id; + Wait('stop'); + $location.path('/inventories/' + inventory_id + '/manage'); + }) + .error(function (data, status) { + ProcessErrors( $scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new inventory. Post returned status: ' + status }); + }); + } catch (err) { + Wait('stop'); + Alert("Error", "Error parsing inventory variables. Parser returned: " + err); + } + + }; + + $scope.formCancel = function () { + $state.transitionTo('inventories'); + }; +} + +export default['$scope', '$rootScope', '$compile', '$location', + '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', + 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', + 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', + 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd] diff --git a/awx/ui/client/src/inventory/inventory-edit.controller.js b/awx/ui/client/src/inventory/inventory-edit.controller.js new file mode 100644 index 0000000000..dd45a566ac --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-edit.controller.js @@ -0,0 +1,332 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesEdit($scope, $rootScope, $compile, $location, + $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, + ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, + PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, + ParseVariableString, RelatedSearchInit, RelatedPaginateInit, + Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) { + + ClearScope(); + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoryForm(), + generator = GenerateForm, + inventory_id = $stateParams.inventory_id, + master = {}, + fld, json_data, data, + relatedSets = {}; + + form.well = true; + form.formLabelSize = null; + form.formFieldSize = null; + $scope.inventory_id = inventory_id; + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); + + generator.reset(); + + + // After the project is loaded, retrieve each related set + if ($scope.inventoryLoadedRemove) { + $scope.inventoryLoadedRemove(); + } + $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () { + var set; + for (set in relatedSets) { + $scope.search(relatedSets[set].iterator); + } + }); + + Wait('start'); + Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); + Rest.get() + .success(function (data) { + var fld; + for (fld in form.fields) { + if (fld === 'variables') { + $scope.variables = ParseVariableString(data.variables); + master.variables = $scope.variables; + } else if (fld === 'inventory_name') { + $scope[fld] = data.name; + master[fld] = $scope[fld]; + } else if (fld === 'inventory_description') { + $scope[fld] = data.description; + master[fld] = $scope[fld]; + } else if (data[fld]) { + $scope[fld] = data[fld]; + master[fld] = $scope[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]; + } + } + relatedSets = form.relatedSets(data.related); + + // Initialize related search functions. Doing it here to make sure relatedSets object is populated. + RelatedSearchInit({ + scope: $scope, + form: form, + relatedSets: relatedSets + }); + RelatedPaginateInit({ + scope: $scope, + relatedSets: relatedSets + }); + + Wait('stop'); + $scope.parseType = 'yaml'; + ParseTypeChange({ + scope: $scope, + variable: 'variables', + parse_variable: 'parseType', + field_id: 'inventory_variables' + }); + LookUpInit({ + scope: $scope, + form: form, + current_item: $scope.organization, + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + $scope.$emit('inventoryLoaded'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status }); + }); + // Save + $scope.formSave = function () { + Wait('start'); + + // Make sure we have valid variable data + json_data = ToJSON($scope.parseType, $scope.variables); + + data = {}; + for (fld in form.fields) { + if (form.fields[fld].realName) { + data[form.fields[fld].realName] = $scope[fld]; + } else { + data[fld] = $scope[fld]; + } + } + + Rest.setUrl(defaultUrl + inventory_id + '/'); + Rest.put(data) + .success(function () { + Wait('stop'); + $location.path('/inventories/'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to update inventory. PUT returned status: ' + status }); + }); + }; + + $scope.manageInventory = function(){ + $location.path($location.path() + '/manage'); + }; + + $scope.formCancel = function () { + $state.transitionTo('inventories'); + }; + + $scope.addScanJob = function(){ + $location.path($location.path()+'/job_templates/add'); + }; + + $scope.launchScanJob = function(){ + PlaybookRun({ scope: $scope, id: this.scan_job_template.id }); + }; + + $scope.scheduleScanJob = function(){ + $location.path('/job_templates/'+this.scan_job_template.id+'/schedules'); + }; + + $scope.editScanJob = function(){ + $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); + }; + + $scope.copyScanJobTemplate = function(){ + var id = this.scan_job_template.id, + name = this.scan_job_template.name, + element, + buttons = [{ + "label": "Cancel", + "onClick": function() { + $(this).dialog('close'); + }, + "icon": "fa-times", + "class": "btn btn-default", + "id": "copy-close-button" + },{ + "label": "Copy", + "onClick": function() { + copyAction(); + }, + "icon": "fa-copy", + "class": "btn btn-primary", + "id": "job-copy-button" + }], + copyAction = function () { + // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id + Wait('start'); + var url = GetBasePath('job_templates')+id; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + data.name = $scope.new_copy_name; + delete data.id; + $scope.$emit('GoToCopy', data); + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }; + + + CreateDialog({ + id: 'copy-job-modal' , + title: "Copy", + scope: $scope, + buttons: buttons, + width: 500, + height: 300, + minWidth: 200, + callback: 'CopyDialogReady' + }); + + $('#job_name').text(name); + $('#copy-job-modal').show(); + + + if ($scope.removeCopyDialogReady) { + $scope.removeCopyDialogReady(); + } + $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() { + //clear any old remaining text + $scope.new_copy_name = "" ; + $scope.copy_form.$setPristine(); + $('#copy-job-modal').dialog('open'); + $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid"); + element = angular.element(document.getElementById('job-copy-button')); + $compile(element)($scope); + + }); + + if ($scope.removeGoToCopy) { + $scope.removeGoToCopy(); + } + $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) { + var url = GetBasePath('job_templates'), + old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ; + Rest.setUrl(url); + Rest.post(data) + .success(function (data) { + if(data.survey_enabled===true){ + $scope.$emit("CopySurvey", data, old_survey_url); + } + else { + $('#copy-job-modal').dialog('close'); + Wait('stop'); + $location.path($location.path() + '/job_templates/' + data.id); + } + + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }); + + if ($scope.removeCopySurvey) { + $scope.removeCopySurvey(); + } + $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) { + // var url = data.related.survey_spec; + Rest.setUrl(old_url); + Rest.get() + .success(function (survey_data) { + + Rest.setUrl(new_data.related.survey_spec); + Rest.post(survey_data) + .success(function () { + $('#copy-job-modal').dialog('close'); + Wait('stop'); + $location.path($location.path() + '/job_templates/' + new_data.id); + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status }); + }); + + + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status }); + }); + + }); + + }; + + $scope.deleteScanJob = function () { + var id = this.scan_job_template.id , + action = function () { + $('#prompt-modal').modal('hide'); + Wait('start'); + deleteJobTemplate(id) + .success(function () { + $('#prompt-modal').modal('hide'); + $scope.search(form.related.scan_job_templates.iterator); + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'DELETE returned status: ' + status }); + }); + }; + + Prompt({ + hdr: 'Delete', + body: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
', + action: action, + actionText: 'DELETE' + }); + + }; + +} + +export default ['$scope', '$rootScope', '$compile', '$location', + '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', + 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', + 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', + 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', + 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', + 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state', + InventoriesEdit, +]; diff --git a/awx/ui/client/src/inventory/inventory-list.controller.js b/awx/ui/client/src/inventory/inventory-list.controller.js new file mode 100644 index 0000000000..98f02df918 --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-list.controller.js @@ -0,0 +1,371 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesList($scope, $rootScope, $location, $log, + $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, + generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, + ClearScope, ProcessErrors, GetBasePath, Wait, + EditInventoryProperties, Find, Empty, $state) { + + var list = InventoryList, + defaultUrl = GetBasePath('inventory'), + view = generateList, + paths = $location.path().replace(/^\//, '').split('/'), + mode = (paths[0] === 'inventories') ? 'edit' : 'select'; + + function ellipsis(a) { + if (a.length > 20) { + return a.substr(0,20) + '...'; + } + return a; + } + + function attachElem(event, html, title) { + var elem = $(event.target).parent(); + try { + elem.tooltip('hide'); + elem.popover('destroy'); + } + catch(err) { + //ignore + } + $('.popover').each(function() { + // remove lingering popover
. Seems to be a bug in TB3 RC1 + $(this).remove(); + }); + $('.tooltip').each( function() { + // close any lingering tool tipss + $(this).hide(); + }); + elem.attr({ + "aw-pop-over": html, + "data-popover-title": title, + "data-placement": "right" }); + $compile(elem)($scope); + elem.on('shown.bs.popover', function() { + $('.popover').each(function() { + $compile($(this))($scope); //make nested directives work! + }); + $('.popover-content, .popover-title').click(function() { + elem.popover('hide'); + }); + }); + elem.popover('show'); + } + + view.inject(InventoryList, { mode: mode, scope: $scope }); + $rootScope.flashMessage = null; + + SearchInit({ + scope: $scope, + set: 'inventories', + list: list, + url: defaultUrl + }); + + PaginateInit({ + scope: $scope, + list: list, + url: defaultUrl + }); + + if ($stateParams.name) { + $scope[InventoryList.iterator + 'InputDisable'] = false; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name; + $scope[InventoryList.iterator + 'SearchField'] = 'name'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label; + $scope[InventoryList.iterator + 'SearchSelectValue'] = null; + } + + if ($stateParams.has_active_failures) { + $scope[InventoryList.iterator + 'InputDisable'] = true; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures; + $scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label; + $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? { + value: 1 + } : { + value: 0 + }; + } + + if ($stateParams.has_inventory_sources) { + $scope[InventoryList.iterator + 'InputDisable'] = true; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources; + $scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label; + $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? { + value: 1 + } : { + value: 0 + }; + } + + if ($stateParams.inventory_sources_with_failures) { + // pass a value of true, however this field actually contains an integer value + $scope[InventoryList.iterator + 'InputDisable'] = true; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures; + $scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label; + $scope[InventoryList.iterator + 'SearchType'] = 'gtzero'; + } + + $scope.search(list.iterator); + + if ($scope.removePostRefresh) { + $scope.removePostRefresh(); + } + $scope.removePostRefresh = $scope.$on('PostRefresh', function () { + //If we got here by deleting an inventory, stop the spinner and cleanup events + Wait('stop'); + try { + $('#prompt-modal').modal('hide'); + } + catch(e) { + // ignore + } + $scope.inventories.forEach(function(inventory, idx) { + $scope.inventories[idx].launch_class = ""; + if (inventory.has_inventory_sources) { + if (inventory.inventory_sources_with_failures > 0) { + $scope.inventories[idx].syncStatus = 'error'; + $scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details'; + } + else { + $scope.inventories[idx].syncStatus = 'successful'; + $scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.'; + } + } + else { + $scope.inventories[idx].syncStatus = 'na'; + $scope.inventories[idx].syncTip = 'Not configured for inventory sync.'; + $scope.inventories[idx].launch_class = "btn-disabled"; + } + if (inventory.has_active_failures) { + $scope.inventories[idx].hostsStatus = 'error'; + $scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.'; + } + else if (inventory.total_hosts) { + $scope.inventories[idx].hostsStatus = 'successful'; + $scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.'; + } + else { + $scope.inventories[idx].hostsStatus = 'none'; + $scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.'; + } + }); + }); + + if ($scope.removeRefreshInventories) { + $scope.removeRefreshInventories(); + } + $scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () { + // Reflect changes after inventory properties edit completes + $scope.search(list.iterator); + }); + + if ($scope.removeHostSummaryReady) { + $scope.removeHostSummaryReady(); + } + $scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) { + + var html, title = "Recent Jobs"; + Wait('stop'); + if (data.count > 0) { + html = "\n"; + html += "\n"; + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + html += "\n"; + html += "\n"; + + data.results.forEach(function(row) { + html += "\n"; + html += "\n"; + html += ""; + html += ""; + html += "\n"; + }); + html += "\n"; + html += "
StatusFinishedName
" + ($filter('longDate')(row.finished)).replace(/ /,'
') + "
" + ellipsis(row.name) + "
\n"; + } + else { + html = "

No recent job data available for this inventory.

\n"; + } + attachElem(event, html, title); + }); + + if ($scope.removeGroupSummaryReady) { + $scope.removeGroupSummaryReady(); + } + $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) { + var html, title; + + Wait('stop'); + + // Build the html for our popover + html = "\n"; + html += "\n"; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + html += "\n"; + data.results.forEach( function(row) { + if (row.related.last_update) { + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + } + else { + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + } + }); + html += "\n"; + html += "
StatusLast SyncGroup
" + ($filter('longDate')(row.last_updated)).replace(/ /,'
') + "
" + ellipsis(row.summary_fields.group.name) + "
NA" + ellipsis(row.summary_fields.group.name) + "
\n"; + title = "Sync Status"; + attachElem(event, html, title); + }); + + $scope.showGroupSummary = function(event, id) { + var inventory; + if (!Empty(id)) { + inventory = Find({ list: $scope.inventories, key: 'id', val: id }); + if (inventory.syncStatus !== 'na') { + Wait('start'); + Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); + Rest.get() + .success(function(data) { + $scope.$emit('GroupSummaryReady', event, inventory, data); + }) + .error(function(data, status) { + ProcessErrors( $scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status + }); + }); + } + } + }; + + $scope.showHostSummary = function(event, id) { + var url, inventory; + if (!Empty(id)) { + inventory = Find({ list: $scope.inventories, key: 'id', val: id }); + if (inventory.total_hosts > 0) { + Wait('start'); + url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed="; + url += (inventory.has_active_failures) ? 'true' : "false"; + url += "&order_by=-finished&page_size=5"; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + $scope.$emit('HostSummaryReady', event, data); + }) + .error( function(data, status) { + ProcessErrors( $scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status + }); + }); + } + } + }; + + $scope.viewJob = function(url) { + + // Pull the id out of the URL + var id = url.replace(/^\//, '').split('/')[3]; + + $state.go('inventorySyncStdout', {id: id}); + + }; + + $scope.editInventoryProperties = function (inventory_id) { + EditInventoryProperties({ scope: $scope, inventory_id: inventory_id }); + }; + + $scope.addInventory = function () { + $state.go('inventories.add'); + }; + + $scope.editInventory = function (id) { + $state.go('inventories.edit', {inventory_id: id}); + }; + + $scope.manageInventory = function(id){ + $location.path($location.path() + '/' + id + '/manage'); + }; + + $scope.deleteInventory = function (id, name) { + + var action = function () { + var url = defaultUrl + id + '/'; + Wait('start'); + $('#prompt-modal').modal('hide'); + 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 + }); + }); + }; + + Prompt({ + hdr: 'Delete', + body: '
Are you sure you want to delete the inventory below?
' + $filter('sanitize')(name) + '
', + action: action, + actionText: 'DELETE' + }); + }; + + $scope.lookupOrganization = function (organization_id) { + Rest.setUrl(GetBasePath('organizations') + organization_id + '/'); + Rest.get() + .success(function (data) { + return data.name; + }); + }; + + + // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status + $scope.viewJobs = function (id) { + $location.url('/jobs/?inventory__int=' + id); + }; + + $scope.viewFailedJobs = function (id) { + $location.url('/jobs/?inventory__int=' + id + '&status=failed'); + }; +} + +export default ['$scope', '$rootScope', '$location', '$log', + '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', + 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', + 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', + 'EditInventoryProperties', 'Find', 'Empty', '$state', InventoriesList]; diff --git a/awx/ui/client/src/inventory/inventory-manage.controller.js b/awx/ui/client/src/inventory/inventory-manage.controller.js new file mode 100644 index 0000000000..651547894e --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-manage.controller.js @@ -0,0 +1,532 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesManage($log, $scope, $rootScope, $location, + $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, + GetBasePath, ProcessErrors, InventoryGroups, + InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, + GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate, + ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, + EditInventoryProperties, ToggleHostEnabled, ShowJobSummary, + InventoryGroupsHelp, HelpDialog, + GroupsCopy, HostsCopy, $stateParams) { + + var PreviousSearchParams, + url, + hostScope = $scope.$new(); + + ClearScope(); + + // TODO: only display adhoc button if the user has permission to use it. + // TODO: figure out how to get the action-list partial to update so that + // the tooltip can be changed based off things being selected or not. + $scope.adhocButtonTipContents = "Launch adhoc command for the inventory"; + + // watcher for the group list checkbox changes + $scope.$on('multiSelectList.selectionChanged', function(e, selection) { + if (selection.length > 0) { + $scope.groupsSelected = true; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "selected groups and hosts."; + } else { + $scope.groupsSelected = false; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "inventory."; + } + $scope.groupsSelectedItems = selection.selectedItems; + }); + + // watcher for the host list checkbox changes + hostScope.$on('multiSelectList.selectionChanged', function(e, selection) { + // you need this so that the event doesn't bubble to the watcher above + // for the host list + e.stopPropagation(); + if (selection.length === 0) { + $scope.hostsSelected = false; + } else if (selection.length === 1) { + $scope.systemTrackingTooltip = "Compare host over time"; + $scope.hostsSelected = true; + $scope.systemTrackingDisabled = false; + } else if (selection.length === 2) { + $scope.systemTrackingTooltip = "Compare hosts against each other"; + $scope.hostsSelected = true; + $scope.systemTrackingDisabled = false; + } else { + $scope.hostsSelected = true; + $scope.systemTrackingDisabled = true; + } + $scope.hostsSelectedItems = selection.selectedItems; + }); + + $scope.systemTracking = function() { + var hostIds = _.map($scope.hostsSelectedItems, function(x){ + return x.id; + }); + $state.transitionTo('systemTracking', + { inventory: $scope.inventory, + inventoryId: $scope.inventory.id, + hosts: $scope.hostsSelectedItems, + hostIds: hostIds + }); + }; + + // populates host patterns based on selected hosts/groups + $scope.populateAdhocForm = function() { + var host_patterns = "all"; + if ($scope.hostsSelected || $scope.groupsSelected) { + var allSelectedItems = []; + if ($scope.groupsSelectedItems) { + allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems); + } + if ($scope.hostsSelectedItems) { + allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems); + } + if (allSelectedItems) { + host_patterns = _.pluck(allSelectedItems, "name").join(":"); + } + } + $rootScope.hostPatterns = host_patterns; + $state.go('inventoryManage.adhoc'); + }; + + $scope.refreshHostsOnGroupRefresh = false; + $scope.selected_group_id = null; + + Wait('start'); + + + if ($scope.removeHostReloadComplete) { + $scope.removeHostReloadComplete(); + } + $scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() { + if ($scope.initial_height) { + var host_height = $('#hosts-container .well').height(), + group_height = $('#group-list-container .well').height(), + new_height; + + if (host_height > group_height) { + new_height = host_height - (host_height - group_height); + } + else if (host_height < group_height) { + new_height = host_height + (group_height - host_height); + } + if (new_height) { + $('#hosts-container .well').height(new_height); + } + $scope.initial_height = null; + } + }); + + if ($scope.removeRowCountReady) { + $scope.removeRowCountReady(); + } + $scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) { + // Add hosts view + $scope.show_failures = false; + InjectHosts({ + group_scope: $scope, + host_scope: hostScope, + inventory_id: $scope.inventory.id, + tree_id: null, + group_id: null, + pageSize: rows + }); + + SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups }); + PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows }); + $scope.search(InventoryGroups.iterator, null, true); + }); + + if ($scope.removeInventoryLoaded) { + $scope.removeInventoryLoaded(); + } + $scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() { + var rows; + + // Add groups view + generateList.inject(InventoryGroups, { + mode: 'edit', + id: 'group-list-container', + searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12', + scope: $scope + }); + + rows = 20; + hostScope.host_page_size = rows; + $scope.group_page_size = rows; + + $scope.show_failures = false; + InjectHosts({ + group_scope: $scope, + host_scope: hostScope, + inventory_id: $scope.inventory.id, + tree_id: null, + group_id: null, + pageSize: rows + }); + + // Load data + SearchInit({ + scope: $scope, + set: 'groups', + list: InventoryGroups, + url: $scope.inventory.related.root_groups + }); + + PaginateInit({ + scope: $scope, + list: InventoryGroups , + url: $scope.inventory.related.root_groups, + pageSize: rows + }); + + $scope.search(InventoryGroups.iterator, null, true); + + $scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates + }); + + if ($scope.removePostRefresh) { + $scope.removePostRefresh(); + } + $scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) { + if (set === 'groups') { + $scope.groups.forEach( function(group, idx) { + var stat, hosts_status; + stat = GetSyncStatusMsg({ + status: group.summary_fields.inventory_source.status, + has_inventory_sources: group.has_inventory_sources, + source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null ) + }); // from helpers/Groups.js + $scope.groups[idx].status_class = stat['class']; + $scope.groups[idx].status_tooltip = stat.tooltip; + $scope.groups[idx].launch_tooltip = stat.launch_tip; + $scope.groups[idx].launch_class = stat.launch_class; + hosts_status = GetHostsStatusMsg({ + active_failures: group.hosts_with_active_failures, + total_hosts: group.total_hosts, + inventory_id: $scope.inventory.id, + group_id: group.id + }); // from helpers/Groups.js + $scope.groups[idx].hosts_status_tip = hosts_status.tooltip; + $scope.groups[idx].show_failures = hosts_status.failures; + $scope.groups[idx].hosts_status_class = hosts_status['class']; + + $scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null; + $scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null; + + }); + if ($scope.refreshHostsOnGroupRefresh) { + $scope.refreshHostsOnGroupRefresh = false; + HostsReload({ + scope: hostScope, + group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id, + pageSize: hostScope.host_page_size + }); + } + else { + Wait('stop'); + } + } + }); + + // Load Inventory + url = GetBasePath('inventory') + $stateParams.inventory_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + $scope.inventory = data; + $scope.$emit('InventoryLoaded'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id + + ' GET returned status: ' + status }); + }); + + // start watching for real-time updates + if ($rootScope.removeWatchUpdateStatus) { + $rootScope.removeWatchUpdateStatus(); + } + $rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) { + var stat, group; + if (data.group_id) { + group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); + if (data.status === "failed" || data.status === "successful") { + if (data.group_id === $scope.selected_group_id || group) { + // job completed, fefresh all groups + $log.debug('Update completed. Refreshing the tree.'); + $scope.refreshGroups(); + } + } + else if (group) { + // incremental update, just update + $log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status); + stat = GetSyncStatusMsg({ + status: data.status, + has_inventory_sources: group.has_inventory_sources, + source: group.source + }); + $log.debug('changing tooltip to: ' + stat.tooltip); + group.status = data.status; + group.status_class = stat['class']; + group.status_tooltip = stat.tooltip; + group.launch_tooltip = stat.launch_tip; + group.launch_class = stat.launch_class; + } + } + }); + + // Load group on selection + function loadGroups(url) { + SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url }); + PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size }); + $scope.search(InventoryGroups.iterator, null, true, false, true); + } + + $scope.refreshHosts = function() { + HostsReload({ + scope: hostScope, + group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id, + pageSize: hostScope.host_page_size + }); + }; + + $scope.refreshGroups = function() { + $scope.refreshHostsOnGroupRefresh = true; + $scope.search(InventoryGroups.iterator, null, true, false, true); + }; + + $scope.restoreSearch = function() { + // Restore search params and related stuff, plus refresh + // groups and hosts lists + SearchInit({ + scope: $scope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + }); + $scope.refreshHostsOnGroupRefresh = true; + $scope.search(InventoryGroups.iterator, null, true, false, true); + }; + + $scope.groupSelect = function(id) { + var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id }); + if($state.params.groups){ + groups.push($state.params.groups); + } + groups.push(group.id); + groups = groups.join(); + $state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false }); + loadGroups(group.related.children, group.id); + }; + + $scope.createGroup = function () { + PreviousSearchParams = Store('group_current_search_params'); + GroupsEdit({ + scope: $scope, + inventory_id: $scope.inventory.id, + group_id: $scope.selected_group_id, + mode: 'add' + }); + }; + + $scope.editGroup = function (id) { + PreviousSearchParams = Store('group_current_search_params'); + GroupsEdit({ + scope: $scope, + inventory_id: $scope.inventory.id, + group_id: id, + mode: 'edit' + }); + }; + + // Launch inventory sync + $scope.updateGroup = function (id) { + var group = Find({ list: $scope.groups, key: 'id', val: id }); + if (group) { + if (Empty(group.source)) { + // if no source, do nothing. + } else if (group.status === 'updating') { + Alert('Update in Progress', 'The inventory update process is currently running for group ' + + group.name + ' Click the button to monitor the status.', 'alert-info', null, null, null, null, true); + } else { + Wait('start'); + Rest.setUrl(group.related.inventory_source); + Rest.get() + .success(function (data) { + InventoryUpdate({ + scope: $scope, + url: data.related.update, + group_name: data.summary_fields.group.name, + group_source: data.source, + group_id: group.id, + }); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + + group.related.inventory_source + ' GET returned status: ' + status }); + }); + } + } + }; + + $scope.cancelUpdate = function (id) { + GroupsCancelUpdate({ scope: $scope, id: id }); + }; + + $scope.viewUpdateStatus = function (id) { + ViewUpdateStatus({ + scope: $scope, + group_id: id + }); + }; + + $scope.copyGroup = function(id) { + PreviousSearchParams = Store('group_current_search_params'); + GroupsCopy({ + scope: $scope, + group_id: id + }); + }; + + $scope.deleteGroup = function (id) { + GroupsDelete({ + scope: $scope, + group_id: id, + inventory_id: $scope.inventory.id + }); + }; + + $scope.editInventoryProperties = function () { + // EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id }); + $location.path('/inventories/' + $scope.inventory.id + '/'); + }; + + hostScope.createHost = function () { + HostsEdit({ + host_scope: hostScope, + group_scope: $scope, + mode: 'add', + host_id: null, + selected_group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id + }); + }; + + hostScope.editHost = function (host_id) { + HostsEdit({ + host_scope: hostScope, + group_scope: $scope, + mode: 'edit', + host_id: host_id, + inventory_id: $scope.inventory.id + }); + }; + + hostScope.deleteHost = function (host_id, host_name) { + HostsDelete({ + parent_scope: $scope, + host_scope: hostScope, + host_id: host_id, + host_name: host_name + }); + }; + + hostScope.copyHost = function(id) { + PreviousSearchParams = Store('group_current_search_params'); + HostsCopy({ + group_scope: $scope, + host_scope: hostScope, + host_id: id + }); + }; + + /*hostScope.restoreSearch = function() { + SearchInit({ + scope: hostScope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + }); + hostScope.search('host'); + };*/ + + hostScope.toggleHostEnabled = function (host_id, external_source) { + ToggleHostEnabled({ + parent_scope: $scope, + host_scope: hostScope, + host_id: host_id, + external_source: external_source + }); + }; + + hostScope.showJobSummary = function (job_id) { + ShowJobSummary({ + job_id: job_id + }); + }; + + $scope.showGroupHelp = function (params) { + var opts = { + defn: InventoryGroupsHelp + }; + if (params) { + opts.autoShow = params.autoShow || false; + } + HelpDialog(opts); + } +; + $scope.showHosts = function (group_id, show_failures) { + // Clicked on group + if (group_id !== null) { + Wait('start'); + hostScope.show_failures = show_failures; + $scope.groupSelect(group_id); + hostScope.hosts = []; + $scope.show_failures = show_failures; // turn on failed hosts + // filter in hosts view + } else { + Wait('stop'); + } + }; + + if ($scope.removeGroupDeleteCompleted) { + $scope.removeGroupDeleteCompleted(); + } + $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', + function() { + $scope.refreshGroups(); + } + ); +} + +export default [ + '$log', '$scope', '$rootScope', '$location', + '$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait', + 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', + 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', + 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', + 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', + 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', + 'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary', + 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy', + 'HostsCopy', '$stateParams', InventoriesManage, +]; diff --git a/awx/ui/client/src/inventory/main.js b/awx/ui/client/src/inventory/main.js new file mode 100644 index 0000000000..0689a2d726 --- /dev/null +++ b/awx/ui/client/src/inventory/main.js @@ -0,0 +1,10 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +export default +angular.module('inventory', [ +]) From b0b416341d314c4434cc0dc86a046fac4b2a45b4 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Wed, 16 Mar 2016 09:23:05 -0400 Subject: [PATCH 02/33] Further modularization. --- awx/ui/client/src/app.js | 43 ++++++++++++++++++- .../add}/inventory-add.controller.js | 2 +- .../inventories/add/inventory-add.route.js | 24 +++++++++++ awx/ui/client/src/inventories/add/main.js | 14 ++++++ .../edit}/inventory-edit.controller.js | 2 +- .../inventories/edit/inventory-edit.route.js | 26 +++++++++++ awx/ui/client/src/inventories/edit/main.js | 14 ++++++ .../inventories.partial.html} | 0 .../list}/inventory-list.controller.js | 2 +- .../inventories/list/inventory-list.route.js | 27 ++++++++++++ awx/ui/client/src/inventories/list/main.js | 14 ++++++ awx/ui/client/src/inventories/main.js | 18 ++++++++ .../manage}/inventory-manage.controller.js | 2 +- .../manage/inventory-manage.partial.html} | 0 .../manage/inventory-manage.route.js | 28 ++++++++++++ awx/ui/client/src/inventories/manage/main.js | 14 ++++++ awx/ui/client/src/inventory/main.js | 10 ----- 17 files changed, 225 insertions(+), 15 deletions(-) rename awx/ui/client/src/{inventory => inventories/add}/inventory-add.controller.js (98%) create mode 100644 awx/ui/client/src/inventories/add/inventory-add.route.js create mode 100644 awx/ui/client/src/inventories/add/main.js rename awx/ui/client/src/{inventory => inventories/edit}/inventory-edit.controller.js (99%) create mode 100644 awx/ui/client/src/inventories/edit/inventory-edit.route.js create mode 100644 awx/ui/client/src/inventories/edit/main.js rename awx/ui/client/src/{partials/inventories.html => inventories/inventories.partial.html} (100%) rename awx/ui/client/src/{inventory => inventories/list}/inventory-list.controller.js (99%) create mode 100644 awx/ui/client/src/inventories/list/inventory-list.route.js create mode 100644 awx/ui/client/src/inventories/list/main.js create mode 100644 awx/ui/client/src/inventories/main.js rename awx/ui/client/src/{inventory => inventories/manage}/inventory-manage.controller.js (99%) rename awx/ui/client/src/{partials/inventory-manage.html => inventories/manage/inventory-manage.partial.html} (100%) create mode 100644 awx/ui/client/src/inventories/manage/inventory-manage.route.js create mode 100644 awx/ui/client/src/inventories/manage/main.js delete mode 100644 awx/ui/client/src/inventory/main.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 2a3367805c..55753b9f40 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -26,7 +26,7 @@ import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Cr import {JobsListController} from './controllers/Jobs'; import {PortalController} from './controllers/Portal'; import systemTracking from './system-tracking/main'; -import inventory from './inventory/main'; +import inventories from './inventories/main'; import inventoryScripts from './inventory-scripts/main'; import organizations from './organizations/main'; import permissions from './permissions/main'; @@ -91,6 +91,7 @@ var tower = angular.module('Tower', [ RestServices.name, browserData.name, systemTracking.name, + inventories.name, inventoryScripts.name, organizations.name, permissions.name, @@ -372,6 +373,7 @@ var tower = angular.module('Tower', [ } }). +<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35 state('inventories', { url: '/inventories', templateUrl: urlPrefix + 'partials/inventories.html', @@ -382,6 +384,22 @@ var tower = angular.module('Tower', [ }, ncyBreadcrumb: { label: "INVENTORIES" +======= + state('organizations', { + url: '/organizations', + templateUrl: urlPrefix + 'partials/organizations.html', + controller: OrganizationsList, + data: { + activityStream: true, + activityStreamTarget: 'organization' + }, + ncyBreadcrumb: { + parent: function($scope) { + $scope.$parent.$emit("ReloadOrgListView"); + return "setup"; + }, + label: "ORGANIZATIONS" +>>>>>>> Further modularization. }, resolve: { features: ['FeaturesService', function(FeaturesService) { @@ -390,6 +408,7 @@ var tower = angular.module('Tower', [ } }). +<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35 state('inventories.add', { url: '/add', templateUrl: urlPrefix + 'partials/inventories.html', @@ -397,6 +416,15 @@ var tower = angular.module('Tower', [ ncyBreadcrumb: { parent: "inventories", label: "CREATE INVENTORY" +======= + state('organizations.add', { + url: '/add', + templateUrl: urlPrefix + 'partials/organizations.crud.html', + controller: OrganizationsAdd, + ncyBreadcrumb: { + parent: "organizations", + label: "CREATE ORGANIZATION" +>>>>>>> Further modularization. }, resolve: { features: ['FeaturesService', function(FeaturesService) { @@ -405,6 +433,7 @@ var tower = angular.module('Tower', [ } }). +<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35 state('inventories.edit', { url: '/:inventory_id', templateUrl: urlPrefix + 'partials/inventories.html', @@ -427,6 +456,18 @@ var tower = angular.module('Tower', [ activityStream: true, activityStreamTarget: 'inventory', activityStreamId: 'inventory_id' +======= + state('organizations.edit', { + url: '/:organization_id', + templateUrl: urlPrefix + 'partials/organizations.crud.html', + controller: OrganizationsEdit, + data: { + activityStreamId: 'organization_id' + }, + ncyBreadcrumb: { + parent: "organizations", + label: "{{name}}" +>>>>>>> Further modularization. }, resolve: { features: ['FeaturesService', function(FeaturesService) { diff --git a/awx/ui/client/src/inventory/inventory-add.controller.js b/awx/ui/client/src/inventories/add/inventory-add.controller.js similarity index 98% rename from awx/ui/client/src/inventory/inventory-add.controller.js rename to awx/ui/client/src/inventories/add/inventory-add.controller.js index a0903f4fab..befcc1d14e 100644 --- a/awx/ui/client/src/inventory/inventory-add.controller.js +++ b/awx/ui/client/src/inventories/add/inventory-add.controller.js @@ -10,7 +10,7 @@ * @description This controller's for the Inventory page */ -import '../job-templates/main'; +import '../../job-templates/main'; function InventoriesAdd($scope, $rootScope, $compile, $location, $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, diff --git a/awx/ui/client/src/inventories/add/inventory-add.route.js b/awx/ui/client/src/inventories/add/inventory-add.route.js new file mode 100644 index 0000000000..50ba5b26a6 --- /dev/null +++ b/awx/ui/client/src/inventories/add/inventory-add.route.js @@ -0,0 +1,24 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; +import InventoriesAdd from './inventory-add.controller'; + +export default { + name: 'inventories.add', + route: '/add', + templateUrl: templateUrl('inventories/inventories'), + controller: InventoriesAdd, + ncyBreadcrumb: { + parent: "inventories", + label: "CREATE INVENTORY" + }, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } +}; diff --git a/awx/ui/client/src/inventories/add/main.js b/awx/ui/client/src/inventories/add/main.js new file mode 100644 index 0000000000..e12ff940ac --- /dev/null +++ b/awx/ui/client/src/inventories/add/main.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './inventory-add.route'; +import controller from './inventory-add.controller'; + +export default + angular.module('inventoryAdd', []) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/inventory/inventory-edit.controller.js b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js similarity index 99% rename from awx/ui/client/src/inventory/inventory-edit.controller.js rename to awx/ui/client/src/inventories/edit/inventory-edit.controller.js index dd45a566ac..3876f67c20 100644 --- a/awx/ui/client/src/inventory/inventory-edit.controller.js +++ b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js @@ -10,7 +10,7 @@ * @description This controller's for the Inventory page */ -import '../job-templates/main'; +import '../../job-templates/main'; function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, diff --git a/awx/ui/client/src/inventories/edit/inventory-edit.route.js b/awx/ui/client/src/inventories/edit/inventory-edit.route.js new file mode 100644 index 0000000000..d721ba92a4 --- /dev/null +++ b/awx/ui/client/src/inventories/edit/inventory-edit.route.js @@ -0,0 +1,26 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; +import InventoriesEdit from './inventory-edit.controller'; + +export default { + name: 'inventories.edit', + route: '/:inventory_id', + templateUrl: templateUrl('inventories/inventories'), + controller: InventoriesEdit, + data: { + activityStreamId: 'inventory_id' + }, + ncyBreadcrumb: { + label: "INVENTORY EDIT" + }, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } +}; diff --git a/awx/ui/client/src/inventories/edit/main.js b/awx/ui/client/src/inventories/edit/main.js new file mode 100644 index 0000000000..28c99819b7 --- /dev/null +++ b/awx/ui/client/src/inventories/edit/main.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './inventory-edit.route'; +import controller from './inventory-edit.controller'; + +export default + angular.module('inventoryEdit', []) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/partials/inventories.html b/awx/ui/client/src/inventories/inventories.partial.html similarity index 100% rename from awx/ui/client/src/partials/inventories.html rename to awx/ui/client/src/inventories/inventories.partial.html diff --git a/awx/ui/client/src/inventory/inventory-list.controller.js b/awx/ui/client/src/inventories/list/inventory-list.controller.js similarity index 99% rename from awx/ui/client/src/inventory/inventory-list.controller.js rename to awx/ui/client/src/inventories/list/inventory-list.controller.js index 98f02df918..c535187925 100644 --- a/awx/ui/client/src/inventory/inventory-list.controller.js +++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js @@ -10,7 +10,7 @@ * @description This controller's for the Inventory page */ -import '../job-templates/main'; +import '../../job-templates/main'; function InventoriesList($scope, $rootScope, $location, $log, $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, diff --git a/awx/ui/client/src/inventories/list/inventory-list.route.js b/awx/ui/client/src/inventories/list/inventory-list.route.js new file mode 100644 index 0000000000..2804370249 --- /dev/null +++ b/awx/ui/client/src/inventories/list/inventory-list.route.js @@ -0,0 +1,27 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; +import InventoriesList from './inventory-list.controller'; + +export default { + name: 'inventories', + route: '/inventories', + templateUrl: templateUrl('inventories/inventories'), + controller: InventoriesList, + data: { + activityStream: true, + activityStreamTarget: 'inventory' + }, + ncyBreadcrumb: { + label: "INVENTORIES" + }, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } +}; diff --git a/awx/ui/client/src/inventories/list/main.js b/awx/ui/client/src/inventories/list/main.js new file mode 100644 index 0000000000..4d67816cd7 --- /dev/null +++ b/awx/ui/client/src/inventories/list/main.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './inventory-list.route'; +import controller from './inventory-list.controller'; + +export default + angular.module('inventoryList', []) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/inventories/main.js b/awx/ui/client/src/inventories/main.js new file mode 100644 index 0000000000..52f9986ef3 --- /dev/null +++ b/awx/ui/client/src/inventories/main.js @@ -0,0 +1,18 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import inventoryAdd from './add/main'; +import inventoryEdit from './edit/main'; +import inventoryList from './list/main'; +import inventoryManage from './manage/main'; + +export default +angular.module('inventory', [ + inventoryAdd.name, + inventoryEdit.name, + inventoryList.name, + inventoryManage.name, +]); diff --git a/awx/ui/client/src/inventory/inventory-manage.controller.js b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js similarity index 99% rename from awx/ui/client/src/inventory/inventory-manage.controller.js rename to awx/ui/client/src/inventories/manage/inventory-manage.controller.js index 651547894e..3b1d3b4625 100644 --- a/awx/ui/client/src/inventory/inventory-manage.controller.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js @@ -10,7 +10,7 @@ * @description This controller's for the Inventory page */ -import '../job-templates/main'; +import '../../job-templates/main'; function InventoriesManage($log, $scope, $rootScope, $location, $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, diff --git a/awx/ui/client/src/partials/inventory-manage.html b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html similarity index 100% rename from awx/ui/client/src/partials/inventory-manage.html rename to awx/ui/client/src/inventories/manage/inventory-manage.partial.html diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js new file mode 100644 index 0000000000..1cfc9176ac --- /dev/null +++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js @@ -0,0 +1,28 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../../shared/template-url/template-url.factory'; +import InventoriesManage from './inventory-manage.controller'; + +export default { + name: 'inventoryManage', + route: '/inventories/:inventory_id/manage', + templateUrl: templateUrl('inventories/manage/inventory-manage'), + controller: InventoriesManage, + data: { + activityStream: true, + activityStreamTarget: 'inventory', + activityStreamId: 'inventory_id' + }, + ncyBreadcrumb: { + label: "INVENTORY MANAGE" + }, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } +}; diff --git a/awx/ui/client/src/inventories/manage/main.js b/awx/ui/client/src/inventories/manage/main.js new file mode 100644 index 0000000000..7bd839ecc1 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/main.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './inventory-manage.route'; +import controller from './inventory-manage.controller'; + +export default + angular.module('inventoryManage', []) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/inventory/main.js b/awx/ui/client/src/inventory/main.js deleted file mode 100644 index 0689a2d726..0000000000 --- a/awx/ui/client/src/inventory/main.js +++ /dev/null @@ -1,10 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default -angular.module('inventory', [ -]) From 6ee654c879f85897899e9f5530a9992e1e1f7ef3 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Wed, 16 Mar 2016 11:35:23 -0400 Subject: [PATCH 03/33] Removed unused job templates import from some controllers --- awx/ui/client/src/inventories/list/inventory-list.controller.js | 2 -- .../src/inventories/manage/inventory-manage.controller.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/awx/ui/client/src/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories/list/inventory-list.controller.js index c535187925..a55529491c 100644 --- a/awx/ui/client/src/inventories/list/inventory-list.controller.js +++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js @@ -10,8 +10,6 @@ * @description This controller's for the Inventory page */ -import '../../job-templates/main'; - function InventoriesList($scope, $rootScope, $location, $log, $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.controller.js b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js index 3b1d3b4625..58c50dfbe2 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.controller.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js @@ -10,8 +10,6 @@ * @description This controller's for the Inventory page */ -import '../../job-templates/main'; - function InventoriesManage($log, $scope, $rootScope, $location, $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, GetBasePath, ProcessErrors, InventoryGroups, From d24ab43c9941223934438710eb447370b2d25433 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 14 Mar 2016 16:13:01 -0400 Subject: [PATCH 04/33] Set up inventory module and split apart controllers/inventory.js into more manageable chunks. --- awx/ui/client/src/app.js | 40 -- .../src/inventory/inventory-add.controller.js | 98 ++++ .../inventory/inventory-edit.controller.js | 332 +++++++++++ .../inventory/inventory-list.controller.js | 371 ++++++++++++ .../inventory/inventory-manage.controller.js | 532 ++++++++++++++++++ awx/ui/client/src/inventory/main.js | 10 + 6 files changed, 1343 insertions(+), 40 deletions(-) create mode 100644 awx/ui/client/src/inventory/inventory-add.controller.js create mode 100644 awx/ui/client/src/inventory/inventory-edit.controller.js create mode 100644 awx/ui/client/src/inventory/inventory-list.controller.js create mode 100644 awx/ui/client/src/inventory/inventory-manage.controller.js create mode 100644 awx/ui/client/src/inventory/main.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 55753b9f40..aabeec0f4c 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -373,7 +373,6 @@ var tower = angular.module('Tower', [ } }). -<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35 state('inventories', { url: '/inventories', templateUrl: urlPrefix + 'partials/inventories.html', @@ -384,22 +383,6 @@ var tower = angular.module('Tower', [ }, ncyBreadcrumb: { label: "INVENTORIES" -======= - state('organizations', { - url: '/organizations', - templateUrl: urlPrefix + 'partials/organizations.html', - controller: OrganizationsList, - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "setup"; - }, - label: "ORGANIZATIONS" ->>>>>>> Further modularization. }, resolve: { features: ['FeaturesService', function(FeaturesService) { @@ -408,7 +391,6 @@ var tower = angular.module('Tower', [ } }). -<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35 state('inventories.add', { url: '/add', templateUrl: urlPrefix + 'partials/inventories.html', @@ -416,15 +398,6 @@ var tower = angular.module('Tower', [ ncyBreadcrumb: { parent: "inventories", label: "CREATE INVENTORY" -======= - state('organizations.add', { - url: '/add', - templateUrl: urlPrefix + 'partials/organizations.crud.html', - controller: OrganizationsAdd, - ncyBreadcrumb: { - parent: "organizations", - label: "CREATE ORGANIZATION" ->>>>>>> Further modularization. }, resolve: { features: ['FeaturesService', function(FeaturesService) { @@ -433,7 +406,6 @@ var tower = angular.module('Tower', [ } }). -<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35 state('inventories.edit', { url: '/:inventory_id', templateUrl: urlPrefix + 'partials/inventories.html', @@ -456,18 +428,6 @@ var tower = angular.module('Tower', [ activityStream: true, activityStreamTarget: 'inventory', activityStreamId: 'inventory_id' -======= - state('organizations.edit', { - url: '/:organization_id', - templateUrl: urlPrefix + 'partials/organizations.crud.html', - controller: OrganizationsEdit, - data: { - activityStreamId: 'organization_id' - }, - ncyBreadcrumb: { - parent: "organizations", - label: "{{name}}" ->>>>>>> Further modularization. }, resolve: { features: ['FeaturesService', function(FeaturesService) { diff --git a/awx/ui/client/src/inventory/inventory-add.controller.js b/awx/ui/client/src/inventory/inventory-add.controller.js new file mode 100644 index 0000000000..a0903f4fab --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-add.controller.js @@ -0,0 +1,98 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesAdd($scope, $rootScope, $compile, $location, $log, + $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, + ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, + PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, + $state) { + + ClearScope(); + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoryForm(), + generator = GenerateForm; + + form.well = true; + form.formLabelSize = null; + form.formFieldSize = null; + + generator.inject(form, { mode: 'add', related: false, scope: $scope }); + + generator.reset(); + + $scope.parseType = 'yaml'; + ParseTypeChange({ + scope: $scope, + variable: 'variables', + parse_variable: 'parseType', + field_id: 'inventory_variables' + }); + + LookUpInit({ + scope: $scope, + form: form, + current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null, + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + + // Save + $scope.formSave = function () { + generator.clearApiErrors(); + Wait('start'); + try { + var fld, json_data, data; + + json_data = ToJSON($scope.parseType, $scope.variables, true); + + data = {}; + for (fld in form.fields) { + if (form.fields[fld].realName) { + data[form.fields[fld].realName] = $scope[fld]; + } else { + data[fld] = $scope[fld]; + } + } + + Rest.setUrl(defaultUrl); + Rest.post(data) + .success(function (data) { + var inventory_id = data.id; + Wait('stop'); + $location.path('/inventories/' + inventory_id + '/manage'); + }) + .error(function (data, status) { + ProcessErrors( $scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new inventory. Post returned status: ' + status }); + }); + } catch (err) { + Wait('stop'); + Alert("Error", "Error parsing inventory variables. Parser returned: " + err); + } + + }; + + $scope.formCancel = function () { + $state.transitionTo('inventories'); + }; +} + +export default['$scope', '$rootScope', '$compile', '$location', + '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', + 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', + 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', + 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd] diff --git a/awx/ui/client/src/inventory/inventory-edit.controller.js b/awx/ui/client/src/inventory/inventory-edit.controller.js new file mode 100644 index 0000000000..dd45a566ac --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-edit.controller.js @@ -0,0 +1,332 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesEdit($scope, $rootScope, $compile, $location, + $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, + ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, + PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, + ParseVariableString, RelatedSearchInit, RelatedPaginateInit, + Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) { + + ClearScope(); + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoryForm(), + generator = GenerateForm, + inventory_id = $stateParams.inventory_id, + master = {}, + fld, json_data, data, + relatedSets = {}; + + form.well = true; + form.formLabelSize = null; + form.formFieldSize = null; + $scope.inventory_id = inventory_id; + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); + + generator.reset(); + + + // After the project is loaded, retrieve each related set + if ($scope.inventoryLoadedRemove) { + $scope.inventoryLoadedRemove(); + } + $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () { + var set; + for (set in relatedSets) { + $scope.search(relatedSets[set].iterator); + } + }); + + Wait('start'); + Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); + Rest.get() + .success(function (data) { + var fld; + for (fld in form.fields) { + if (fld === 'variables') { + $scope.variables = ParseVariableString(data.variables); + master.variables = $scope.variables; + } else if (fld === 'inventory_name') { + $scope[fld] = data.name; + master[fld] = $scope[fld]; + } else if (fld === 'inventory_description') { + $scope[fld] = data.description; + master[fld] = $scope[fld]; + } else if (data[fld]) { + $scope[fld] = data[fld]; + master[fld] = $scope[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]; + } + } + relatedSets = form.relatedSets(data.related); + + // Initialize related search functions. Doing it here to make sure relatedSets object is populated. + RelatedSearchInit({ + scope: $scope, + form: form, + relatedSets: relatedSets + }); + RelatedPaginateInit({ + scope: $scope, + relatedSets: relatedSets + }); + + Wait('stop'); + $scope.parseType = 'yaml'; + ParseTypeChange({ + scope: $scope, + variable: 'variables', + parse_variable: 'parseType', + field_id: 'inventory_variables' + }); + LookUpInit({ + scope: $scope, + form: form, + current_item: $scope.organization, + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + $scope.$emit('inventoryLoaded'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status }); + }); + // Save + $scope.formSave = function () { + Wait('start'); + + // Make sure we have valid variable data + json_data = ToJSON($scope.parseType, $scope.variables); + + data = {}; + for (fld in form.fields) { + if (form.fields[fld].realName) { + data[form.fields[fld].realName] = $scope[fld]; + } else { + data[fld] = $scope[fld]; + } + } + + Rest.setUrl(defaultUrl + inventory_id + '/'); + Rest.put(data) + .success(function () { + Wait('stop'); + $location.path('/inventories/'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to update inventory. PUT returned status: ' + status }); + }); + }; + + $scope.manageInventory = function(){ + $location.path($location.path() + '/manage'); + }; + + $scope.formCancel = function () { + $state.transitionTo('inventories'); + }; + + $scope.addScanJob = function(){ + $location.path($location.path()+'/job_templates/add'); + }; + + $scope.launchScanJob = function(){ + PlaybookRun({ scope: $scope, id: this.scan_job_template.id }); + }; + + $scope.scheduleScanJob = function(){ + $location.path('/job_templates/'+this.scan_job_template.id+'/schedules'); + }; + + $scope.editScanJob = function(){ + $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); + }; + + $scope.copyScanJobTemplate = function(){ + var id = this.scan_job_template.id, + name = this.scan_job_template.name, + element, + buttons = [{ + "label": "Cancel", + "onClick": function() { + $(this).dialog('close'); + }, + "icon": "fa-times", + "class": "btn btn-default", + "id": "copy-close-button" + },{ + "label": "Copy", + "onClick": function() { + copyAction(); + }, + "icon": "fa-copy", + "class": "btn btn-primary", + "id": "job-copy-button" + }], + copyAction = function () { + // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id + Wait('start'); + var url = GetBasePath('job_templates')+id; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + data.name = $scope.new_copy_name; + delete data.id; + $scope.$emit('GoToCopy', data); + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }; + + + CreateDialog({ + id: 'copy-job-modal' , + title: "Copy", + scope: $scope, + buttons: buttons, + width: 500, + height: 300, + minWidth: 200, + callback: 'CopyDialogReady' + }); + + $('#job_name').text(name); + $('#copy-job-modal').show(); + + + if ($scope.removeCopyDialogReady) { + $scope.removeCopyDialogReady(); + } + $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() { + //clear any old remaining text + $scope.new_copy_name = "" ; + $scope.copy_form.$setPristine(); + $('#copy-job-modal').dialog('open'); + $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid"); + element = angular.element(document.getElementById('job-copy-button')); + $compile(element)($scope); + + }); + + if ($scope.removeGoToCopy) { + $scope.removeGoToCopy(); + } + $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) { + var url = GetBasePath('job_templates'), + old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ; + Rest.setUrl(url); + Rest.post(data) + .success(function (data) { + if(data.survey_enabled===true){ + $scope.$emit("CopySurvey", data, old_survey_url); + } + else { + $('#copy-job-modal').dialog('close'); + Wait('stop'); + $location.path($location.path() + '/job_templates/' + data.id); + } + + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }); + + if ($scope.removeCopySurvey) { + $scope.removeCopySurvey(); + } + $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) { + // var url = data.related.survey_spec; + Rest.setUrl(old_url); + Rest.get() + .success(function (survey_data) { + + Rest.setUrl(new_data.related.survey_spec); + Rest.post(survey_data) + .success(function () { + $('#copy-job-modal').dialog('close'); + Wait('stop'); + $location.path($location.path() + '/job_templates/' + new_data.id); + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status }); + }); + + + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status }); + }); + + }); + + }; + + $scope.deleteScanJob = function () { + var id = this.scan_job_template.id , + action = function () { + $('#prompt-modal').modal('hide'); + Wait('start'); + deleteJobTemplate(id) + .success(function () { + $('#prompt-modal').modal('hide'); + $scope.search(form.related.scan_job_templates.iterator); + }) + .error(function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'DELETE returned status: ' + status }); + }); + }; + + Prompt({ + hdr: 'Delete', + body: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
', + action: action, + actionText: 'DELETE' + }); + + }; + +} + +export default ['$scope', '$rootScope', '$compile', '$location', + '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', + 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', + 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', + 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', + 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', + 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state', + InventoriesEdit, +]; diff --git a/awx/ui/client/src/inventory/inventory-list.controller.js b/awx/ui/client/src/inventory/inventory-list.controller.js new file mode 100644 index 0000000000..98f02df918 --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-list.controller.js @@ -0,0 +1,371 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesList($scope, $rootScope, $location, $log, + $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, + generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, + ClearScope, ProcessErrors, GetBasePath, Wait, + EditInventoryProperties, Find, Empty, $state) { + + var list = InventoryList, + defaultUrl = GetBasePath('inventory'), + view = generateList, + paths = $location.path().replace(/^\//, '').split('/'), + mode = (paths[0] === 'inventories') ? 'edit' : 'select'; + + function ellipsis(a) { + if (a.length > 20) { + return a.substr(0,20) + '...'; + } + return a; + } + + function attachElem(event, html, title) { + var elem = $(event.target).parent(); + try { + elem.tooltip('hide'); + elem.popover('destroy'); + } + catch(err) { + //ignore + } + $('.popover').each(function() { + // remove lingering popover
. Seems to be a bug in TB3 RC1 + $(this).remove(); + }); + $('.tooltip').each( function() { + // close any lingering tool tipss + $(this).hide(); + }); + elem.attr({ + "aw-pop-over": html, + "data-popover-title": title, + "data-placement": "right" }); + $compile(elem)($scope); + elem.on('shown.bs.popover', function() { + $('.popover').each(function() { + $compile($(this))($scope); //make nested directives work! + }); + $('.popover-content, .popover-title').click(function() { + elem.popover('hide'); + }); + }); + elem.popover('show'); + } + + view.inject(InventoryList, { mode: mode, scope: $scope }); + $rootScope.flashMessage = null; + + SearchInit({ + scope: $scope, + set: 'inventories', + list: list, + url: defaultUrl + }); + + PaginateInit({ + scope: $scope, + list: list, + url: defaultUrl + }); + + if ($stateParams.name) { + $scope[InventoryList.iterator + 'InputDisable'] = false; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name; + $scope[InventoryList.iterator + 'SearchField'] = 'name'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label; + $scope[InventoryList.iterator + 'SearchSelectValue'] = null; + } + + if ($stateParams.has_active_failures) { + $scope[InventoryList.iterator + 'InputDisable'] = true; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures; + $scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label; + $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? { + value: 1 + } : { + value: 0 + }; + } + + if ($stateParams.has_inventory_sources) { + $scope[InventoryList.iterator + 'InputDisable'] = true; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources; + $scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label; + $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? { + value: 1 + } : { + value: 0 + }; + } + + if ($stateParams.inventory_sources_with_failures) { + // pass a value of true, however this field actually contains an integer value + $scope[InventoryList.iterator + 'InputDisable'] = true; + $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures; + $scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures'; + $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label; + $scope[InventoryList.iterator + 'SearchType'] = 'gtzero'; + } + + $scope.search(list.iterator); + + if ($scope.removePostRefresh) { + $scope.removePostRefresh(); + } + $scope.removePostRefresh = $scope.$on('PostRefresh', function () { + //If we got here by deleting an inventory, stop the spinner and cleanup events + Wait('stop'); + try { + $('#prompt-modal').modal('hide'); + } + catch(e) { + // ignore + } + $scope.inventories.forEach(function(inventory, idx) { + $scope.inventories[idx].launch_class = ""; + if (inventory.has_inventory_sources) { + if (inventory.inventory_sources_with_failures > 0) { + $scope.inventories[idx].syncStatus = 'error'; + $scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details'; + } + else { + $scope.inventories[idx].syncStatus = 'successful'; + $scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.'; + } + } + else { + $scope.inventories[idx].syncStatus = 'na'; + $scope.inventories[idx].syncTip = 'Not configured for inventory sync.'; + $scope.inventories[idx].launch_class = "btn-disabled"; + } + if (inventory.has_active_failures) { + $scope.inventories[idx].hostsStatus = 'error'; + $scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.'; + } + else if (inventory.total_hosts) { + $scope.inventories[idx].hostsStatus = 'successful'; + $scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.'; + } + else { + $scope.inventories[idx].hostsStatus = 'none'; + $scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.'; + } + }); + }); + + if ($scope.removeRefreshInventories) { + $scope.removeRefreshInventories(); + } + $scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () { + // Reflect changes after inventory properties edit completes + $scope.search(list.iterator); + }); + + if ($scope.removeHostSummaryReady) { + $scope.removeHostSummaryReady(); + } + $scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) { + + var html, title = "Recent Jobs"; + Wait('stop'); + if (data.count > 0) { + html = "\n"; + html += "\n"; + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + html += "\n"; + html += "\n"; + + data.results.forEach(function(row) { + html += "\n"; + html += "\n"; + html += ""; + html += ""; + html += "\n"; + }); + html += "\n"; + html += "
StatusFinishedName
" + ($filter('longDate')(row.finished)).replace(/ /,'
') + "
" + ellipsis(row.name) + "
\n"; + } + else { + html = "

No recent job data available for this inventory.

\n"; + } + attachElem(event, html, title); + }); + + if ($scope.removeGroupSummaryReady) { + $scope.removeGroupSummaryReady(); + } + $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) { + var html, title; + + Wait('stop'); + + // Build the html for our popover + html = "\n"; + html += "\n"; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + html += "\n"; + data.results.forEach( function(row) { + if (row.related.last_update) { + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + } + else { + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + } + }); + html += "\n"; + html += "
StatusLast SyncGroup
" + ($filter('longDate')(row.last_updated)).replace(/ /,'
') + "
" + ellipsis(row.summary_fields.group.name) + "
NA" + ellipsis(row.summary_fields.group.name) + "
\n"; + title = "Sync Status"; + attachElem(event, html, title); + }); + + $scope.showGroupSummary = function(event, id) { + var inventory; + if (!Empty(id)) { + inventory = Find({ list: $scope.inventories, key: 'id', val: id }); + if (inventory.syncStatus !== 'na') { + Wait('start'); + Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); + Rest.get() + .success(function(data) { + $scope.$emit('GroupSummaryReady', event, inventory, data); + }) + .error(function(data, status) { + ProcessErrors( $scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status + }); + }); + } + } + }; + + $scope.showHostSummary = function(event, id) { + var url, inventory; + if (!Empty(id)) { + inventory = Find({ list: $scope.inventories, key: 'id', val: id }); + if (inventory.total_hosts > 0) { + Wait('start'); + url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed="; + url += (inventory.has_active_failures) ? 'true' : "false"; + url += "&order_by=-finished&page_size=5"; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + $scope.$emit('HostSummaryReady', event, data); + }) + .error( function(data, status) { + ProcessErrors( $scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status + }); + }); + } + } + }; + + $scope.viewJob = function(url) { + + // Pull the id out of the URL + var id = url.replace(/^\//, '').split('/')[3]; + + $state.go('inventorySyncStdout', {id: id}); + + }; + + $scope.editInventoryProperties = function (inventory_id) { + EditInventoryProperties({ scope: $scope, inventory_id: inventory_id }); + }; + + $scope.addInventory = function () { + $state.go('inventories.add'); + }; + + $scope.editInventory = function (id) { + $state.go('inventories.edit', {inventory_id: id}); + }; + + $scope.manageInventory = function(id){ + $location.path($location.path() + '/' + id + '/manage'); + }; + + $scope.deleteInventory = function (id, name) { + + var action = function () { + var url = defaultUrl + id + '/'; + Wait('start'); + $('#prompt-modal').modal('hide'); + 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 + }); + }); + }; + + Prompt({ + hdr: 'Delete', + body: '
Are you sure you want to delete the inventory below?
' + $filter('sanitize')(name) + '
', + action: action, + actionText: 'DELETE' + }); + }; + + $scope.lookupOrganization = function (organization_id) { + Rest.setUrl(GetBasePath('organizations') + organization_id + '/'); + Rest.get() + .success(function (data) { + return data.name; + }); + }; + + + // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status + $scope.viewJobs = function (id) { + $location.url('/jobs/?inventory__int=' + id); + }; + + $scope.viewFailedJobs = function (id) { + $location.url('/jobs/?inventory__int=' + id + '&status=failed'); + }; +} + +export default ['$scope', '$rootScope', '$location', '$log', + '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', + 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', + 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', + 'EditInventoryProperties', 'Find', 'Empty', '$state', InventoriesList]; diff --git a/awx/ui/client/src/inventory/inventory-manage.controller.js b/awx/ui/client/src/inventory/inventory-manage.controller.js new file mode 100644 index 0000000000..651547894e --- /dev/null +++ b/awx/ui/client/src/inventory/inventory-manage.controller.js @@ -0,0 +1,532 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +import '../job-templates/main'; + +function InventoriesManage($log, $scope, $rootScope, $location, + $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, + GetBasePath, ProcessErrors, InventoryGroups, + InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, + GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate, + ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, + EditInventoryProperties, ToggleHostEnabled, ShowJobSummary, + InventoryGroupsHelp, HelpDialog, + GroupsCopy, HostsCopy, $stateParams) { + + var PreviousSearchParams, + url, + hostScope = $scope.$new(); + + ClearScope(); + + // TODO: only display adhoc button if the user has permission to use it. + // TODO: figure out how to get the action-list partial to update so that + // the tooltip can be changed based off things being selected or not. + $scope.adhocButtonTipContents = "Launch adhoc command for the inventory"; + + // watcher for the group list checkbox changes + $scope.$on('multiSelectList.selectionChanged', function(e, selection) { + if (selection.length > 0) { + $scope.groupsSelected = true; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "selected groups and hosts."; + } else { + $scope.groupsSelected = false; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "inventory."; + } + $scope.groupsSelectedItems = selection.selectedItems; + }); + + // watcher for the host list checkbox changes + hostScope.$on('multiSelectList.selectionChanged', function(e, selection) { + // you need this so that the event doesn't bubble to the watcher above + // for the host list + e.stopPropagation(); + if (selection.length === 0) { + $scope.hostsSelected = false; + } else if (selection.length === 1) { + $scope.systemTrackingTooltip = "Compare host over time"; + $scope.hostsSelected = true; + $scope.systemTrackingDisabled = false; + } else if (selection.length === 2) { + $scope.systemTrackingTooltip = "Compare hosts against each other"; + $scope.hostsSelected = true; + $scope.systemTrackingDisabled = false; + } else { + $scope.hostsSelected = true; + $scope.systemTrackingDisabled = true; + } + $scope.hostsSelectedItems = selection.selectedItems; + }); + + $scope.systemTracking = function() { + var hostIds = _.map($scope.hostsSelectedItems, function(x){ + return x.id; + }); + $state.transitionTo('systemTracking', + { inventory: $scope.inventory, + inventoryId: $scope.inventory.id, + hosts: $scope.hostsSelectedItems, + hostIds: hostIds + }); + }; + + // populates host patterns based on selected hosts/groups + $scope.populateAdhocForm = function() { + var host_patterns = "all"; + if ($scope.hostsSelected || $scope.groupsSelected) { + var allSelectedItems = []; + if ($scope.groupsSelectedItems) { + allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems); + } + if ($scope.hostsSelectedItems) { + allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems); + } + if (allSelectedItems) { + host_patterns = _.pluck(allSelectedItems, "name").join(":"); + } + } + $rootScope.hostPatterns = host_patterns; + $state.go('inventoryManage.adhoc'); + }; + + $scope.refreshHostsOnGroupRefresh = false; + $scope.selected_group_id = null; + + Wait('start'); + + + if ($scope.removeHostReloadComplete) { + $scope.removeHostReloadComplete(); + } + $scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() { + if ($scope.initial_height) { + var host_height = $('#hosts-container .well').height(), + group_height = $('#group-list-container .well').height(), + new_height; + + if (host_height > group_height) { + new_height = host_height - (host_height - group_height); + } + else if (host_height < group_height) { + new_height = host_height + (group_height - host_height); + } + if (new_height) { + $('#hosts-container .well').height(new_height); + } + $scope.initial_height = null; + } + }); + + if ($scope.removeRowCountReady) { + $scope.removeRowCountReady(); + } + $scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) { + // Add hosts view + $scope.show_failures = false; + InjectHosts({ + group_scope: $scope, + host_scope: hostScope, + inventory_id: $scope.inventory.id, + tree_id: null, + group_id: null, + pageSize: rows + }); + + SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups }); + PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows }); + $scope.search(InventoryGroups.iterator, null, true); + }); + + if ($scope.removeInventoryLoaded) { + $scope.removeInventoryLoaded(); + } + $scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() { + var rows; + + // Add groups view + generateList.inject(InventoryGroups, { + mode: 'edit', + id: 'group-list-container', + searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12', + scope: $scope + }); + + rows = 20; + hostScope.host_page_size = rows; + $scope.group_page_size = rows; + + $scope.show_failures = false; + InjectHosts({ + group_scope: $scope, + host_scope: hostScope, + inventory_id: $scope.inventory.id, + tree_id: null, + group_id: null, + pageSize: rows + }); + + // Load data + SearchInit({ + scope: $scope, + set: 'groups', + list: InventoryGroups, + url: $scope.inventory.related.root_groups + }); + + PaginateInit({ + scope: $scope, + list: InventoryGroups , + url: $scope.inventory.related.root_groups, + pageSize: rows + }); + + $scope.search(InventoryGroups.iterator, null, true); + + $scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates + }); + + if ($scope.removePostRefresh) { + $scope.removePostRefresh(); + } + $scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) { + if (set === 'groups') { + $scope.groups.forEach( function(group, idx) { + var stat, hosts_status; + stat = GetSyncStatusMsg({ + status: group.summary_fields.inventory_source.status, + has_inventory_sources: group.has_inventory_sources, + source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null ) + }); // from helpers/Groups.js + $scope.groups[idx].status_class = stat['class']; + $scope.groups[idx].status_tooltip = stat.tooltip; + $scope.groups[idx].launch_tooltip = stat.launch_tip; + $scope.groups[idx].launch_class = stat.launch_class; + hosts_status = GetHostsStatusMsg({ + active_failures: group.hosts_with_active_failures, + total_hosts: group.total_hosts, + inventory_id: $scope.inventory.id, + group_id: group.id + }); // from helpers/Groups.js + $scope.groups[idx].hosts_status_tip = hosts_status.tooltip; + $scope.groups[idx].show_failures = hosts_status.failures; + $scope.groups[idx].hosts_status_class = hosts_status['class']; + + $scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null; + $scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null; + + }); + if ($scope.refreshHostsOnGroupRefresh) { + $scope.refreshHostsOnGroupRefresh = false; + HostsReload({ + scope: hostScope, + group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id, + pageSize: hostScope.host_page_size + }); + } + else { + Wait('stop'); + } + } + }); + + // Load Inventory + url = GetBasePath('inventory') + $stateParams.inventory_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + $scope.inventory = data; + $scope.$emit('InventoryLoaded'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id + + ' GET returned status: ' + status }); + }); + + // start watching for real-time updates + if ($rootScope.removeWatchUpdateStatus) { + $rootScope.removeWatchUpdateStatus(); + } + $rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) { + var stat, group; + if (data.group_id) { + group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); + if (data.status === "failed" || data.status === "successful") { + if (data.group_id === $scope.selected_group_id || group) { + // job completed, fefresh all groups + $log.debug('Update completed. Refreshing the tree.'); + $scope.refreshGroups(); + } + } + else if (group) { + // incremental update, just update + $log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status); + stat = GetSyncStatusMsg({ + status: data.status, + has_inventory_sources: group.has_inventory_sources, + source: group.source + }); + $log.debug('changing tooltip to: ' + stat.tooltip); + group.status = data.status; + group.status_class = stat['class']; + group.status_tooltip = stat.tooltip; + group.launch_tooltip = stat.launch_tip; + group.launch_class = stat.launch_class; + } + } + }); + + // Load group on selection + function loadGroups(url) { + SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url }); + PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size }); + $scope.search(InventoryGroups.iterator, null, true, false, true); + } + + $scope.refreshHosts = function() { + HostsReload({ + scope: hostScope, + group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id, + pageSize: hostScope.host_page_size + }); + }; + + $scope.refreshGroups = function() { + $scope.refreshHostsOnGroupRefresh = true; + $scope.search(InventoryGroups.iterator, null, true, false, true); + }; + + $scope.restoreSearch = function() { + // Restore search params and related stuff, plus refresh + // groups and hosts lists + SearchInit({ + scope: $scope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + }); + $scope.refreshHostsOnGroupRefresh = true; + $scope.search(InventoryGroups.iterator, null, true, false, true); + }; + + $scope.groupSelect = function(id) { + var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id }); + if($state.params.groups){ + groups.push($state.params.groups); + } + groups.push(group.id); + groups = groups.join(); + $state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false }); + loadGroups(group.related.children, group.id); + }; + + $scope.createGroup = function () { + PreviousSearchParams = Store('group_current_search_params'); + GroupsEdit({ + scope: $scope, + inventory_id: $scope.inventory.id, + group_id: $scope.selected_group_id, + mode: 'add' + }); + }; + + $scope.editGroup = function (id) { + PreviousSearchParams = Store('group_current_search_params'); + GroupsEdit({ + scope: $scope, + inventory_id: $scope.inventory.id, + group_id: id, + mode: 'edit' + }); + }; + + // Launch inventory sync + $scope.updateGroup = function (id) { + var group = Find({ list: $scope.groups, key: 'id', val: id }); + if (group) { + if (Empty(group.source)) { + // if no source, do nothing. + } else if (group.status === 'updating') { + Alert('Update in Progress', 'The inventory update process is currently running for group ' + + group.name + ' Click the button to monitor the status.', 'alert-info', null, null, null, null, true); + } else { + Wait('start'); + Rest.setUrl(group.related.inventory_source); + Rest.get() + .success(function (data) { + InventoryUpdate({ + scope: $scope, + url: data.related.update, + group_name: data.summary_fields.group.name, + group_source: data.source, + group_id: group.id, + }); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + + group.related.inventory_source + ' GET returned status: ' + status }); + }); + } + } + }; + + $scope.cancelUpdate = function (id) { + GroupsCancelUpdate({ scope: $scope, id: id }); + }; + + $scope.viewUpdateStatus = function (id) { + ViewUpdateStatus({ + scope: $scope, + group_id: id + }); + }; + + $scope.copyGroup = function(id) { + PreviousSearchParams = Store('group_current_search_params'); + GroupsCopy({ + scope: $scope, + group_id: id + }); + }; + + $scope.deleteGroup = function (id) { + GroupsDelete({ + scope: $scope, + group_id: id, + inventory_id: $scope.inventory.id + }); + }; + + $scope.editInventoryProperties = function () { + // EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id }); + $location.path('/inventories/' + $scope.inventory.id + '/'); + }; + + hostScope.createHost = function () { + HostsEdit({ + host_scope: hostScope, + group_scope: $scope, + mode: 'add', + host_id: null, + selected_group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id + }); + }; + + hostScope.editHost = function (host_id) { + HostsEdit({ + host_scope: hostScope, + group_scope: $scope, + mode: 'edit', + host_id: host_id, + inventory_id: $scope.inventory.id + }); + }; + + hostScope.deleteHost = function (host_id, host_name) { + HostsDelete({ + parent_scope: $scope, + host_scope: hostScope, + host_id: host_id, + host_name: host_name + }); + }; + + hostScope.copyHost = function(id) { + PreviousSearchParams = Store('group_current_search_params'); + HostsCopy({ + group_scope: $scope, + host_scope: hostScope, + host_id: id + }); + }; + + /*hostScope.restoreSearch = function() { + SearchInit({ + scope: hostScope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + }); + hostScope.search('host'); + };*/ + + hostScope.toggleHostEnabled = function (host_id, external_source) { + ToggleHostEnabled({ + parent_scope: $scope, + host_scope: hostScope, + host_id: host_id, + external_source: external_source + }); + }; + + hostScope.showJobSummary = function (job_id) { + ShowJobSummary({ + job_id: job_id + }); + }; + + $scope.showGroupHelp = function (params) { + var opts = { + defn: InventoryGroupsHelp + }; + if (params) { + opts.autoShow = params.autoShow || false; + } + HelpDialog(opts); + } +; + $scope.showHosts = function (group_id, show_failures) { + // Clicked on group + if (group_id !== null) { + Wait('start'); + hostScope.show_failures = show_failures; + $scope.groupSelect(group_id); + hostScope.hosts = []; + $scope.show_failures = show_failures; // turn on failed hosts + // filter in hosts view + } else { + Wait('stop'); + } + }; + + if ($scope.removeGroupDeleteCompleted) { + $scope.removeGroupDeleteCompleted(); + } + $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', + function() { + $scope.refreshGroups(); + } + ); +} + +export default [ + '$log', '$scope', '$rootScope', '$location', + '$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait', + 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', + 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', + 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', + 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', + 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', + 'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary', + 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy', + 'HostsCopy', '$stateParams', InventoriesManage, +]; diff --git a/awx/ui/client/src/inventory/main.js b/awx/ui/client/src/inventory/main.js new file mode 100644 index 0000000000..0689a2d726 --- /dev/null +++ b/awx/ui/client/src/inventory/main.js @@ -0,0 +1,10 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +export default +angular.module('inventory', [ +]) From a76b361f18a9547607ab4773226343cd470f4ac1 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Thu, 17 Mar 2016 16:11:42 -0400 Subject: [PATCH 05/33] Removed artifacts from merge --- .../src/inventory/inventory-add.controller.js | 98 ---- .../inventory/inventory-edit.controller.js | 332 ----------- .../inventory/inventory-list.controller.js | 371 ------------ .../inventory/inventory-manage.controller.js | 532 ------------------ awx/ui/client/src/inventory/main.js | 10 - 5 files changed, 1343 deletions(-) delete mode 100644 awx/ui/client/src/inventory/inventory-add.controller.js delete mode 100644 awx/ui/client/src/inventory/inventory-edit.controller.js delete mode 100644 awx/ui/client/src/inventory/inventory-list.controller.js delete mode 100644 awx/ui/client/src/inventory/inventory-manage.controller.js delete mode 100644 awx/ui/client/src/inventory/main.js diff --git a/awx/ui/client/src/inventory/inventory-add.controller.js b/awx/ui/client/src/inventory/inventory-add.controller.js deleted file mode 100644 index a0903f4fab..0000000000 --- a/awx/ui/client/src/inventory/inventory-add.controller.js +++ /dev/null @@ -1,98 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -import '../job-templates/main'; - -function InventoriesAdd($scope, $rootScope, $compile, $location, $log, - $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, - ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, - PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, - $state) { - - ClearScope(); - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = InventoryForm(), - generator = GenerateForm; - - form.well = true; - form.formLabelSize = null; - form.formFieldSize = null; - - generator.inject(form, { mode: 'add', related: false, scope: $scope }); - - generator.reset(); - - $scope.parseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - variable: 'variables', - parse_variable: 'parseType', - field_id: 'inventory_variables' - }); - - LookUpInit({ - scope: $scope, - form: form, - current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null, - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); - - // Save - $scope.formSave = function () { - generator.clearApiErrors(); - Wait('start'); - try { - var fld, json_data, data; - - json_data = ToJSON($scope.parseType, $scope.variables, true); - - data = {}; - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - - Rest.setUrl(defaultUrl); - Rest.post(data) - .success(function (data) { - var inventory_id = data.id; - Wait('stop'); - $location.path('/inventories/' + inventory_id + '/manage'); - }) - .error(function (data, status) { - ProcessErrors( $scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to add new inventory. Post returned status: ' + status }); - }); - } catch (err) { - Wait('stop'); - Alert("Error", "Error parsing inventory variables. Parser returned: " + err); - } - - }; - - $scope.formCancel = function () { - $state.transitionTo('inventories'); - }; -} - -export default['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', - 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', - 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', - 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd] diff --git a/awx/ui/client/src/inventory/inventory-edit.controller.js b/awx/ui/client/src/inventory/inventory-edit.controller.js deleted file mode 100644 index dd45a566ac..0000000000 --- a/awx/ui/client/src/inventory/inventory-edit.controller.js +++ /dev/null @@ -1,332 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -import '../job-templates/main'; - -function InventoriesEdit($scope, $rootScope, $compile, $location, - $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, - ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, - PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, - ParseVariableString, RelatedSearchInit, RelatedPaginateInit, - Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) { - - ClearScope(); - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = InventoryForm(), - generator = GenerateForm, - inventory_id = $stateParams.inventory_id, - master = {}, - fld, json_data, data, - relatedSets = {}; - - form.well = true; - form.formLabelSize = null; - form.formFieldSize = null; - $scope.inventory_id = inventory_id; - generator.inject(form, { mode: 'edit', related: true, scope: $scope }); - - generator.reset(); - - - // After the project is loaded, retrieve each related set - if ($scope.inventoryLoadedRemove) { - $scope.inventoryLoadedRemove(); - } - $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () { - var set; - for (set in relatedSets) { - $scope.search(relatedSets[set].iterator); - } - }); - - Wait('start'); - Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); - Rest.get() - .success(function (data) { - var fld; - for (fld in form.fields) { - if (fld === 'variables') { - $scope.variables = ParseVariableString(data.variables); - master.variables = $scope.variables; - } else if (fld === 'inventory_name') { - $scope[fld] = data.name; - master[fld] = $scope[fld]; - } else if (fld === 'inventory_description') { - $scope[fld] = data.description; - master[fld] = $scope[fld]; - } else if (data[fld]) { - $scope[fld] = data[fld]; - master[fld] = $scope[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]; - } - } - relatedSets = form.relatedSets(data.related); - - // Initialize related search functions. Doing it here to make sure relatedSets object is populated. - RelatedSearchInit({ - scope: $scope, - form: form, - relatedSets: relatedSets - }); - RelatedPaginateInit({ - scope: $scope, - relatedSets: relatedSets - }); - - Wait('stop'); - $scope.parseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - variable: 'variables', - parse_variable: 'parseType', - field_id: 'inventory_variables' - }); - LookUpInit({ - scope: $scope, - form: form, - current_item: $scope.organization, - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); - $scope.$emit('inventoryLoaded'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status }); - }); - // Save - $scope.formSave = function () { - Wait('start'); - - // Make sure we have valid variable data - json_data = ToJSON($scope.parseType, $scope.variables); - - data = {}; - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - - Rest.setUrl(defaultUrl + inventory_id + '/'); - Rest.put(data) - .success(function () { - Wait('stop'); - $location.path('/inventories/'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to update inventory. PUT returned status: ' + status }); - }); - }; - - $scope.manageInventory = function(){ - $location.path($location.path() + '/manage'); - }; - - $scope.formCancel = function () { - $state.transitionTo('inventories'); - }; - - $scope.addScanJob = function(){ - $location.path($location.path()+'/job_templates/add'); - }; - - $scope.launchScanJob = function(){ - PlaybookRun({ scope: $scope, id: this.scan_job_template.id }); - }; - - $scope.scheduleScanJob = function(){ - $location.path('/job_templates/'+this.scan_job_template.id+'/schedules'); - }; - - $scope.editScanJob = function(){ - $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); - }; - - $scope.copyScanJobTemplate = function(){ - var id = this.scan_job_template.id, - name = this.scan_job_template.name, - element, - buttons = [{ - "label": "Cancel", - "onClick": function() { - $(this).dialog('close'); - }, - "icon": "fa-times", - "class": "btn btn-default", - "id": "copy-close-button" - },{ - "label": "Copy", - "onClick": function() { - copyAction(); - }, - "icon": "fa-copy", - "class": "btn btn-primary", - "id": "job-copy-button" - }], - copyAction = function () { - // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id - Wait('start'); - var url = GetBasePath('job_templates')+id; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - data.name = $scope.new_copy_name; - delete data.id; - $scope.$emit('GoToCopy', data); - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - }; - - - CreateDialog({ - id: 'copy-job-modal' , - title: "Copy", - scope: $scope, - buttons: buttons, - width: 500, - height: 300, - minWidth: 200, - callback: 'CopyDialogReady' - }); - - $('#job_name').text(name); - $('#copy-job-modal').show(); - - - if ($scope.removeCopyDialogReady) { - $scope.removeCopyDialogReady(); - } - $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() { - //clear any old remaining text - $scope.new_copy_name = "" ; - $scope.copy_form.$setPristine(); - $('#copy-job-modal').dialog('open'); - $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid"); - element = angular.element(document.getElementById('job-copy-button')); - $compile(element)($scope); - - }); - - if ($scope.removeGoToCopy) { - $scope.removeGoToCopy(); - } - $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) { - var url = GetBasePath('job_templates'), - old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ; - Rest.setUrl(url); - Rest.post(data) - .success(function (data) { - if(data.survey_enabled===true){ - $scope.$emit("CopySurvey", data, old_survey_url); - } - else { - $('#copy-job-modal').dialog('close'); - Wait('stop'); - $location.path($location.path() + '/job_templates/' + data.id); - } - - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - }); - - if ($scope.removeCopySurvey) { - $scope.removeCopySurvey(); - } - $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) { - // var url = data.related.survey_spec; - Rest.setUrl(old_url); - Rest.get() - .success(function (survey_data) { - - Rest.setUrl(new_data.related.survey_spec); - Rest.post(survey_data) - .success(function () { - $('#copy-job-modal').dialog('close'); - Wait('stop'); - $location.path($location.path() + '/job_templates/' + new_data.id); - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status }); - }); - - - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status }); - }); - - }); - - }; - - $scope.deleteScanJob = function () { - var id = this.scan_job_template.id , - action = function () { - $('#prompt-modal').modal('hide'); - Wait('start'); - deleteJobTemplate(id) - .success(function () { - $('#prompt-modal').modal('hide'); - $scope.search(form.related.scan_job_templates.iterator); - }) - .error(function (data) { - Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'DELETE returned status: ' + status }); - }); - }; - - Prompt({ - hdr: 'Delete', - body: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
', - action: action, - actionText: 'DELETE' - }); - - }; - -} - -export default ['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', - 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList', - 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', - 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', - 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', - 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state', - InventoriesEdit, -]; diff --git a/awx/ui/client/src/inventory/inventory-list.controller.js b/awx/ui/client/src/inventory/inventory-list.controller.js deleted file mode 100644 index 98f02df918..0000000000 --- a/awx/ui/client/src/inventory/inventory-list.controller.js +++ /dev/null @@ -1,371 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -import '../job-templates/main'; - -function InventoriesList($scope, $rootScope, $location, $log, - $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, - generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, - ClearScope, ProcessErrors, GetBasePath, Wait, - EditInventoryProperties, Find, Empty, $state) { - - var list = InventoryList, - defaultUrl = GetBasePath('inventory'), - view = generateList, - paths = $location.path().replace(/^\//, '').split('/'), - mode = (paths[0] === 'inventories') ? 'edit' : 'select'; - - function ellipsis(a) { - if (a.length > 20) { - return a.substr(0,20) + '...'; - } - return a; - } - - function attachElem(event, html, title) { - var elem = $(event.target).parent(); - try { - elem.tooltip('hide'); - elem.popover('destroy'); - } - catch(err) { - //ignore - } - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each( function() { - // close any lingering tool tipss - $(this).hide(); - }); - elem.attr({ - "aw-pop-over": html, - "data-popover-title": title, - "data-placement": "right" }); - $compile(elem)($scope); - elem.on('shown.bs.popover', function() { - $('.popover').each(function() { - $compile($(this))($scope); //make nested directives work! - }); - $('.popover-content, .popover-title').click(function() { - elem.popover('hide'); - }); - }); - elem.popover('show'); - } - - view.inject(InventoryList, { mode: mode, scope: $scope }); - $rootScope.flashMessage = null; - - SearchInit({ - scope: $scope, - set: 'inventories', - list: list, - url: defaultUrl - }); - - PaginateInit({ - scope: $scope, - list: list, - url: defaultUrl - }); - - if ($stateParams.name) { - $scope[InventoryList.iterator + 'InputDisable'] = false; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name; - $scope[InventoryList.iterator + 'SearchField'] = 'name'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label; - $scope[InventoryList.iterator + 'SearchSelectValue'] = null; - } - - if ($stateParams.has_active_failures) { - $scope[InventoryList.iterator + 'InputDisable'] = true; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures; - $scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label; - $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? { - value: 1 - } : { - value: 0 - }; - } - - if ($stateParams.has_inventory_sources) { - $scope[InventoryList.iterator + 'InputDisable'] = true; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources; - $scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label; - $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? { - value: 1 - } : { - value: 0 - }; - } - - if ($stateParams.inventory_sources_with_failures) { - // pass a value of true, however this field actually contains an integer value - $scope[InventoryList.iterator + 'InputDisable'] = true; - $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures; - $scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures'; - $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label; - $scope[InventoryList.iterator + 'SearchType'] = 'gtzero'; - } - - $scope.search(list.iterator); - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function () { - //If we got here by deleting an inventory, stop the spinner and cleanup events - Wait('stop'); - try { - $('#prompt-modal').modal('hide'); - } - catch(e) { - // ignore - } - $scope.inventories.forEach(function(inventory, idx) { - $scope.inventories[idx].launch_class = ""; - if (inventory.has_inventory_sources) { - if (inventory.inventory_sources_with_failures > 0) { - $scope.inventories[idx].syncStatus = 'error'; - $scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details'; - } - else { - $scope.inventories[idx].syncStatus = 'successful'; - $scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.'; - } - } - else { - $scope.inventories[idx].syncStatus = 'na'; - $scope.inventories[idx].syncTip = 'Not configured for inventory sync.'; - $scope.inventories[idx].launch_class = "btn-disabled"; - } - if (inventory.has_active_failures) { - $scope.inventories[idx].hostsStatus = 'error'; - $scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.'; - } - else if (inventory.total_hosts) { - $scope.inventories[idx].hostsStatus = 'successful'; - $scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.'; - } - else { - $scope.inventories[idx].hostsStatus = 'none'; - $scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.'; - } - }); - }); - - if ($scope.removeRefreshInventories) { - $scope.removeRefreshInventories(); - } - $scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () { - // Reflect changes after inventory properties edit completes - $scope.search(list.iterator); - }); - - if ($scope.removeHostSummaryReady) { - $scope.removeHostSummaryReady(); - } - $scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) { - - var html, title = "Recent Jobs"; - Wait('stop'); - if (data.count > 0) { - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - html += "\n"; - - data.results.forEach(function(row) { - html += "\n"; - html += "\n"; - html += ""; - html += ""; - html += "\n"; - }); - html += "\n"; - html += "
StatusFinishedName
" + ($filter('longDate')(row.finished)).replace(/ /,'
') + "
" + ellipsis(row.name) + "
\n"; - } - else { - html = "

No recent job data available for this inventory.

\n"; - } - attachElem(event, html, title); - }); - - if ($scope.removeGroupSummaryReady) { - $scope.removeGroupSummaryReady(); - } - $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) { - var html, title; - - Wait('stop'); - - // Build the html for our popover - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - data.results.forEach( function(row) { - if (row.related.last_update) { - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - } - else { - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - } - }); - html += "\n"; - html += "
StatusLast SyncGroup
" + ($filter('longDate')(row.last_updated)).replace(/ /,'
') + "
" + ellipsis(row.summary_fields.group.name) + "
NA" + ellipsis(row.summary_fields.group.name) + "
\n"; - title = "Sync Status"; - attachElem(event, html, title); - }); - - $scope.showGroupSummary = function(event, id) { - var inventory; - if (!Empty(id)) { - inventory = Find({ list: $scope.inventories, key: 'id', val: id }); - if (inventory.syncStatus !== 'na') { - Wait('start'); - Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); - Rest.get() - .success(function(data) { - $scope.$emit('GroupSummaryReady', event, inventory, data); - }) - .error(function(data, status) { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status - }); - }); - } - } - }; - - $scope.showHostSummary = function(event, id) { - var url, inventory; - if (!Empty(id)) { - inventory = Find({ list: $scope.inventories, key: 'id', val: id }); - if (inventory.total_hosts > 0) { - Wait('start'); - url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed="; - url += (inventory.has_active_failures) ? 'true' : "false"; - url += "&order_by=-finished&page_size=5"; - Rest.setUrl(url); - Rest.get() - .success( function(data) { - $scope.$emit('HostSummaryReady', event, data); - }) - .error( function(data, status) { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status - }); - }); - } - } - }; - - $scope.viewJob = function(url) { - - // Pull the id out of the URL - var id = url.replace(/^\//, '').split('/')[3]; - - $state.go('inventorySyncStdout', {id: id}); - - }; - - $scope.editInventoryProperties = function (inventory_id) { - EditInventoryProperties({ scope: $scope, inventory_id: inventory_id }); - }; - - $scope.addInventory = function () { - $state.go('inventories.add'); - }; - - $scope.editInventory = function (id) { - $state.go('inventories.edit', {inventory_id: id}); - }; - - $scope.manageInventory = function(id){ - $location.path($location.path() + '/' + id + '/manage'); - }; - - $scope.deleteInventory = function (id, name) { - - var action = function () { - var url = defaultUrl + id + '/'; - Wait('start'); - $('#prompt-modal').modal('hide'); - 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 - }); - }); - }; - - Prompt({ - hdr: 'Delete', - body: '
Are you sure you want to delete the inventory below?
' + $filter('sanitize')(name) + '
', - action: action, - actionText: 'DELETE' - }); - }; - - $scope.lookupOrganization = function (organization_id) { - Rest.setUrl(GetBasePath('organizations') + organization_id + '/'); - Rest.get() - .success(function (data) { - return data.name; - }); - }; - - - // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status - $scope.viewJobs = function (id) { - $location.url('/jobs/?inventory__int=' + id); - }; - - $scope.viewFailedJobs = function (id) { - $location.url('/jobs/?inventory__int=' + id + '&status=failed'); - }; -} - -export default ['$scope', '$rootScope', '$location', '$log', - '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', - 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', - 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', - 'EditInventoryProperties', 'Find', 'Empty', '$state', InventoriesList]; diff --git a/awx/ui/client/src/inventory/inventory-manage.controller.js b/awx/ui/client/src/inventory/inventory-manage.controller.js deleted file mode 100644 index 651547894e..0000000000 --- a/awx/ui/client/src/inventory/inventory-manage.controller.js +++ /dev/null @@ -1,532 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -import '../job-templates/main'; - -function InventoriesManage($log, $scope, $rootScope, $location, - $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, - GetBasePath, ProcessErrors, InventoryGroups, - InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, - GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate, - ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, - EditInventoryProperties, ToggleHostEnabled, ShowJobSummary, - InventoryGroupsHelp, HelpDialog, - GroupsCopy, HostsCopy, $stateParams) { - - var PreviousSearchParams, - url, - hostScope = $scope.$new(); - - ClearScope(); - - // TODO: only display adhoc button if the user has permission to use it. - // TODO: figure out how to get the action-list partial to update so that - // the tooltip can be changed based off things being selected or not. - $scope.adhocButtonTipContents = "Launch adhoc command for the inventory"; - - // watcher for the group list checkbox changes - $scope.$on('multiSelectList.selectionChanged', function(e, selection) { - if (selection.length > 0) { - $scope.groupsSelected = true; - // $scope.adhocButtonTipContents = "Launch adhoc command for the " - // + "selected groups and hosts."; - } else { - $scope.groupsSelected = false; - // $scope.adhocButtonTipContents = "Launch adhoc command for the " - // + "inventory."; - } - $scope.groupsSelectedItems = selection.selectedItems; - }); - - // watcher for the host list checkbox changes - hostScope.$on('multiSelectList.selectionChanged', function(e, selection) { - // you need this so that the event doesn't bubble to the watcher above - // for the host list - e.stopPropagation(); - if (selection.length === 0) { - $scope.hostsSelected = false; - } else if (selection.length === 1) { - $scope.systemTrackingTooltip = "Compare host over time"; - $scope.hostsSelected = true; - $scope.systemTrackingDisabled = false; - } else if (selection.length === 2) { - $scope.systemTrackingTooltip = "Compare hosts against each other"; - $scope.hostsSelected = true; - $scope.systemTrackingDisabled = false; - } else { - $scope.hostsSelected = true; - $scope.systemTrackingDisabled = true; - } - $scope.hostsSelectedItems = selection.selectedItems; - }); - - $scope.systemTracking = function() { - var hostIds = _.map($scope.hostsSelectedItems, function(x){ - return x.id; - }); - $state.transitionTo('systemTracking', - { inventory: $scope.inventory, - inventoryId: $scope.inventory.id, - hosts: $scope.hostsSelectedItems, - hostIds: hostIds - }); - }; - - // populates host patterns based on selected hosts/groups - $scope.populateAdhocForm = function() { - var host_patterns = "all"; - if ($scope.hostsSelected || $scope.groupsSelected) { - var allSelectedItems = []; - if ($scope.groupsSelectedItems) { - allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems); - } - if ($scope.hostsSelectedItems) { - allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems); - } - if (allSelectedItems) { - host_patterns = _.pluck(allSelectedItems, "name").join(":"); - } - } - $rootScope.hostPatterns = host_patterns; - $state.go('inventoryManage.adhoc'); - }; - - $scope.refreshHostsOnGroupRefresh = false; - $scope.selected_group_id = null; - - Wait('start'); - - - if ($scope.removeHostReloadComplete) { - $scope.removeHostReloadComplete(); - } - $scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() { - if ($scope.initial_height) { - var host_height = $('#hosts-container .well').height(), - group_height = $('#group-list-container .well').height(), - new_height; - - if (host_height > group_height) { - new_height = host_height - (host_height - group_height); - } - else if (host_height < group_height) { - new_height = host_height + (group_height - host_height); - } - if (new_height) { - $('#hosts-container .well').height(new_height); - } - $scope.initial_height = null; - } - }); - - if ($scope.removeRowCountReady) { - $scope.removeRowCountReady(); - } - $scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) { - // Add hosts view - $scope.show_failures = false; - InjectHosts({ - group_scope: $scope, - host_scope: hostScope, - inventory_id: $scope.inventory.id, - tree_id: null, - group_id: null, - pageSize: rows - }); - - SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups }); - PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows }); - $scope.search(InventoryGroups.iterator, null, true); - }); - - if ($scope.removeInventoryLoaded) { - $scope.removeInventoryLoaded(); - } - $scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() { - var rows; - - // Add groups view - generateList.inject(InventoryGroups, { - mode: 'edit', - id: 'group-list-container', - searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12', - scope: $scope - }); - - rows = 20; - hostScope.host_page_size = rows; - $scope.group_page_size = rows; - - $scope.show_failures = false; - InjectHosts({ - group_scope: $scope, - host_scope: hostScope, - inventory_id: $scope.inventory.id, - tree_id: null, - group_id: null, - pageSize: rows - }); - - // Load data - SearchInit({ - scope: $scope, - set: 'groups', - list: InventoryGroups, - url: $scope.inventory.related.root_groups - }); - - PaginateInit({ - scope: $scope, - list: InventoryGroups , - url: $scope.inventory.related.root_groups, - pageSize: rows - }); - - $scope.search(InventoryGroups.iterator, null, true); - - $scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates - }); - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) { - if (set === 'groups') { - $scope.groups.forEach( function(group, idx) { - var stat, hosts_status; - stat = GetSyncStatusMsg({ - status: group.summary_fields.inventory_source.status, - has_inventory_sources: group.has_inventory_sources, - source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null ) - }); // from helpers/Groups.js - $scope.groups[idx].status_class = stat['class']; - $scope.groups[idx].status_tooltip = stat.tooltip; - $scope.groups[idx].launch_tooltip = stat.launch_tip; - $scope.groups[idx].launch_class = stat.launch_class; - hosts_status = GetHostsStatusMsg({ - active_failures: group.hosts_with_active_failures, - total_hosts: group.total_hosts, - inventory_id: $scope.inventory.id, - group_id: group.id - }); // from helpers/Groups.js - $scope.groups[idx].hosts_status_tip = hosts_status.tooltip; - $scope.groups[idx].show_failures = hosts_status.failures; - $scope.groups[idx].hosts_status_class = hosts_status['class']; - - $scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null; - $scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null; - - }); - if ($scope.refreshHostsOnGroupRefresh) { - $scope.refreshHostsOnGroupRefresh = false; - HostsReload({ - scope: hostScope, - group_id: $scope.selected_group_id, - inventory_id: $scope.inventory.id, - pageSize: hostScope.host_page_size - }); - } - else { - Wait('stop'); - } - } - }); - - // Load Inventory - url = GetBasePath('inventory') + $stateParams.inventory_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - $scope.inventory = data; - $scope.$emit('InventoryLoaded'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id + - ' GET returned status: ' + status }); - }); - - // start watching for real-time updates - if ($rootScope.removeWatchUpdateStatus) { - $rootScope.removeWatchUpdateStatus(); - } - $rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) { - var stat, group; - if (data.group_id) { - group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); - if (data.status === "failed" || data.status === "successful") { - if (data.group_id === $scope.selected_group_id || group) { - // job completed, fefresh all groups - $log.debug('Update completed. Refreshing the tree.'); - $scope.refreshGroups(); - } - } - else if (group) { - // incremental update, just update - $log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status); - stat = GetSyncStatusMsg({ - status: data.status, - has_inventory_sources: group.has_inventory_sources, - source: group.source - }); - $log.debug('changing tooltip to: ' + stat.tooltip); - group.status = data.status; - group.status_class = stat['class']; - group.status_tooltip = stat.tooltip; - group.launch_tooltip = stat.launch_tip; - group.launch_class = stat.launch_class; - } - } - }); - - // Load group on selection - function loadGroups(url) { - SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url }); - PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size }); - $scope.search(InventoryGroups.iterator, null, true, false, true); - } - - $scope.refreshHosts = function() { - HostsReload({ - scope: hostScope, - group_id: $scope.selected_group_id, - inventory_id: $scope.inventory.id, - pageSize: hostScope.host_page_size - }); - }; - - $scope.refreshGroups = function() { - $scope.refreshHostsOnGroupRefresh = true; - $scope.search(InventoryGroups.iterator, null, true, false, true); - }; - - $scope.restoreSearch = function() { - // Restore search params and related stuff, plus refresh - // groups and hosts lists - SearchInit({ - scope: $scope, - set: PreviousSearchParams.set, - list: PreviousSearchParams.list, - url: PreviousSearchParams.defaultUrl, - iterator: PreviousSearchParams.iterator, - sort_order: PreviousSearchParams.sort_order, - setWidgets: false - }); - $scope.refreshHostsOnGroupRefresh = true; - $scope.search(InventoryGroups.iterator, null, true, false, true); - }; - - $scope.groupSelect = function(id) { - var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id }); - if($state.params.groups){ - groups.push($state.params.groups); - } - groups.push(group.id); - groups = groups.join(); - $state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false }); - loadGroups(group.related.children, group.id); - }; - - $scope.createGroup = function () { - PreviousSearchParams = Store('group_current_search_params'); - GroupsEdit({ - scope: $scope, - inventory_id: $scope.inventory.id, - group_id: $scope.selected_group_id, - mode: 'add' - }); - }; - - $scope.editGroup = function (id) { - PreviousSearchParams = Store('group_current_search_params'); - GroupsEdit({ - scope: $scope, - inventory_id: $scope.inventory.id, - group_id: id, - mode: 'edit' - }); - }; - - // Launch inventory sync - $scope.updateGroup = function (id) { - var group = Find({ list: $scope.groups, key: 'id', val: id }); - if (group) { - if (Empty(group.source)) { - // if no source, do nothing. - } else if (group.status === 'updating') { - Alert('Update in Progress', 'The inventory update process is currently running for group ' + - group.name + ' Click the button to monitor the status.', 'alert-info', null, null, null, null, true); - } else { - Wait('start'); - Rest.setUrl(group.related.inventory_source); - Rest.get() - .success(function (data) { - InventoryUpdate({ - scope: $scope, - url: data.related.update, - group_name: data.summary_fields.group.name, - group_source: data.source, - group_id: group.id, - }); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + - group.related.inventory_source + ' GET returned status: ' + status }); - }); - } - } - }; - - $scope.cancelUpdate = function (id) { - GroupsCancelUpdate({ scope: $scope, id: id }); - }; - - $scope.viewUpdateStatus = function (id) { - ViewUpdateStatus({ - scope: $scope, - group_id: id - }); - }; - - $scope.copyGroup = function(id) { - PreviousSearchParams = Store('group_current_search_params'); - GroupsCopy({ - scope: $scope, - group_id: id - }); - }; - - $scope.deleteGroup = function (id) { - GroupsDelete({ - scope: $scope, - group_id: id, - inventory_id: $scope.inventory.id - }); - }; - - $scope.editInventoryProperties = function () { - // EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id }); - $location.path('/inventories/' + $scope.inventory.id + '/'); - }; - - hostScope.createHost = function () { - HostsEdit({ - host_scope: hostScope, - group_scope: $scope, - mode: 'add', - host_id: null, - selected_group_id: $scope.selected_group_id, - inventory_id: $scope.inventory.id - }); - }; - - hostScope.editHost = function (host_id) { - HostsEdit({ - host_scope: hostScope, - group_scope: $scope, - mode: 'edit', - host_id: host_id, - inventory_id: $scope.inventory.id - }); - }; - - hostScope.deleteHost = function (host_id, host_name) { - HostsDelete({ - parent_scope: $scope, - host_scope: hostScope, - host_id: host_id, - host_name: host_name - }); - }; - - hostScope.copyHost = function(id) { - PreviousSearchParams = Store('group_current_search_params'); - HostsCopy({ - group_scope: $scope, - host_scope: hostScope, - host_id: id - }); - }; - - /*hostScope.restoreSearch = function() { - SearchInit({ - scope: hostScope, - set: PreviousSearchParams.set, - list: PreviousSearchParams.list, - url: PreviousSearchParams.defaultUrl, - iterator: PreviousSearchParams.iterator, - sort_order: PreviousSearchParams.sort_order, - setWidgets: false - }); - hostScope.search('host'); - };*/ - - hostScope.toggleHostEnabled = function (host_id, external_source) { - ToggleHostEnabled({ - parent_scope: $scope, - host_scope: hostScope, - host_id: host_id, - external_source: external_source - }); - }; - - hostScope.showJobSummary = function (job_id) { - ShowJobSummary({ - job_id: job_id - }); - }; - - $scope.showGroupHelp = function (params) { - var opts = { - defn: InventoryGroupsHelp - }; - if (params) { - opts.autoShow = params.autoShow || false; - } - HelpDialog(opts); - } -; - $scope.showHosts = function (group_id, show_failures) { - // Clicked on group - if (group_id !== null) { - Wait('start'); - hostScope.show_failures = show_failures; - $scope.groupSelect(group_id); - hostScope.hosts = []; - $scope.show_failures = show_failures; // turn on failed hosts - // filter in hosts view - } else { - Wait('stop'); - } - }; - - if ($scope.removeGroupDeleteCompleted) { - $scope.removeGroupDeleteCompleted(); - } - $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', - function() { - $scope.refreshGroups(); - } - ); -} - -export default [ - '$log', '$scope', '$rootScope', '$location', - '$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait', - 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', - 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', - 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', - 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', - 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', - 'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary', - 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy', - 'HostsCopy', '$stateParams', InventoriesManage, -]; diff --git a/awx/ui/client/src/inventory/main.js b/awx/ui/client/src/inventory/main.js deleted file mode 100644 index 0689a2d726..0000000000 --- a/awx/ui/client/src/inventory/main.js +++ /dev/null @@ -1,10 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default -angular.module('inventory', [ -]) From 66b96ced38d84b9f5a3485ece47523d60dd008d5 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Fri, 18 Mar 2016 10:37:26 -0400 Subject: [PATCH 06/33] Manage hosts directive set up w/ route --- .../manage/inventory-manage.partial.html | 5 +++- awx/ui/client/src/inventories/manage/main.js | 5 ++++ .../manage-groups.controller.html | 0 .../manage-groups.directive.html | 0 .../manage-groups/manage-groups.partial.html | 0 .../manage-hosts/manage-hosts.controller.js | 20 +++++++++++++ .../manage-hosts/manage-hosts.directive.js | 25 ++++++++++++++++ .../manage-hosts/manage-hosts.partial.html | 2 ++ .../manage/manage-hosts/manage-hosts.route.js | 29 +++++++++++++++++++ 9 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/manage-groups.controller.html create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/manage-groups.directive.html create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.partial.html b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html index ecae801c20..816f2fb040 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.partial.html +++ b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html @@ -1,5 +1,8 @@
-
+ +
In the ui view + +
diff --git a/awx/ui/client/src/inventories/manage/main.js b/awx/ui/client/src/inventories/manage/main.js index 7bd839ecc1..14bcb8a5e5 100644 --- a/awx/ui/client/src/inventories/manage/main.js +++ b/awx/ui/client/src/inventories/manage/main.js @@ -7,8 +7,13 @@ import route from './inventory-manage.route'; import controller from './inventory-manage.controller'; +import manageHostsDirective from './manage-hosts/manage-hosts.directive'; +import manageHostsRoute from './manage-hosts/manage-hosts.route'; + export default angular.module('inventoryManage', []) + .directive('manageHosts', manageHostsDirective) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(route); + $stateExtender.addState(manageHostsRoute); }]); diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.controller.html b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.controller.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.directive.html b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.directive.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js new file mode 100644 index 0000000000..01dda998c1 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js @@ -0,0 +1,20 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +function manageHostDirectiveController($q, $rootScope, $scope, $state, + $stateParams, $compile, Rest, ProcessErrors, + CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, + PaginateInit, GetRootGroups) { + + var vm = this; + + }; + +export default ['$q', '$rootScope', '$scope', '$state', '$stateParams', + 'ScopePass', '$compile', 'Rest', 'ProcessErrors', 'CreateDialog', + 'GetBasePath', 'Wait', 'generateList', 'GroupList', 'SearchInit', + 'PaginateInit', 'GetRootGroups', manageHostDirectiveController +]; diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js new file mode 100644 index 0000000000..531e965ee9 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js @@ -0,0 +1,25 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/* jshint unused: vars */ +import manageHostsController from './manage-hosts.controller'; + +export default ['templateUrl', + function(templateUrl) { + return { + restrict: 'EA', + scope: true, + replace: true, + templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'), + link: function(scope, element, attrs) { + + }, + // controller: manageHostsController, + // controllerAs: 'vm', + // bindToController: true + }; + } +]; diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html new file mode 100644 index 0000000000..a8016b0ba6 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html @@ -0,0 +1,2 @@ +
This is the manage hosts directive. +
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js new file mode 100644 index 0000000000..c8de0182be --- /dev/null +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js @@ -0,0 +1,29 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import { + templateUrl +} from '../../../shared/template-url/template-url.factory'; + +export default { + name: 'inventoryManage.manageHosts', + route: '/managehosts', + template: 'SOMETHING', + + // data: { + // activityStream: true, + // activityStreamTarget: 'inventory', + // activityStreamId: 'inventory_id' + // }, + // ncyBreadcrumb: { + // label: "INVENTORY MANAGE" + // }, + // resolve: { + // features: ['FeaturesService', function(FeaturesService) { + // return FeaturesService.get(); + // }] + // }, +}; From 632f3ca56781931e112ebac430ca6a0549ca6ba3 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Fri, 18 Mar 2016 15:22:41 -0400 Subject: [PATCH 07/33] More route work and directive --- awx/ui/client/src/helpers/Hosts.js | 8 ++++--- .../manage/inventory-manage.partial.html | 5 ++--- awx/ui/client/src/inventories/manage/main.js | 10 ++++----- .../inventories/manage/manage-hosts/main.js | 13 +++++++++++ .../manage-hosts/manage-hosts.controller.js | 12 +++++----- .../manage-hosts/manage-hosts.directive.js | 6 ++--- .../manage/manage-hosts/manage-hosts.route.js | 22 +++++++++++++------ awx/ui/client/src/shared/Utilities.js | 18 ++++++++++++++- 8 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/main.js diff --git a/awx/ui/client/src/helpers/Hosts.js b/awx/ui/client/src/helpers/Hosts.js index f55b1199d2..1e4e22bec7 100644 --- a/awx/ui/client/src/helpers/Hosts.js +++ b/awx/ui/client/src/helpers/Hosts.js @@ -437,12 +437,14 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, .factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', - 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', + 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', 'ScopePass', function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON, - ParseVariableString, CreateDialog, TextareaResize) { + ParseVariableString, CreateDialog, TextareaResize, ScopePass) { return function(params) { - + ScopePass.set(params); + var passing = ScopePass.get(); + console.info(passing); var parent_scope = params.host_scope, group_scope = params.group_scope, host_id = params.host_id, diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.partial.html b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html index 816f2fb040..c04eb31552 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.partial.html +++ b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html @@ -1,7 +1,6 @@
- -
In the ui view - +
+
diff --git a/awx/ui/client/src/inventories/manage/main.js b/awx/ui/client/src/inventories/manage/main.js index 14bcb8a5e5..c33658777f 100644 --- a/awx/ui/client/src/inventories/manage/main.js +++ b/awx/ui/client/src/inventories/manage/main.js @@ -11,9 +11,9 @@ import manageHostsDirective from './manage-hosts/manage-hosts.directive'; import manageHostsRoute from './manage-hosts/manage-hosts.route'; export default - angular.module('inventoryManage', []) +angular.module('inventoryManage', []) .directive('manageHosts', manageHostsDirective) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - $stateExtender.addState(manageHostsRoute); - }]); + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + $stateExtender.addState(manageHostsRoute); + }]); diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/main.js b/awx/ui/client/src/inventories/manage/manage-hosts/main.js new file mode 100644 index 0000000000..3815d1bc98 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/manage-hosts/main.js @@ -0,0 +1,13 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './manage-hosts.route'; + +export default + angular.module('manageHosts', []) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js index 01dda998c1..3f38abfaaf 100644 --- a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js @@ -5,16 +5,18 @@ *************************************************/ function manageHostDirectiveController($q, $rootScope, $scope, $state, - $stateParams, $compile, Rest, ProcessErrors, + $stateParams, $compile, ScopePass, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit, GetRootGroups) { - var vm = this; + var vm = this; + console.info(ScopePass); - }; +}; export default ['$q', '$rootScope', '$scope', '$state', '$stateParams', - 'ScopePass', '$compile', 'Rest', 'ProcessErrors', 'CreateDialog', + 'ScopePass', '$compile', 'ScopePass', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList', 'SearchInit', - 'PaginateInit', 'GetRootGroups', manageHostDirectiveController + 'PaginateInit', 'GetRootGroups', + manageHostDirectiveController ]; diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js index 531e965ee9..734d043019 100644 --- a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.directive.js @@ -17,9 +17,9 @@ export default ['templateUrl', link: function(scope, element, attrs) { }, - // controller: manageHostsController, - // controllerAs: 'vm', - // bindToController: true + controller: manageHostsController, + controllerAs: 'vm', + bindToController: true }; } ]; diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js index c8de0182be..ec9679fccb 100644 --- a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js +++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js @@ -4,23 +4,31 @@ * All Rights Reserved *************************************************/ -import { - templateUrl -} from '../../../shared/template-url/template-url.factory'; +// import { +// templateUrl +// } from '../../../shared/template-url/template-url.factory'; + +import manageHostDirectiveController from './manage-hosts.controller' export default { name: 'inventoryManage.manageHosts', route: '/managehosts', - template: 'SOMETHING', + //template: '
SOMETHING
', + views: { + "manage@inventoryManage" : { + template: '
the template from route
' + } + }, // data: { // activityStream: true, // activityStreamTarget: 'inventory', // activityStreamId: 'inventory_id' // }, - // ncyBreadcrumb: { - // label: "INVENTORY MANAGE" - // }, + ncyBreadcrumb: { + label: "INVENTORY MANAGE" + }, + controller: manageHostDirectiveController, // resolve: { // features: ['FeaturesService', function(FeaturesService) { // return FeaturesService.get(); diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index 434526cd7d..3190bb775e 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -871,4 +871,20 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) }; } -]); +]) +.factory('ScopePass', function() { + var savedData = {} + + function set(data) { + savedData = data; + } + + function get() { + return savedData; + } + + return { + set: set, + get: get + } +}); From d6c13043bd2e14eca04cfd2a1166c75ac31e1b05 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 21 Mar 2016 09:11:02 -0400 Subject: [PATCH 08/33] Inventory modularization, manage inventory groups/hosts Hosts mostly working, groups directive scaffold in place, rerouting. Groups edit mostly working. Source populating in group edit. Pre cleanup. Cleanup and fresh routing fixed. --- awx/ui/client/src/helpers/Hosts.js | 8 +- .../manage/inventory-manage.controller.js | 56 +- .../manage/inventory-manage.partial.html | 7 +- awx/ui/client/src/inventories/manage/main.js | 12 +- .../manage-groups.directive.controller.js | 540 ++++++++++++++++++ .../directive/manage-groups.directive.js | 25 + .../manage-groups.directive.partial.html | 18 + .../inventories/manage/manage-groups/main.js | 16 + .../manage-groups.controller.html | 0 .../manage-groups.directive.html | 0 .../manage-groups/manage-groups.partial.html | 5 + .../manage-groups/manage-groups.route.js | 46 ++ .../manage-hosts.directive.controller.js | 186 ++++++ .../{ => directive}/manage-hosts.directive.js | 14 +- .../manage-hosts.directive.partial.html | 17 + .../inventories/manage/manage-hosts/main.js | 7 +- .../manage-hosts/manage-hosts.controller.js | 22 - .../manage-hosts/manage-hosts.partial.html | 7 +- .../manage/manage-hosts/manage-hosts.route.js | 59 +- .../src/inventories/manage/manage.block.less | 6 + awx/ui/client/src/shared/Utilities.js | 6 +- awx/ui/client/src/shared/form-generator.js | 15 +- 22 files changed, 979 insertions(+), 93 deletions(-) create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/main.js delete mode 100644 awx/ui/client/src/inventories/manage/manage-groups/manage-groups.controller.html delete mode 100644 awx/ui/client/src/inventories/manage/manage-groups/manage-groups.directive.html create mode 100644 awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js rename awx/ui/client/src/inventories/manage/manage-hosts/{ => directive}/manage-hosts.directive.js (57%) create mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html delete mode 100644 awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.controller.js create mode 100644 awx/ui/client/src/inventories/manage/manage.block.less diff --git a/awx/ui/client/src/helpers/Hosts.js b/awx/ui/client/src/helpers/Hosts.js index 1e4e22bec7..6a7b864e02 100644 --- a/awx/ui/client/src/helpers/Hosts.js +++ b/awx/ui/client/src/helpers/Hosts.js @@ -437,14 +437,12 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, .factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', - 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', 'ScopePass', + 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', 'ParamPass', function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON, - ParseVariableString, CreateDialog, TextareaResize, ScopePass) { + ParseVariableString, CreateDialog, TextareaResize, ParamPass) { return function(params) { - ScopePass.set(params); - var passing = ScopePass.get(); - console.info(passing); + var parent_scope = params.host_scope, group_scope = params.group_scope, host_id = params.host_id, diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.controller.js b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js index 58c50dfbe2..a5c215bb0a 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.controller.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js @@ -18,7 +18,7 @@ function InventoriesManage($log, $scope, $rootScope, $location, ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, EditInventoryProperties, ToggleHostEnabled, ShowJobSummary, InventoryGroupsHelp, HelpDialog, - GroupsCopy, HostsCopy, $stateParams) { + GroupsCopy, HostsCopy, $stateParams, ParamPass) { var PreviousSearchParams, url, @@ -335,22 +335,38 @@ function InventoriesManage($log, $scope, $rootScope, $location, $scope.createGroup = function () { PreviousSearchParams = Store('group_current_search_params'); - GroupsEdit({ + // GroupsEdit({ + // scope: $scope, + // inventory_id: $scope.inventory.id, + // group_id: $scope.selected_group_id, + // mode: 'add' + // }); + var params = { scope: $scope, inventory_id: $scope.inventory.id, group_id: $scope.selected_group_id, mode: 'add' - }); + } + ParamPass.set(params); + $state.go('inventoryManage.addGroup'); }; $scope.editGroup = function (id) { PreviousSearchParams = Store('group_current_search_params'); - GroupsEdit({ + // GroupsEdit({ + // scope: $scope, + // inventory_id: $scope.inventory.id, + // group_id: id, + // mode: 'edit' + // }); + var params = { scope: $scope, inventory_id: $scope.inventory.id, group_id: id, mode: 'edit' - }); + } + ParamPass.set(params); + $state.go('inventoryManage.editGroup', {group_id: id}); }; // Launch inventory sync @@ -416,24 +432,44 @@ function InventoriesManage($log, $scope, $rootScope, $location, }; hostScope.createHost = function () { - HostsEdit({ + // HostsEdit({ + // host_scope: hostScope, + // group_scope: $scope, + // mode: 'add', + // host_id: null, + // selected_group_id: $scope.selected_group_id, + // inventory_id: $scope.inventory.id + // }); + + var params = { host_scope: hostScope, group_scope: $scope, mode: 'add', host_id: null, selected_group_id: $scope.selected_group_id, inventory_id: $scope.inventory.id - }); + } + ParamPass.set(params); + $state.go('inventoryManage.addHost'); }; hostScope.editHost = function (host_id) { - HostsEdit({ + // HostsEdit({ + // host_scope: hostScope, + // group_scope: $scope, + // mode: 'edit', + // host_id: host_id, + // inventory_id: $scope.inventory.id + // }); + var params = { host_scope: hostScope, group_scope: $scope, mode: 'edit', host_id: host_id, inventory_id: $scope.inventory.id - }); + } + ParamPass.set(params); + $state.go('inventoryManage.editHost', {host_id: host_id}); }; hostScope.deleteHost = function (host_id, host_name) { @@ -526,5 +562,5 @@ export default [ 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', 'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary', 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy', - 'HostsCopy', '$stateParams', InventoriesManage, + 'HostsCopy', '$stateParams', 'ParamPass', InventoriesManage, ]; diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.partial.html b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html index c04eb31552..f465ef47c0 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.partial.html +++ b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html @@ -1,7 +1,5 @@
-
- -
+
@@ -12,9 +10,6 @@
- -
- \n"; //end of Form-header - html += "
"; - html += "
\n"; - html += "
\n"; //end of Form-header if (this.form.tabs) { var collection; From 23ae408e567159c879a52fecb2dfd0e5f4585f10 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Wed, 23 Mar 2016 15:40:21 -0400 Subject: [PATCH 09/33] Fixed merge wonkiness, added back in groups query string to route --- awx/ui/client/src/app.js | 71 ++----------------- .../manage/inventory-manage.route.js | 2 +- 2 files changed, 5 insertions(+), 68 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index aabeec0f4c..ffe729895f 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -55,10 +55,10 @@ import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects'; import OrganizationsList from './organizations/list/organizations-list.controller'; import OrganizationsAdd from './organizations/add/organizations-add.controller'; import OrganizationsEdit from './organizations/edit/organizations-edit.controller'; -import InventoriesAdd from './inventory/inventory-add.controller'; -import InventoriesEdit from './inventory/inventory-edit.controller'; -import InventoriesList from './inventory/inventory-list.controller'; -import InventoriesManage from './inventory/inventory-manage.controller'; +import InventoriesAdd from './inventories/add/inventory-add.controller'; +import InventoriesEdit from './inventories/edit/inventory-edit.controller'; +import InventoriesList from './inventories/list/inventory-list.controller'; +import InventoriesManage from './inventories/manage/inventory-manage.controller'; import {AdminsList} from './controllers/Admins'; import {UsersList, UsersAdd, UsersEdit} from './controllers/Users'; import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams'; @@ -373,69 +373,6 @@ var tower = angular.module('Tower', [ } }). - state('inventories', { - url: '/inventories', - templateUrl: urlPrefix + 'partials/inventories.html', - controller: InventoriesList, - data: { - activityStream: true, - activityStreamTarget: 'inventory' - }, - ncyBreadcrumb: { - label: "INVENTORIES" - }, - resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }] - } - }). - - state('inventories.add', { - url: '/add', - templateUrl: urlPrefix + 'partials/inventories.html', - controller: InventoriesAdd, - ncyBreadcrumb: { - parent: "inventories", - label: "CREATE INVENTORY" - }, - resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }] - } - }). - - state('inventories.edit', { - url: '/:inventory_id', - templateUrl: urlPrefix + 'partials/inventories.html', - controller: InventoriesEdit, - data: { - activityStreamId: 'inventory_id' - }, - resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }] - } - }). - - state('inventoryManage', { - url: '/inventories/:inventory_id/manage?groups', - templateUrl: urlPrefix + 'partials/inventory-manage.html', - controller: InventoriesManage, - data: { - activityStream: true, - activityStreamTarget: 'inventory', - activityStreamId: 'inventory_id' - }, - resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }] - } - }). - state('organizationAdmins', { url: '/organizations/:organization_id/admins', templateUrl: urlPrefix + 'partials/organizations.html', diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js index 1cfc9176ac..62306cd91b 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.route.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js @@ -9,7 +9,7 @@ import InventoriesManage from './inventory-manage.controller'; export default { name: 'inventoryManage', - route: '/inventories/:inventory_id/manage', + url: '/inventories/:inventory_id/manage?groups', templateUrl: templateUrl('inventories/manage/inventory-manage'), controller: InventoriesManage, data: { From 05b8538fde5993c19b3ff75e2b46b0aebc191618 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 25 Mar 2016 14:39:45 -0400 Subject: [PATCH 10/33] Job Detail - host event modal, refactor EventViewer into job-details/host-event/ module #1131 first predemo pass --- awx/ui/client/src/app.js | 2 + awx/ui/client/src/helpers/EventViewer.js | 568 ------------------ .../host-event-details.partial.html | 49 ++ .../host-event/host-event-json.partial.html | 2 + .../host-event/host-event-modal.partial.html | 36 ++ .../host-event/host-event-stdout.partial.html | 13 + .../host-event/host-event-timing.partial.html | 1 + .../host-event/host-event.block.less | 66 ++ .../host-event/host-event.controller.js | 71 +++ .../job-detail/host-event/host-event.route.js | 86 +++ .../client/src/job-detail/host-event/main.js | 21 + .../host-events/host-events.controller.js | 58 +- .../host-events/host-events.partial.html | 6 +- .../host-events/host-events.route.js | 9 +- .../src/job-detail/job-detail.controller.js | 15 +- .../src/job-detail/job-detail.partial.html | 6 +- .../src/job-detail/job-detail.service.js | 74 ++- awx/ui/client/src/job-detail/main.js | 4 +- awx/ui/client/src/partials/eventviewer.html | 34 -- .../src/shared/layouts/one-plus-two.less | 3 +- 20 files changed, 447 insertions(+), 677 deletions(-) delete mode 100644 awx/ui/client/src/helpers/EventViewer.js create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-details.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-json.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event.block.less create mode 100644 awx/ui/client/src/job-detail/host-event/host-event.controller.js create mode 100644 awx/ui/client/src/job-detail/host-event/host-event.route.js create mode 100644 awx/ui/client/src/job-detail/host-event/main.js delete mode 100644 awx/ui/client/src/partials/eventviewer.html diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e207eab59b..e045c0322d 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -211,6 +211,8 @@ var tower = angular.module('Tower', [ templateUrl: urlPrefix + 'partials/breadcrumb.html' }); + // route to the details pane of /job/:id/host-event/:eventId if no other child specified + $urlRouterProvider.when('/jobs/*/host-event/*', '/jobs/*/host-event/*/details') // $urlRouterProvider.otherwise("/home"); $urlRouterProvider.otherwise(function($injector){ var $state = $injector.get("$state"); diff --git a/awx/ui/client/src/helpers/EventViewer.js b/awx/ui/client/src/helpers/EventViewer.js deleted file mode 100644 index cb075fa5e9..0000000000 --- a/awx/ui/client/src/helpers/EventViewer.js +++ /dev/null @@ -1,568 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:EventViewer - * @description eventviewerhelper -*/ - -export default - angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition', 'HostsHelper']) - - .factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'Empty', 'EventAddPreFormattedText', - function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, Empty, EventAddPreFormattedText) { - return function(params) { - var parent_scope = params.scope, - url = params.url, - event_id = params.event_id, - parent_id = params.parent_id, - title = params.title, //optional - scope = parent_scope.$new(true), - index = params.index, - page, - current_event; - - if (scope.removeShowNextEvent) { - scope.removeShowNextEvent(); - } - scope.removeShowNextEvent = scope.$on('ShowNextEvent', function(e, data, show_event) { - scope.events = data; - $('#event-next-spinner').slideUp(200); - if (show_event === 'prev') { - showEvent(scope.events.length - 1); - } - else if (show_event === 'next') { - showEvent(0); - } - }); - - // show scope.events[idx] - function showEvent(idx) { - var show_tabs = false, elem, data; - - if (idx > scope.events.length - 1) { - GetEvent({ - scope: scope, - url: scope.next_event_set, - show_event: 'next' - }); - return; - } - - if (idx < 0) { - GetEvent({ - scope: scope, - url: scope.prev_event_set, - show_event: 'prev' - }); - return; - } - - data = scope.events[idx]; - current_event = idx; - - $('#status-form-container').empty(); - $('#results-form-container').empty(); - $('#timing-form-container').empty(); - $('#stdout-form-container').empty(); - $('#stderr-form-container').empty(); - $('#traceback-form-container').empty(); - $('#json-form-container').empty(); - $('#eventview-tabs li:eq(1)').hide(); - $('#eventview-tabs li:eq(2)').hide(); - $('#eventview-tabs li:eq(3)').hide(); - $('#eventview-tabs li:eq(4)').hide(); - $('#eventview-tabs li:eq(5)').hide(); - $('#eventview-tabs li:eq(6)').hide(); - - EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' }); - - if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) { - show_tabs = true; - $('#eventview-tabs li:eq(1)').show(); - } - - if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) { - show_tabs = true; - $('#eventview-tabs li:eq(2)').show(); - } - - if (data.stdout) { - show_tabs = true; - $('#eventview-tabs li:eq(3)').show(); - EventAddPreFormattedText({ - id: 'stdout-form-container', - val: data.stdout - }); - } - - if (data.stderr) { - show_tabs = true; - $('#eventview-tabs li:eq(4)').show(); - EventAddPreFormattedText({ - id: 'stderr-form-container', - val: data.stderr - }); - } - - if (data.traceback) { - show_tabs = true; - $('#eventview-tabs li:eq(5)').show(); - EventAddPreFormattedText({ - id: 'traceback-form-container', - val: data.traceback - }); - } - - show_tabs = true; - $('#eventview-tabs li:eq(6)').show(); - EventAddPreFormattedText({ - id: 'json-form-container', - val: JSON.stringify(data, null, 2) - }); - - if (!show_tabs) { - $('#eventview-tabs').hide(); - } - - elem = angular.element(document.getElementById('eventviewer-modal-dialog')); - $compile(elem)(scope); - } - - function setButtonMargin() { - var width = ($('.ui-dialog[aria-describedby="eventviewer-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#events-next-button').outerWidth() - 73; - $('#events-next-button').css({'margin-right': width + 'px'}); - } - - function addSpinner() { - var position; - if ($('#event-next-spinner').length > 0) { - $('#event-next-spinner').remove(); - } - position = $('#events-next-button').position(); - $('#events-next-button').after(''); - } - - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - Wait('stop'); - $('#eventviewer-modal-dialog').dialog('open'); - }); - - if (scope.removeJobReady) { - scope.removeJobReady(); - } - scope.removeEventReady = scope.$on('EventReady', function(e, data) { - var btns; - scope.events = data; - if (event_id) { - // find and show the selected event - data.every(function(row, idx) { - if (parseInt(row.id,10) === parseInt(event_id,10)) { - current_event = idx; - return false; - } - return true; - }); - } - else { - current_event = 0; - } - showEvent(current_event); - - btns = []; - if (scope.events.length > 1) { - btns.push({ - label: "Prev", - onClick: function () { - if (current_event - 1 === 0 && !scope.prev_event_set) { - $('#events-prev-button').prop('disabled', true); - } - if (current_event - 1 < scope.events.length - 1) { - $('#events-next-button').prop('disabled', false); - } - showEvent(current_event - 1); - }, - icon: "fa-chevron-left", - "class": "btn btn-primary", - id: "events-prev-button" - }); - btns.push({ - label: "Next", - onClick: function() { - if (current_event + 1 > 0) { - $('#events-prev-button').prop('disabled', false); - } - if (current_event + 1 >= scope.events.length - 1 && !scope.next_event_set) { - $('#events-next-button').prop('disabled', true); - } - showEvent(current_event + 1); - }, - icon: "fa-chevron-right", - "class": "btn btn-primary", - id: "events-next-button" - }); - } - btns.push({ - label: "OK", - onClick: function() { - scope.modalOK(); - }, - icon: "", - "class": "btn btn-primary", - id: "dialog-ok-button" - }); - - CreateDialog({ - scope: scope, - width: 675, - height: 600, - minWidth: 450, - callback: 'ModalReady', - id: 'eventviewer-modal-dialog', - // onResizeStop: resizeText, - title: ( (title) ? title : 'Host Event' ), - buttons: btns, - closeOnEscape: true, - onResizeStop: function() { - setButtonMargin(); - addSpinner(); - }, - onClose: function() { - try { - scope.$destroy(); - } - catch(e) { - //ignore - } - }, - onOpen: function() { - $('#eventview-tabs a:first').tab('show'); - $('#dialog-ok-button').focus(); - if (scope.events.length > 1 && current_event === 0 && !scope.prev_event_set) { - $('#events-prev-button').prop('disabled', true); - } - if ((current_event === scope.events.length - 1) && !scope.next_event_set) { - $('#events-next-button').prop('disabled', true); - } - if (scope.events.length > 1) { - setButtonMargin(); - addSpinner(); - } - } - }); - }); - - page = (index) ? Math.ceil((index+1)/50) : 1; - url += (/\/$/.test(url)) ? '?' : '&'; - url += (parent_id) ? 'page='+page +'&parent=' + parent_id + '&page_size=50&order=host_name,counter' : 'page_size=50&order=host_name,counter'; - - GetEvent({ - url: url, - scope: scope - }); - - scope.modalOK = function() { - $('#eventviewer-modal-dialog').dialog('close'); - scope.$destroy(); - }; - - }; - }]) - - .factory('GetEvent', ['Wait', 'Rest', 'ProcessErrors', - function(Wait, Rest, ProcessErrors) { - return function(params) { - var url = params.url, - scope = params.scope, - show_event = params.show_event, - results= []; - - if (show_event) { - $('#event-next-spinner').show(); - } - else { - Wait('start'); - } - - function getStatus(e) { - return (e.event === "runner_on_unreachable") ? "unreachable" : (e.event === "runner_on_skipped") ? 'skipped' : (e.failed) ? 'failed' : - (e.changed) ? 'changed' : 'ok'; - } - - Rest.setUrl(url); - Rest.get() - .success( function(data) { - - if(jQuery.isEmptyObject(data)) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get event ' + url + '. ' }); - - } - else { - scope.next_event_set = data.next; - scope.prev_event_set = data.previous; - data.results.forEach(function(event) { - var msg, key, event_data = {}; - if (event.event_data.res) { - if (typeof event.event_data.res !== 'object') { - // turn event_data.res into an object - msg = event.event_data.res; - event.event_data.res = {}; - event.event_data.res.msg = msg; - } - for (key in event.event_data) { - if (key !== "res") { - event.event_data.res[key] = event.event_data[key]; - } - } - if (event.event_data.res.ansible_facts) { - // don't show fact gathering results - event.event_data.res.task = "Gathering Facts"; - delete event.event_data.res.ansible_facts; - } - event.event_data.res.status = getStatus(event); - event_data = event.event_data.res; - } - else { - event.event_data.status = getStatus(event); - event_data = event.event_data; - } - // convert results to stdout - if (event_data.results && typeof event_data.results === "object" && Array.isArray(event_data.results)) { - event_data.stdout = ""; - event_data.results.forEach(function(row) { - event_data.stdout += row + "\n"; - }); - delete event_data.results; - } - if (event_data.invocation) { - for (key in event_data.invocation) { - event_data[key] = event_data.invocation[key]; - } - delete event_data.invocation; - } - event_data.play = event.play; - if (event.task) { - event_data.task = event.task; - } - event_data.created = event.created; - event_data.role = event.role; - event_data.host_id = event.host; - event_data.host_name = event.host_name; - if (event_data.host) { - delete event_data.host; - } - event_data.id = event.id; - event_data.parent = event.parent; - event_data.event = (event.event_display) ? event.event_display : event.event; - results.push(event_data); - }); - if (show_event) { - scope.$emit('ShowNextEvent', results, show_event); - } - else { - scope.$emit('EventReady', results); - } - } //else statement - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get event ' + url + '. GET returned: ' + status }); - }); - }; - }]) - - .factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) { - return function(params) { - var scope = params.scope, - id = params.id, - event = params.event, - section = params.section, - html = '', e; - - function parseObject(obj) { - // parse nested JSON objects. a mini version of parseJSON without references to the event form object. - var i, key, html = ''; - for (key in obj) { - if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") { - html += "" + key + ":" + obj[key] + ""; - } - else if (typeof obj[key] === "object" && Array.isArray(obj[key])) { - html += "" + key + ":["; - for (i = 0; i < obj[key].length; i++) { - html += obj[key][i] + ","; - } - html = html.replace(/,$/,''); - html += "]\n"; - } - else if (typeof obj[key] === "object") { - html += "" + key + ":\n\n" + parseObject(obj[key]) + "\n
\n\n"; - } - } - return html; - } - - function parseItem(itm, key, label) { - var i, html = ''; - if (Empty(itm)) { - // exclude empty items - } - else if (typeof itm === "boolean" || typeof itm === "number" || typeof itm === "string") { - html += "" + label + ":"; - if (key === "status") { - html += " " + itm; - } - else if (key === "start" || key === "end" || key === "created") { - if (!/Z$/.test(itm)) { - itm = itm.replace(/\ /,'T') + 'Z'; - html += $filter('longDate')(itm); - } - else { - html += $filter('longDate')(itm); - } - } - else if (key === "host_name" && event.host_id) { - html += "" + itm + ""; - } - else { - if( typeof itm === "string"){ - if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){ - itm = $filter('sanitize')(itm); - } - } - html += "" + itm + ""; - } - - html += "\n"; - } - else if (typeof itm === "object" && Array.isArray(itm)) { - html += "" + label + ":["; - for (i = 0; i < itm.length; i++) { - html += itm[i] + ","; - } - html = html.replace(/,$/,''); - html += "]\n"; - } - else if (typeof itm === "object") { - html += "" + label + ":\n\n" + parseObject(itm) + "\n
\n\n"; - } - return html; - } - - function parseJSON(obj) { - var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = ""; - if (typeof obj === "object") { - html += "\n"; - html += "\n"; - keys = []; - for (key in EventsViewerForm.fields) { - if (EventsViewerForm.fields[key].section === section) { - keys.push(key); - } - } - keys.forEach(function(key) { - var h, label; - label = EventsViewerForm.fields[key].label; - h = parseItem(obj[key], key, label); - if (h) { - html += h; - found = true; - } - }); - if (section === 'Results') { - // Add to result fields that might not be found in the form object. - for (key in obj) { - h = ''; - if (key !== 'host_id' && key !== 'parent' && key !== 'event' && key !== 'src' && key !== 'md5sum' && - key !== 'stdout' && key !== 'traceback' && key !== 'stderr' && key !== 'cmd' && key !=='changed' && key !== "verbose_override" && - key !== 'feature_result' && key !== 'warnings') { - if (!EventsViewerForm.fields[key]) { - h = parseItem(obj[key], key, key); - if (h) { - html += h; - found = true; - } - } - } else if (key === 'cmd') { - // only show cmd if it's a cmd that was run - if (!EventsViewerForm.fields[key] && obj[key].length > 0) { - // include the label head Shell Command instead of CMD in the modal - if(typeof(obj[key]) === 'string'){ - obj[key] = [obj[key]]; - } - string_cmd += obj[key].join(" "); - h = parseItem(string_cmd, key, "Shell Command"); - if (h) { - html += h; - found = true; - } - } - } else if (key === 'warnings') { - if (!EventsViewerForm.fields[key] && obj[key].length > 0) { - if(typeof(obj[key]) === 'string'){ - obj[key] = [obj[key]]; - } - string_warnings += obj[key].join(" "); - h = parseItem(string_warnings, key, "Warnings"); - if (h) { - html += h; - found = true; - } - } - } - } - } - html += "\n"; - html += "
\n"; - } - return (found) ? html : ''; - } - html = parseJSON(event); - - e = angular.element(document.getElementById(id)); - e.empty(); - if (html) { - e.html(html); - $compile(e)(scope); - } - return (html) ? true : false; - }; - }]) - - .factory('EventAddTextarea', [ function() { - return function(params) { - var container_id = params.container_id, - val = params.val, - fld_id = params.fld_id, - html; - html = "
\n" + - "" + - "
\n"; - $('#' + container_id).empty().html(html); - }; - }]) - - .factory('EventAddPreFormattedText', ['$filter', function($filter) { - return function(params) { - var id = params.id, - val = params.val, - html; - if( typeof val === "string"){ - if(val.indexOf('<') > -1 || val.indexOf('>') > -1){ - val = $filter('sanitize')(val); - } - } - html = "
" + val + "
\n"; - $('#' + id).empty().html(html); - }; - }]); diff --git a/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html new file mode 100644 index 0000000000..66a60fcec9 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html @@ -0,0 +1,49 @@ +
+
+
EVENT
+ +
+ +
+ STATUS + + + + + {{event.status || "No result found"}} + +
+
+ ID + {{event.id || "No result found"}} +
+
+ CREATED + {{event.created || "No result found"}} +
+
+ PLAY + {{event.play || "No result found"}} +
+
+ TASK + {{event.task || "No result found"}} +
+
+ MODULE + {{event.event_data.res.invocation.module_name || "No result found"}} +
+
+
+
RESULTS
+ +
+ {{key}} + {{value}} +
+
+ diff --git a/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html new file mode 100644 index 0000000000..a574043dbd --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html new file mode 100644 index 0000000000..334aa78261 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html new file mode 100644 index 0000000000..436c25262a --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html @@ -0,0 +1,13 @@ +
+
+
STANDARD OUT
+ +
+ +
\ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html new file mode 100644 index 0000000000..06171bd1c5 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html @@ -0,0 +1 @@ +
timing
\ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less new file mode 100644 index 0000000000..73761cbb86 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event.block.less @@ -0,0 +1,66 @@ +@import "awx/ui/client/src/shared/branding/colors.less"; +@import "awx/ui/client/src/shared/branding/colors.default.less"; +@import "awx/ui/client/src/shared/layouts/one-plus-two.less"; + +.HostEvent .modal-footer{ + border: 0; + margin-top: 0px; + padding-top: 5px; +} +.HostEvent-controls{ + float: right; +} +.HostEvent-status--ok{ + color: @green; +} +.HostEvent-status--unreachable{ + color: @unreachable; +} +.HostEvent-status--changed{ + color: @changed; +} +.HostEvent-status--failed{ + color: @warning; +} +.HostEvent-status--skipped{ + color: @skipped; +} +.HostEvent-title{ + color: @default-interface-txt; + font-weight: 600; +} +.HostEvent .modal-body{ + max-height: 500px; + overflow-y: auto; + padding: 20px; +} +.HostEvent-nav{ + padding-top: 12px; + padding-bottom: 12px; +} +.HostEvent-field{ + margin-bottom: 8px; +} +.HostEvent-field--label{ + .OnePlusTwo-left--detailsLabel; + width: 80px; + margin-right: 20px; + font-size: 12px; +} +.HostEvent-field{ + .OnePlusTwo-left--detailsRow; +} +.HostEvent-field--content{ + .OnePlusTwo-left--detailsContent; +} +.HostEvent-details--left, .HostEvent-details--right{ + vertical-align:top; + width:270px; + display: inline-block; + +} +.HostEvent-details--right{ + .HostEvent-field--label{ + width: 170px; + } +} diff --git a/awx/ui/client/src/job-detail/host-event/host-event.controller.js b/awx/ui/client/src/job-detail/host-event/host-event.controller.js new file mode 100644 index 0000000000..a1485f4714 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event.controller.js @@ -0,0 +1,71 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + export default + ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event', + function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){ + // Avoid rendering objects in the details fieldset + // ng-if="processResults(value)" via host-event-details.partial.html + $scope.processResults = function(value){ + if (typeof value == 'object'){return false} + else {return true} + }; + + var codeMirror = function(){ + var el = $('#HostEvent-json')[0]; + var editor = CodeMirror.fromTextArea(el, { + lineNumbers: true, + mode: {name: "javascript", json: true} + }); + editor.getDoc().setValue(JSON.stringify($scope.json, null, 4)); + }; + + $scope.getActiveHostIndex = function(){ + var result = $scope.hostResults.filter(function( obj ) { + return obj.id == $scope.event.id; + }); + return $scope.hostResults.indexOf(result[0]) + }; + + $scope.showPrev = function(){ + return $scope.getActiveHostIndex() != 0 + }; + + $scope.showNext = function(){ + return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1]) + }; + + $scope.goNext = function(){ + var index = $scope.getActiveHostIndex() + 1; + var id = $scope.hostResults[index].id; + $state.go('jobDetail.host-event.details', {eventId: id}) + }; + + $scope.goPrevious = function(){ + var index = $scope.getActiveHostIndex() - 1; + var id = $scope.hostResults[index].id; + $state.go('jobDetail.host-event.details', {eventId: id}) + }; + + var init = function(){ + $scope.event = event.data.results[0]; + $scope.event.created = moment($scope.event.created).format(); + $scope.processEventStatus = JobDetailService.processEventStatus($scope.event); + $scope.hostResults = $stateParams.hostResults; + $scope.json = JobDetailService.processJson($scope.event); + if ($state.current.name == 'jobDetail.host-event.json'){ + codeMirror(); + } + try { + $scope.stdout = $scope.event.event_data.res.stdout + } + catch(err){ + $scope.sdout = null; + } + $('#HostEvent').modal('show'); + }; + init(); + }]; \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event.route.js b/awx/ui/client/src/job-detail/host-event/host-event.route.js new file mode 100644 index 0000000000..7d4f1cc011 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event.route.js @@ -0,0 +1,86 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import {templateUrl} from '../../shared/template-url/template-url.factory'; + +var hostEventModal = { + name: 'jobDetail.host-event', + url: '/host-event/:eventId', + controller: 'HostEventController', + params:{ + hostResults: { + value: null, + squash: false, + } + }, + templateUrl: templateUrl('job-detail/host-event/host-event-modal'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }], + event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { + return JobDetailService.getRelatedJobEvents($stateParams.id, { + id: $stateParams.eventId + }).success(function(res){ return res.results[0]}) + }] + }, + onExit: function($state){ + // close the modal + // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X" + $('#HostEvent').modal('hide'); + // hacky way to handle user browsing away via URL bar + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + } + } + + var hostEventDetails = { + name: 'jobDetail.host-event.details', + url: '/details', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-details'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + } + + var hostEventJson = { + name: 'jobDetail.host-event.json', + url: '/json', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-json'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }; + var hostEventTiming = { + name: 'jobDetail.host-event.timing', + url: '/timing', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-timing'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }; + var hostEventStdout = { + name: 'jobDetail.host-event.stdout', + url: '/stdout', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-stdout'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }; + + export {hostEventDetails, hostEventJson, hostEventTiming, hostEventStdout, hostEventModal} \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/main.js b/awx/ui/client/src/job-detail/host-event/main.js new file mode 100644 index 0000000000..c2b82530a1 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/main.js @@ -0,0 +1,21 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import {hostEventModal, hostEventDetails, hostEventTiming, + hostEventJson, hostEventStdout} from './host-event.route'; + import controller from './host-event.controller'; + + export default + angular.module('jobDetail.hostEvent', []) + .controller('HostEventController', controller) + + .run(['$stateExtender', function($stateExtender){ + $stateExtender.addState(hostEventModal); + $stateExtender.addState(hostEventDetails); + $stateExtender.addState(hostEventTiming); + $stateExtender.addState(hostEventJson); + $stateExtender.addState(hostEventStdout); + }]); \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js index a3a1c8faaf..732ceab45a 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js +++ b/awx/ui/client/src/job-detail/host-events/host-events.controller.js @@ -6,13 +6,14 @@ export default ['$stateParams', '$scope', '$rootScope', '$state', 'Wait', - 'JobDetailService', 'CreateSelect2', + 'JobDetailService', 'CreateSelect2', 'hosts', function($stateParams, $scope, $rootScope, $state, Wait, - JobDetailService, CreateSelect2){ + JobDetailService, CreateSelect2, hosts){ // pagination not implemented yet, but it'll depend on this $scope.page_size = $stateParams.page_size; + $scope.processEventStatus = JobDetailService.processEventStatus; $scope.activeFilter = $stateParams.filter || null; $scope.search = function(){ @@ -39,6 +40,7 @@ var filter = function(filter){ Wait('start'); + if (filter == 'all'){ return JobDetailService.getRelatedJobEvents($stateParams.id, { host_name: $stateParams.hostName, @@ -104,39 +106,6 @@ filter($('.HostEvents-select').val()); }); - $scope.processStatus = function(event, $index){ - // the stack for which status to display is - // unreachable > failed > changed > ok - // uses the API's runner events and convenience properties .failed .changed to determine status. - // see: job_event_callback.py - if (event.event == 'runner_on_unreachable'){ - $scope.results[$index].status = 'Unreachable'; - return 'HostEvents-status--unreachable' - } - // equiv to 'runner_on_error' && 'runner on failed' - if (event.failed){ - $scope.results[$index].status = 'Failed'; - return 'HostEvents-status--failed' - } - // catch the changed case before ok, because both can be true - if (event.changed){ - $scope.results[$index].status = 'Changed'; - return 'HostEvents-status--changed' - } - if (event.event == 'runner_on_ok'){ - $scope.results[$index].status = 'OK'; - return 'HostEvents-status--ok' - } - if (event.event == 'runner_on_skipped'){ - $scope.results[$index].status = 'Skipped'; - return 'HostEvents-status--skipped' - } - else{ - // study a case where none of these apply - } - }; - - var init = function(){ // create filter dropdown CreateSelect2({ @@ -145,6 +114,7 @@ }); // process the filter if one was passed if ($stateParams.filter){ + Wait('start'); filter($stateParams.filter).success(function(res){ $scope.results = res.results; Wait('stop'); @@ -152,25 +122,11 @@ });; } else{ - Wait('start'); - JobDetailService.getRelatedJobEvents($stateParams.id, { - host_name: $stateParams.hostName, - page_size: $stateParams.page_size}) - .success(function(res){ - $scope.pagination = res; - $scope.results = res.results; - Wait('stop'); - $('#HostEvents').modal('show'); - - }); + $scope.results = hosts.data.results; + $('#HostEvents').modal('show'); } }; - $scope.goBack = function(){ - // go back to the job details state - // we're leaning on $stateProvider's onExit to close the modal - $state.go('jobDetail'); - }; init(); diff --git a/awx/ui/client/src/job-detail/host-events/host-events.partial.html b/awx/ui/client/src/job-detail/host-events/host-events.partial.html index ff2d21714a..1b7702ab4a 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.partial.html +++ b/awx/ui/client/src/job-detail/host-events/host-events.partial.html @@ -5,7 +5,7 @@
HOST EVENTS -
@@ -37,7 +37,7 @@ - + {{event.status}} @@ -56,7 +56,7 @@
diff --git a/awx/ui/client/src/job-detail/host-events/host-events.route.js b/awx/ui/client/src/job-detail/host-events/host-events.route.js index ebb2bb7bdd..4e2c6d4e93 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.route.js +++ b/awx/ui/client/src/job-detail/host-events/host-events.route.js @@ -1,5 +1,5 @@ /************************************************* - * Copyright (c) 2015 Ansible, Inc. + * Copyright (c) 2016 Ansible, Inc. * * All Rights Reserved *************************************************/ @@ -25,6 +25,11 @@ export default { resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); - }] + }], + hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { + return JobDetailService.getRelatedJobEvents($stateParams.id, { + host_name: $stateParams.hostName + }).success(function(res){ return res.results[0]}) + }] } }; 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 1383f04c35..9bc6daf2fc 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -18,7 +18,7 @@ export default 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun', 'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit', 'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels', - 'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EventViewer', + 'EditSchedule', 'ParseTypeChange', 'JobDetailService', function( $location, $rootScope, $filter, $scope, $compile, $stateParams, $log, ClearScope, GetBasePath, Wait, ProcessErrors, @@ -27,7 +27,7 @@ export default SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, PlaybookRun, LoadPlays, LoadTasks, LoadHosts, HostsEdit, ParseVariableString, GetChoices, fieldChoices, - fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, EventViewer + fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, ) { ClearScope(); @@ -1119,17 +1119,6 @@ export default } }; - scope.viewHostResults = function(id) { - EventViewer({ - scope: scope, - url: scope.job.related.job_events, - parent_id: scope.selectedTask, - event_id: id, - index: this.$index, - title: 'Host Event' - }); - }; - if (scope.removeDeleteFinished) { scope.removeDeleteFinished(); } diff --git a/awx/ui/client/src/job-detail/job-detail.partial.html b/awx/ui/client/src/job-detail/job-detail.partial.html index 8daba354b5..c1bfd91fbc 100644 --- a/awx/ui/client/src/job-detail/job-detail.partial.html +++ b/awx/ui/client/src/job-detail/job-detail.partial.html @@ -343,7 +343,9 @@ - + @@ -422,7 +424,7 @@
{{ result.name }}{{ result.name }} + {{ result.name }}{{ result.name }} + {{ result.item }} {{ result.msg }}
- {{ host.name }} + {{ host.name }} {{ host.ok }} diff --git a/awx/ui/client/src/job-detail/job-detail.service.js b/awx/ui/client/src/job-detail/job-detail.service.js index 8597fff9f1..58249d9aa2 100644 --- a/awx/ui/client/src/job-detail/job-detail.service.js +++ b/awx/ui/client/src/job-detail/job-detail.service.js @@ -2,13 +2,83 @@ export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){ return { - /* + /* For ES6 it might be useful to set some default params here, e.g. getJobHostSummaries: function(id, page_size=200, order='host_name'){} without ES6, we'd have to supply defaults like this: this.page_size = params.page_size ? params.page_size : 200; - */ + */ + + // the the API passes through Ansible's event_data response + // we need to massage away the verbose and redundant properties + + processJson: function(data){ + // a deep copy + var result = $.extend(true, {}, data); + // configure fields to ignore + var ignored = [ + 'event_data', + 'related', + 'summary_fields', + 'url', + 'ansible_facts', + ]; + + // remove ignored properties + Object.keys(result).forEach(function(key, index){ + if (ignored.indexOf(key) > -1) { + delete result[key] + } + }); + + // flatten Ansible's passed-through response + result.event_data = {}; + Object.keys(data.event_data.res).forEach(function(key, index){ + if (ignored.indexOf(key) > -1) { + return + } + else{ + //console.log(key, data.event_data.res[key]) + result.event_data[key] = data.event_data.res[key]; + } + }); + + return result + }, + + processEventStatus: function(event){ + // Generate a helper class for job_event statuses + // the stack for which status to display is + // unreachable > failed > changed > ok + // uses the API's runner events and convenience properties .failed .changed to determine status. + // see: job_event_callback.py + if (event.event == 'runner_on_unreachable'){ + event.status = 'Unreachable'; + return 'HostEvents-status--unreachable' + } + // equiv to 'runner_on_error' && 'runner on failed' + if (event.failed){ + event.status = 'Failed'; + return 'HostEvents-status--failed' + } + // catch the changed case before ok, because both can be true + if (event.changed){ + event.status = 'Changed'; + return 'HostEvents-status--changed' + } + if (event.event == 'runner_on_ok'){ + event.status = 'OK'; + return 'HostEvents-status--ok' + } + if (event.event == 'runner_on_skipped'){ + event.status = 'Skipped'; + return 'HostEvents-status--skipped' + } + else{ + // study a case where none of these apply + } + }, // GET events related to a job run // e.g. diff --git a/awx/ui/client/src/job-detail/main.js b/awx/ui/client/src/job-detail/main.js index 8a9fc30aff..f497b76677 100644 --- a/awx/ui/client/src/job-detail/main.js +++ b/awx/ui/client/src/job-detail/main.js @@ -8,10 +8,12 @@ import route from './job-detail.route'; import controller from './job-detail.controller'; import service from './job-detail.service'; import hostEvents from './host-events/main'; +import hostEvent from './host-event/main'; export default angular.module('jobDetail', [ - hostEvents.name + hostEvents.name, + hostEvent.name ]) .controller('JobDetailController', controller) .service('JobDetailService', service) diff --git a/awx/ui/client/src/partials/eventviewer.html b/awx/ui/client/src/partials/eventviewer.html deleted file mode 100644 index 941e9d80d6..0000000000 --- a/awx/ui/client/src/partials/eventviewer.html +++ /dev/null @@ -1,34 +0,0 @@ - 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 87c44fe056..a296af7127 100644 --- a/awx/ui/client/src/shared/layouts/one-plus-two.less +++ b/awx/ui/client/src/shared/layouts/one-plus-two.less @@ -61,7 +61,8 @@ } .OnePlusTwo-left--detailsLabel { - width: 140px; + word-wrap: break-word; + width: 170px; display: inline-block; color: @default-interface-txt; text-transform: uppercase; From 45ec13e5d56047986ea04e11a42d9c93016aa18a Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Sun, 27 Mar 2016 20:35:15 -0400 Subject: [PATCH 11/33] yoink EventViewer dependency #1131 --- awx/ui/client/src/app.js | 1 - awx/ui/client/src/helpers.js | 2 -- .../src/job-detail/host-event/host-event-modal.partial.html | 2 +- awx/ui/client/src/job-detail/job-detail.controller.js | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e045c0322d..3c8cdce00e 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -180,7 +180,6 @@ var tower = angular.module('Tower', [ 'LogViewerStatusDefinition', 'StandardOutHelper', 'LogViewerOptionsDefinition', - 'EventViewerHelper', 'JobDetailHelper', 'SocketIO', 'lrInfiniteScroll', diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index e8190ea50e..aae8a17225 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -9,7 +9,6 @@ import './lists'; import Children from "./helpers/Children"; import Credentials from "./helpers/Credentials"; -import EventViewer from "./helpers/EventViewer"; import Events from "./helpers/Events"; import Groups from "./helpers/Groups"; import Hosts from "./helpers/Hosts"; @@ -42,7 +41,6 @@ import ActivityStreamHelper from "./helpers/ActivityStream"; export { Children, Credentials, - EventViewer, Events, Groups, Hosts, diff --git a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html index 334aa78261..afcb388360 100644 --- a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html +++ b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html @@ -13,7 +13,7 @@
- + 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 9bc6daf2fc..d06fa5881f 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -27,7 +27,7 @@ export default SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, PlaybookRun, LoadPlays, LoadTasks, LoadHosts, HostsEdit, ParseVariableString, GetChoices, fieldChoices, - fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, + fieldLabels, EditSchedule, ParseTypeChange, JobDetailService ) { ClearScope(); From 488b3333871724fc85be73c52d6d9ef20d6f1a9e Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Sun, 27 Mar 2016 21:07:51 -0400 Subject: [PATCH 12/33] fix incorrect color var, yoink eventviewer.html #1131 --- awx/ui/client/src/job-detail/host-event/host-event.block.less | 2 +- awx/ui/client/src/job-detail/host-events/host-events.block.less | 2 +- awx/ui/client/src/job-detail/job-detail.partial.html | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less index 73761cbb86..d3e4451c44 100644 --- a/awx/ui/client/src/job-detail/host-event/host-event.block.less +++ b/awx/ui/client/src/job-detail/host-event/host-event.block.less @@ -20,7 +20,7 @@ color: @changed; } .HostEvent-status--failed{ - color: @warning; + color: @default-err; } .HostEvent-status--skipped{ color: @skipped; diff --git a/awx/ui/client/src/job-detail/host-events/host-events.block.less b/awx/ui/client/src/job-detail/host-events/host-events.block.less index 17d318dc89..484f0557dd 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.block.less +++ b/awx/ui/client/src/job-detail/host-events/host-events.block.less @@ -16,7 +16,7 @@ color: @changed; } .HostEvents-status--failed{ - color: @warning; + color: @default-err; } .HostEvents-status--skipped{ color: @skipped; diff --git a/awx/ui/client/src/job-detail/job-detail.partial.html b/awx/ui/client/src/job-detail/job-detail.partial.html index c1bfd91fbc..2ced4bf551 100644 --- a/awx/ui/client/src/job-detail/job-detail.partial.html +++ b/awx/ui/client/src/job-detail/job-detail.partial.html @@ -482,8 +482,6 @@
-
-
From 18d2a30ff426f21e76843bcf587ed899ad758ccf Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 28 Mar 2016 09:17:08 -0400 Subject: [PATCH 13/33] skip test for current devel branch, planned for RBAC --- awx/main/tests/functional/api/test_organization_counts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/main/tests/functional/api/test_organization_counts.py b/awx/main/tests/functional/api/test_organization_counts.py index 6f785cf25f..5a52a1aba4 100644 --- a/awx/main/tests/functional/api/test_organization_counts.py +++ b/awx/main/tests/functional/api/test_organization_counts.py @@ -127,6 +127,7 @@ def test_two_organizations(resourced_organization, organizations, user, get): 'teams': 0 } +@pytest.mark.skip(reason="resolution planned for after RBAC merge") @pytest.mark.django_db def test_JT_associated_with_project(organizations, project, user, get): # Check that adding a project to an organization gets the project's JT From 66bab4fcf2aeffba0e7cde37a110c10a50085c8b Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 28 Mar 2016 11:04:01 -0400 Subject: [PATCH 14/33] Visual updates. Added in Create Host title. --- .../manage-groups.directive.partial.html | 10 +++--- .../manage-hosts.directive.controller.js | 7 ++-- .../manage-hosts.directive.partial.html | 36 +++++++++++-------- .../src/inventories/manage/manage.block.less | 6 ++++ 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html index 86368afbe4..eacbf795c9 100644 --- a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html +++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html @@ -4,14 +4,16 @@ -
-
-
+
+
+
+
+
-
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js index 1fdf0d157a..5fde6e3991 100644 --- a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js +++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js @@ -41,7 +41,7 @@ function manageHostsDirectiveController($rootScope, $location, $log, $stateParam }); generator.reset(); - var name = scope.name; + scope.parseType = 'yaml'; // Retrieve detail record and prepopulate the form @@ -68,7 +68,6 @@ function manageHostsDirectiveController($rootScope, $location, $log, $stateParam } scope.variable_url = data.related.variable_data; scope.has_inventory_sources = data.has_inventory_sources; - //scope.$emit('hostVariablesLoaded'); }) .error(function(data, status) { ProcessErrors(parent_scope, data, status, form, { @@ -172,10 +171,12 @@ function manageHostsDirectiveController($rootScope, $location, $log, $stateParam $state.go('inventoryManage'); }; + + angular.extend(vm, { cancelPanel: cancelPanel, - name: name, saveHost: saveHost, + mode: mode }); } diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html index 7ebdcbbf8d..31c2249234 100644 --- a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html +++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html @@ -1,17 +1,25 @@
-
- -
-
-
-
-
- - -
+
+
Create Host
+
+ +
+
+
+ +
+ +
+
+
+ +
+
diff --git a/awx/ui/client/src/inventories/manage/manage.block.less b/awx/ui/client/src/inventories/manage/manage.block.less index 46598719be..15b41dfd53 100644 --- a/awx/ui/client/src/inventories/manage/manage.block.less +++ b/awx/ui/client/src/inventories/manage/manage.block.less @@ -3,4 +3,10 @@ border: none; text-align: right; } + + #host-panel-form, #properties-tab { + .Form-header { + margin-top: -20px; + } + } } From 323b13f18ceb227db4832bac6616939c514969c7 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 28 Mar 2016 13:33:55 -0400 Subject: [PATCH 15/33] add labels to /api/v1/ --- awx/api/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/api/views.py b/awx/api/views.py index f723ff843c..1fc123100c 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -133,6 +133,7 @@ class ApiV1RootView(APIView): data['schedules'] = reverse('api:schedule_list') data['notifiers'] = reverse('api:notifier_list') data['notifications'] = reverse('api:notification_list') + data['labels'] = reverse('api:label_list') data['unified_job_templates'] = reverse('api:unified_job_template_list') data['unified_jobs'] = reverse('api:unified_job_list') data['activity_stream'] = reverse('api:activity_stream_list') From 8866f2a73859c1c8ca7c69ea9972dde8154115bb Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 28 Mar 2016 13:39:00 -0400 Subject: [PATCH 16/33] adds labels to jt and j summary fields --- awx/api/serializers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index f74f44b0a0..0cca493965 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1626,6 +1626,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer): d['can_copy'] = False d['can_edit'] = False d['recent_jobs'] = [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in obj.jobs.filter(active=True).order_by('-created')[:10]] + d['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]] return d def validate(self, attrs): @@ -1667,6 +1668,11 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer): res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,)) return res + def get_summary_fields(self, obj): + d = super(JobSerializer, self).get_summary_fields(obj) + d['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]] + return d + def to_internal_value(self, data): # When creating a new job and a job template is specified, populate any # fields not provided in data from the job template. From d0f3248d7989a9192709ce60afad375d4d1bdd21 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Mon, 28 Mar 2016 17:34:37 -0400 Subject: [PATCH 17/33] quick change to copy template name building, misc #1131 work --- .../host-event/host-event-modal.partial.html | 2 +- .../host-event/host-event.block.less | 3 +++ .../host-event/host-event.controller.js | 2 +- .../src/job-detail/job-detail.service.js | 22 ++++++++++--------- .../copy/job-templates-copy.service.js | 7 +++++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html index afcb388360..db106deb1b 100644 --- a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html +++ b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html @@ -25,7 +25,7 @@
- diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less index d3e4451c44..04b25f7419 100644 --- a/awx/ui/client/src/job-detail/host-event/host-event.block.less +++ b/awx/ui/client/src/job-detail/host-event/host-event.block.less @@ -9,6 +9,9 @@ } .HostEvent-controls{ float: right; + button { + margin-left: 10px; + } } .HostEvent-status--ok{ color: @green; diff --git a/awx/ui/client/src/job-detail/host-event/host-event.controller.js b/awx/ui/client/src/job-detail/host-event/host-event.controller.js index a1485f4714..ac46279d0f 100644 --- a/awx/ui/client/src/job-detail/host-event/host-event.controller.js +++ b/awx/ui/client/src/job-detail/host-event/host-event.controller.js @@ -44,7 +44,7 @@ $state.go('jobDetail.host-event.details', {eventId: id}) }; - $scope.goPrevious = function(){ + $scope.goPrev = function(){ var index = $scope.getActiveHostIndex() - 1; var id = $scope.hostResults[index].id; $state.go('jobDetail.host-event.details', {eventId: id}) diff --git a/awx/ui/client/src/job-detail/job-detail.service.js b/awx/ui/client/src/job-detail/job-detail.service.js index 58249d9aa2..381ff56c18 100644 --- a/awx/ui/client/src/job-detail/job-detail.service.js +++ b/awx/ui/client/src/job-detail/job-detail.service.js @@ -33,16 +33,18 @@ export default }); // flatten Ansible's passed-through response - result.event_data = {}; - Object.keys(data.event_data.res).forEach(function(key, index){ - if (ignored.indexOf(key) > -1) { - return - } - else{ - //console.log(key, data.event_data.res[key]) - result.event_data[key] = data.event_data.res[key]; - } - }); + try{ + result.event_data = {}; + Object.keys(data.event_data.res).forEach(function(key, index){ + if (ignored.indexOf(key) > -1) { + return + } + else{ + result.event_data[key] = data.event_data.res[key]; + } + }); + } + catch(err){result.event_data = null;} return result }, diff --git a/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js b/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js index d43fbff3b0..f949d142ad 100644 --- a/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js +++ b/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js @@ -23,7 +23,8 @@ set: function(data){ var defaultUrl = GetBasePath('job_templates'); Rest.setUrl(defaultUrl); - data.results[0].name = data.results[0].name + ' ' + moment().format('h:mm:ss a'); // 2:49:11 pm + var name = this.buildName(data.results[0].name) + data.results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm return Rest.post(data.results[0]) .success(function(res){ return res @@ -32,6 +33,10 @@ ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); }); + }, + buildName: function(name){ + var result = name.split('@')[0]; + return result } } } From e2489375959d38378312a26f908247ac41ae4706 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 28 Mar 2016 18:42:03 -0400 Subject: [PATCH 18/33] Remove scan as an option for job type on ad hoc commands. --- awx/main/migrations/0001_initial.py | 2 +- awx/main/models/ad_hoc_commands.py | 2 +- awx/main/models/base.py | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/awx/main/migrations/0001_initial.py b/awx/main/migrations/0001_initial.py index 6d2c78e454..bdc98cace2 100644 --- a/awx/main/migrations/0001_initial.py +++ b/awx/main/migrations/0001_initial.py @@ -381,7 +381,7 @@ class Migration(migrations.Migration): name='AdHocCommand', fields=[ ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), - ('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check'), (b'scan', 'Scan')])), + ('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check')])), ('limit', models.CharField(default=b'', max_length=1024, blank=True)), ('module_name', models.CharField(default=b'', max_length=1024, blank=True)), ('module_args', models.TextField(default=b'', blank=True)), diff --git a/awx/main/models/ad_hoc_commands.py b/awx/main/models/ad_hoc_commands.py index c5ab627046..e04bd510a1 100644 --- a/awx/main/models/ad_hoc_commands.py +++ b/awx/main/models/ad_hoc_commands.py @@ -36,7 +36,7 @@ class AdHocCommand(UnifiedJob): job_type = models.CharField( max_length=64, - choices=JOB_TYPE_CHOICES, + choices=AD_HOC_JOB_TYPE_CHOICES, default='run', ) inventory = models.ForeignKey( diff --git a/awx/main/models/base.py b/awx/main/models/base.py index b912a71572..c1ddb3600e 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -29,7 +29,7 @@ __all__ = ['VarsDictProperty', 'BaseModel', 'CreatedModifiedModel', 'PERM_INVENTORY_ADMIN', 'PERM_INVENTORY_READ', 'PERM_INVENTORY_WRITE', 'PERM_INVENTORY_DEPLOY', 'PERM_INVENTORY_SCAN', 'PERM_INVENTORY_CHECK', 'PERM_JOBTEMPLATE_CREATE', 'JOB_TYPE_CHOICES', - 'PERMISSION_TYPE_CHOICES', 'CLOUD_INVENTORY_SOURCES', + 'AD_HOC_JOB_TYPE_CHOICES', 'PERMISSION_TYPE_CHOICES', 'CLOUD_INVENTORY_SOURCES', 'VERBOSITY_CHOICES'] PERM_INVENTORY_ADMIN = 'admin' @@ -46,6 +46,11 @@ JOB_TYPE_CHOICES = [ (PERM_INVENTORY_SCAN, _('Scan')), ] +AD_HOC_JOB_TYPE_CHOICES = [ + (PERM_INVENTORY_DEPLOY, _('Run')), + (PERM_INVENTORY_CHECK, _('Check')), +] + PERMISSION_TYPE_CHOICES = [ (PERM_INVENTORY_READ, _('Read Inventory')), (PERM_INVENTORY_WRITE, _('Edit Inventory')), From aedf1d87abe8cda4ab5753fad21f34d99a0f1484 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 28 Mar 2016 18:00:53 -0400 Subject: [PATCH 19/33] Fix help text in OPTIONS for common, read-only fields. Also fix display of None for foreign key fields in browsable API help. --- awx/api/metadata.py | 23 +++++++++++++++++-- awx/api/serializers.py | 22 ------------------ .../templates/api/_result_fields_common.md | 2 +- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/awx/api/metadata.py b/awx/api/metadata.py index 6fccdb887d..f5c72fed97 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -6,7 +6,7 @@ from collections import OrderedDict # Django from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.utils.encoding import force_text +from django.utils.encoding import force_text, smart_text # Django REST Framework from rest_framework import exceptions @@ -37,6 +37,25 @@ class Metadata(metadata.SimpleMetadata): if value is not None and value != '': field_info[attr] = force_text(value, strings_only=True) + # Update help text for common fields. + serializer = getattr(field, 'parent', None) + if serializer: + field_help_text = { + 'id': 'Database ID for this {}.', + 'name': 'Name of this {}.', + 'description': 'Optional description of this {}.', + 'type': 'Data type for this {}.', + 'url': 'URL for this {}.', + 'related': 'Data structure with URLs of related resources.', + 'summary_fields': 'Data structure with name/description for related resources.', + 'created': 'Timestamp when this {} was created.', + 'modified': 'Timestamp when this {} was last modified.', + } + if field.field_name in field_help_text: + opts = serializer.Meta.model._meta.concrete_model._meta + verbose_name = smart_text(opts.verbose_name) + field_info['help_text'] = field_help_text[field.field_name].format(verbose_name) + # Indicate if a field has a default value. # FIXME: Still isn't showing all default values? try: @@ -77,7 +96,7 @@ class Metadata(metadata.SimpleMetadata): # Update type of fields returned... if field.field_name == 'type': - field_info['type'] = 'multiple choice' + field_info['type'] = 'choice' elif field.field_name == 'url': field_info['type'] = 'string' elif field.field_name in ('related', 'summary_fields'): diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 0cca493965..9397b93b2e 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -326,7 +326,6 @@ class BaseSerializer(serializers.ModelSerializer): return obj.active def build_standard_field(self, field_name, model_field): - # DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits # when a Model's editable field is set to False. The short circuit skips choice rendering. # @@ -343,27 +342,6 @@ class BaseSerializer(serializers.ModelSerializer): if was_editable is False: field_kwargs['read_only'] = True - # Update help text for common fields. - opts = self.Meta.model._meta.concrete_model._meta - if field_name == 'id': - field_kwargs.setdefault('help_text', 'Database ID for this %s.' % smart_text(opts.verbose_name)) - elif field_name == 'name': - field_kwargs['help_text'] = 'Name of this %s.' % smart_text(opts.verbose_name) - elif field_name == 'description': - field_kwargs['help_text'] = 'Optional description of this %s.' % smart_text(opts.verbose_name) - elif field_name == 'type': - field_kwargs['help_text'] = 'Data type for this %s.' % smart_text(opts.verbose_name) - elif field_name == 'url': - field_kwargs['help_text'] = 'URL for this %s.' % smart_text(opts.verbose_name) - elif field_name == 'related': - field_kwargs['help_text'] = 'Data structure with URLs of related resources.' - elif field_name == 'summary_fields': - field_kwargs['help_text'] = 'Data structure with name/description for related resources.' - elif field_name == 'created': - field_kwargs['help_text'] = 'Timestamp when this %s was created.' % smart_text(opts.verbose_name) - elif field_name == 'modified': - field_kwargs['help_text'] = 'Timestamp when this %s was last modified.' % smart_text(opts.verbose_name) - # Pass model field default onto the serializer field if field is not read-only. if model_field.has_default() and not field_kwargs.get('read_only', False): field_kwargs['default'] = field_kwargs['initial'] = model_field.get_default() diff --git a/awx/api/templates/api/_result_fields_common.md b/awx/api/templates/api/_result_fields_common.md index 35fc3b55d1..43abefc534 100644 --- a/awx/api/templates/api/_result_fields_common.md +++ b/awx/api/templates/api/_result_fields_common.md @@ -1,6 +1,6 @@ {% for fn, fm in serializer_fields.items %}{% spaceless %} {% if not write_only or not fm.read_only %} -* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if write_only and fm.required %}, required{% endif %}{% if write_only and fm.read_only %}, read-only{% endif %}{% if write_only and not fm.choices and not fm.required %}, default=`{% if fm.type == "string" or fm.type == "email" %}"{% firstof fm.default "" %}"{% else %}{{ fm.default }}{% endif %}`{% endif %}){% if fm.choices %}{% for c in fm.choices %} +* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if write_only and fm.required %}, required{% endif %}{% if write_only and fm.read_only %}, read-only{% endif %}{% if write_only and not fm.choices and not fm.required %}, default=`{% if fm.type == "string" or fm.type == "email" %}"{% firstof fm.default "" %}"{% else %}{% if fm.type == "field" and not fm.default %}None{% else %}{{ fm.default }}{% endif %}{% endif %}`{% endif %}){% if fm.choices %}{% for c in fm.choices %} - `{% if c.0 == "" %}""{% else %}{{ c.0 }}{% endif %}`{% if c.1 != c.0 %}: {{ c.1 }}{% endif %}{% if write_only and c.0 == fm.default %} (default){% endif %}{% endfor %}{% endif %}{% endif %} {% endspaceless %} {% endfor %} From 895e082e08808bc86bfbf3545f74587f35f41976 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 28 Mar 2016 18:19:20 -0400 Subject: [PATCH 20/33] Damn you, flake8. --- awx/api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 9397b93b2e..fb0b4525c1 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -20,7 +20,7 @@ from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError from django.db import models # from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import force_text, smart_text +from django.utils.encoding import force_text from django.utils.text import capfirst # Django REST Framework From 3660b04d62be7937e8bf9facf7df8a15846d6e94 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 28 Mar 2016 18:33:46 -0400 Subject: [PATCH 21/33] Add trailing newline to key data for OpenSSH formatted keys. --- awx/main/tasks.py | 4 ++++ awx/main/tests/data/ssh.py | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 381ea31623..387193b06b 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -389,6 +389,10 @@ class BaseTask(Task): if 'OPENSSH PRIVATE KEY' in data and not openssh_keys_supported: raise RuntimeError(OPENSSH_KEY_ERROR) for name, data in private_data.iteritems(): + # OpenSSH formatted keys must have a trailing newline to be + # accepted by ssh-add. + if 'OPENSSH PRIVATE KEY' in data and not data.endswith('\n'): + data += '\n' # For credentials used with ssh-add, write to a named pipe which # will be read then closed, instead of leaving the SSH key on disk. if name in ('credential', 'scm_credential', 'ad_hoc_credential') and not ssh_too_old: diff --git a/awx/main/tests/data/ssh.py b/awx/main/tests/data/ssh.py index ff5592358e..c2a9a29223 100644 --- a/awx/main/tests/data/ssh.py +++ b/awx/main/tests/data/ssh.py @@ -84,8 +84,7 @@ HPUhg3adAmIJ9z9u/VmTErbVklcKWlyZuTUkxeQ/BJmSIRUQAAAIEA3oKAzdDURjy8zxLX gBLCPdi8AxCiqQJBCsGxXCgKtZewset1XJHIN9ryfb4QSZFkSOlm/LgdeGtS8Or0GNPRYd hgnUCF0LkEsDQ7HzPZYujLrAwjumvGQH6ORp5vRh0tQb93o4e1/A2vpdSKeH7gCe/jfUSY h7dFGNoAI4cF7/0AAAAUcm9vdEBwaWxsb3cuaXhtbS5uZXQBAgMEBQYH ------END OPENSSH PRIVATE KEY----- -''' +-----END OPENSSH PRIVATE KEY-----''' TEST_OPENSSH_KEY_DATA_LOCKED = '''-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABALaWMfjc @@ -114,8 +113,7 @@ C6Oxl1Wsp3gPkK2yiuy8qcrvoEoJ25TeEhUGEAPWx2OuQJO/Lpq9aF/JJoqGwnBaXdCsi+ 5ig+ZMq5GKQtyydzyXImjlNEUH1w2prRDiGVEufANA5LSLCtqOLgDzXS62WUBjJBrQJVAM YpWz1tiZQoyv1RT3Y0O0Vwe2Z5AK3fVM0I5jWdiLrIErtcR4ULa6T56QtA52DufhKzINTR Vg9TtUBqfKIpRQikPSjm7vpY/Xnbc= ------END OPENSSH PRIVATE KEY----- -''' +-----END OPENSSH PRIVATE KEY-----''' TEST_SSH_CERT_KEY = """-----BEGIN CERTIFICATE----- MIIDNTCCAh2gAwIBAgIBATALBgkqhkiG9w0BAQswSTEWMBQGA1UEAwwNV2luZG93 From 77f064d728b3c8f637107344264f8aa5146ec4b5 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 29 Mar 2016 13:27:49 -0400 Subject: [PATCH 22/33] Keep model meta around in base serializer. --- awx/api/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index fb0b4525c1..75c00a7db6 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -367,6 +367,7 @@ class BaseSerializer(serializers.ModelSerializer): # Update the message used for the unique validator to use capitalized # verbose name; keeps unique message the same as with DRF 2.x. + opts = self.Meta.model._meta.concrete_model._meta for validator in field_kwargs.get('validators', []): if isinstance(validator, validators.UniqueValidator): unique_error_message = model_field.error_messages.get('unique', None) From 1b9c5ef55b95842c1e816834e488e665b75babe8 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 29 Mar 2016 13:56:41 -0400 Subject: [PATCH 23/33] Keep page number in previous link for page 1. --- awx/api/pagination.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awx/api/pagination.py b/awx/api/pagination.py index 822e6065ee..48329d60c8 100644 --- a/awx/api/pagination.py +++ b/awx/api/pagination.py @@ -22,6 +22,4 @@ class Pagination(pagination.PageNumberPagination): return None url = self.request and self.request.get_full_path() or '' page_number = self.page.previous_page_number() - if page_number == 1: - return remove_query_param(url, self.page_query_param) return replace_query_param(url, self.page_query_param, page_number) From 615f7b50dc906cb8523163310bb13c2e7108ec89 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 29 Mar 2016 14:26:43 -0400 Subject: [PATCH 24/33] Fix browsable API tooltips that linger. --- awx/static/api/api.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/awx/static/api/api.js b/awx/static/api/api.js index 177770fb8f..67053ae2f6 100644 --- a/awx/static/api/api.js +++ b/awx/static/api/api.js @@ -43,11 +43,13 @@ $(function() { $('.description').addClass('prettyprint').parent().css('float', 'none'); $('.hidden a.hide-description').prependTo('.description'); $('a.hide-description').click(function() { + $(this).tooltip('hide'); $('.description').slideUp('fast'); return false; }); $('.hidden a.toggle-description').appendTo('.page-header h1'); $('a.toggle-description').click(function() { + $(this).tooltip('hide'); $('.description').slideToggle('fast'); return false; }); @@ -68,6 +70,7 @@ $(function() { }); $('a.resize').click(function() { + $(this).tooltip('hide'); if ($(this).find('span.glyphicon-resize-full').size()) { $(this).find('span.glyphicon').addClass('glyphicon-resize-small').removeClass('glyphicon-resize-full'); $('.container').addClass('container-fluid').removeClass('container'); From c1c444fd3ca613bf31373e776845169f6ca7eb2a Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 29 Mar 2016 15:19:17 -0400 Subject: [PATCH 25/33] Flake8 fix. --- awx/api/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/pagination.py b/awx/api/pagination.py index 48329d60c8..ee17aee0e1 100644 --- a/awx/api/pagination.py +++ b/awx/api/pagination.py @@ -3,7 +3,7 @@ # Django REST Framework from rest_framework import pagination -from rest_framework.utils.urls import remove_query_param, replace_query_param +from rest_framework.utils.urls import replace_query_param class Pagination(pagination.PageNumberPagination): From 2ef6f764d2d2ec5dcad76975ca9a204a1ca4cbf8 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Tue, 29 Mar 2016 17:12:30 -0400 Subject: [PATCH 26/33] remove name column from Host Events modal, resolves #1132 --- .../client/src/job-detail/host-events/host-events.block.less | 1 + .../src/job-detail/host-events/host-events.controller.js | 2 ++ .../src/job-detail/host-events/host-events.partial.html | 4 +--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/job-detail/host-events/host-events.block.less b/awx/ui/client/src/job-detail/host-events/host-events.block.less index 484f0557dd..1a92e8933c 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.block.less +++ b/awx/ui/client/src/job-detail/host-events/host-events.block.less @@ -47,6 +47,7 @@ padding-bottom: 15px; } .HostEvents-title{ + text-transform: uppercase; color: @default-interface-txt; font-weight: 600; } diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js index 732ceab45a..c97da5eb62 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js +++ b/awx/ui/client/src/job-detail/host-events/host-events.controller.js @@ -107,7 +107,9 @@ }); var init = function(){ + $scope.hostName = $stateParams.hostName; // create filter dropdown + console.log($stateParams) CreateSelect2({ element: '.HostEvents-select', multiple: false diff --git a/awx/ui/client/src/job-detail/host-events/host-events.partial.html b/awx/ui/client/src/job-detail/host-events/host-events.partial.html index 1b7702ab4a..9d0ccca40f 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.partial.html +++ b/awx/ui/client/src/job-detail/host-events/host-events.partial.html @@ -3,7 +3,7 @@