diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index dcf9154154..ce7043c7e6 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -130,6 +130,10 @@ .noselect; } +.Form-tab--notitle { + margin-bottom: 0px; +} + .Form-tab:hover { color: @btn-txt; background-color: @btn-bg-hov; diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 0649023b54..f5eaf2080b 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -40,6 +40,7 @@ if ($basePath) { import portalMode from './portal-mode/main'; import systemTracking from './system-tracking/main'; import inventories from './inventories/main'; +import inventorynew from './inventoriesnew/main'; import inventoryScripts from './inventory-scripts/main'; import credentialTypes from './credential-types/main'; import organizations from './organizations/main'; @@ -99,6 +100,7 @@ var tower = angular.module('Tower', [ configuration.name, systemTracking.name, inventories.name, + inventorynew.name, inventoryScripts.name, credentialTypes.name, organizations.name, 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 2f43f48100..d900048060 100644 --- a/awx/ui/client/src/inventories/list/inventory-list.controller.js +++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js @@ -250,7 +250,7 @@ function InventoriesList($scope, $rootScope, $location, }; $scope.addInventory = function () { - $state.go('inventories.add'); + $state.go('inventoriesnew.add'); }; $scope.editInventory = function (id) { diff --git a/awx/ui/client/src/inventoriesnew/add/inventory-add.controller.js b/awx/ui/client/src/inventoriesnew/add/inventory-add.controller.js new file mode 100644 index 0000000000..17f42276d0 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/add/inventory-add.controller.js @@ -0,0 +1,104 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +function InventoriesAdd($scope, $location, + GenerateForm, InventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors, + ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON, + $state) { + + $scope.canAdd = false; + rbacUiControlService.canAdd(GetBasePath('inventory')) + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + + Rest.setUrl(GetBasePath('inventory')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add an inventory.', 'alert-info'); + } + }); + + ClearScope(); + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoryForm; + + init(); + + function init() { + $scope.canEditOrg = true; + form.formLabelSize = null; + form.formFieldSize = null; + + // apply form definition's default field values + GenerateForm.applyDefaults(form, $scope); + + $scope.parseType = 'yaml'; + ParseTypeChange({ + scope: $scope, + variable: 'variables', + parse_variable: 'parseType', + field_id: 'inventory_variables' + }); + } + + // Save + $scope.formSave = function() { + 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('/inventoriesnew/' + inventory_id); + }) + .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.go('inventoriesnew'); + }; +} + +export default ['$scope', '$location', + 'GenerateForm', 'InventoryForm', 'rbacUiControlService', 'Rest', 'Alert', + 'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', + 'Wait', 'ToJSON', '$state', InventoriesAdd +]; diff --git a/awx/ui/client/src/inventoriesnew/add/main.js b/awx/ui/client/src/inventoriesnew/add/main.js new file mode 100644 index 0000000000..2d3c391495 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/add/main.js @@ -0,0 +1,11 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import controller from './inventory-add.controller'; + +export default +angular.module('newInventoryAdd', []) + .controller('NewInventoryAddController', controller); diff --git a/awx/ui/client/src/inventoriesnew/edit/inventory-edit.controller.js b/awx/ui/client/src/inventoriesnew/edit/inventory-edit.controller.js new file mode 100644 index 0000000000..98c1549499 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/edit/inventory-edit.controller.js @@ -0,0 +1,136 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +function InventoriesEdit($scope, $location, + $stateParams, InventoriesNewForm, Rest, ProcessErrors, + ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON, + ParseVariableString, $state, OrgAdminLookup) { + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoriesNewForm, + inventory_id = $stateParams.inventory_id, + master = {}, + fld, json_data, data; + + ClearScope(); + init(); + + function init() { + ClearScope(); + form.formLabelSize = null; + form.formFieldSize = null; + $scope.inventory_id = inventory_id; + + $scope.$watch('inventory_obj.summary_fields.user_capabilities.edit', function(val) { + if (val === false) { + $scope.canAdd = false; + } + }); + } + + + 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]; + } + } + + Wait('stop'); + $scope.parseType = 'yaml'; + ParseTypeChange({ + scope: $scope, + variable: 'variables', + parse_variable: 'parseType', + field_id: 'inventory_variables' + }); + + OrgAdminLookup.checkForAdminAccess({organization: data.organization}) + .then(function(canEditOrg){ + $scope.canEditOrg = canEditOrg; + }); + + $scope.inventory_obj = data; + $scope.name = data.name; + + $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'); + $state.go($state.current, {}, { reload: true }); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to update inventory. PUT returned status: ' + status + }); + }); + }; + + $scope.formCancel = function() { + $state.go('inventoriesnew'); + }; + +} + +export default ['$scope', '$location', + '$stateParams', 'InventoriesNewForm', 'Rest', + 'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait', + 'ToJSON', 'ParseVariableString', + '$state', 'OrgAdminLookup', InventoriesEdit, +]; diff --git a/awx/ui/client/src/inventoriesnew/edit/main.js b/awx/ui/client/src/inventoriesnew/edit/main.js new file mode 100644 index 0000000000..a6a404a869 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/edit/main.js @@ -0,0 +1,11 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import controller from './inventory-edit.controller'; + +export default + angular.module('newInventoryEdit', []) + .controller('NewInventoryEditController', controller); diff --git a/awx/ui/client/src/inventoriesnew/hosts/add/host-add.controller.js b/awx/ui/client/src/inventoriesnew/hosts/add/host-add.controller.js new file mode 100644 index 0000000000..151d75401d --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/add/host-add.controller.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +function HostsAdd($scope) { + +console.log('inside host add'); + +} + +export default ['$scope', HostsAdd +]; diff --git a/awx/ui/client/src/inventoriesnew/hosts/add/main.js b/awx/ui/client/src/inventoriesnew/hosts/add/main.js new file mode 100644 index 0000000000..8205e986c6 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/add/main.js @@ -0,0 +1,11 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import controller from './host-add.controller'; + +export default +angular.module('hostsAdd', []) + .controller('NewHostAddController', controller); diff --git a/awx/ui/client/src/inventoriesnew/hosts/edit/host-edit.controller.js b/awx/ui/client/src/inventoriesnew/hosts/edit/host-edit.controller.js new file mode 100644 index 0000000000..d1d1092085 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/edit/host-edit.controller.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +function HostsEdit($scope) { + + console.log('inside host edit'); + +} + +export default ['$scope', HostsEdit +]; diff --git a/awx/ui/client/src/inventoriesnew/hosts/edit/main.js b/awx/ui/client/src/inventoriesnew/hosts/edit/main.js new file mode 100644 index 0000000000..420e870e99 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/edit/main.js @@ -0,0 +1,11 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import controller from './host-edit.controller'; + +export default +angular.module('hostsEdit', []) + .controller('NewHostEditController', controller); diff --git a/awx/ui/client/src/inventoriesnew/hosts/host.form.js b/awx/ui/client/src/inventoriesnew/hosts/host.form.js new file mode 100644 index 0000000000..efb64a4785 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/host.form.js @@ -0,0 +1,103 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + /** + * @ngdoc function + * @name forms.function:Hosts + * @description This form is for adding/editing a host on the inventory page +*/ + +export default ['i18n', function(i18n) { + return { + + addTitle: i18n._('CREATE HOST'), + editTitle: '{{ host.name }}', + name: 'host', + basePath: 'hosts', + well: false, + formLabelSize: 'col-lg-3', + formFieldSize: 'col-lg-9', + iterator: 'host', + headerFields:{ + enabled: { + class: 'Form-header-field', + ngClick: 'toggleHostEnabled(host)', + type: 'toggle', + awToolTip: "
" + + i18n._("Indicates if a host is available and should be included in running jobs.") + + "
" + + i18n._("For hosts that are part of an external" + + " inventory, this flag cannot be changed. It will be" + + " set by the inventory sync process.") + + "
", + dataTitle: i18n._('Host Enabled'), + ngDisabled: 'host.has_inventory_sources' + } + }, + fields: { + name: { + label: i18n._('Host Name'), + type: 'text', + required: true, + awPopOver: "" + + i18n._("Provide a host name, ip address, or ip address:port. Examples include:") + + "
" + + "myserver.domain.com", + dataTitle: i18n._('Host Name'), + dataPlacement: 'right', + dataContainer: 'body', + ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)' + }, + description: { + label: i18n._('Description'), + ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)', + type: 'text' + }, + variables: { + label: i18n._('Variables'), + type: 'textarea', + rows: 6, + class: 'Form-formGroup--fullWidth', + "default": "---", + awPopOver: "
" + + "127.0.0.1
" + + "10.1.0.140:25
" + + "server.example.com:25" + + "
" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "
" + + "JSON:{\n" + + "YAML:
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
---\n" + + '
somevar: somevalue
password: magic
' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '
' + + '' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '
', + dataTitle: i18n._('Host Variables'), + dataPlacement: 'right', + dataContainer: 'body' + }, + inventory: { + type: 'hidden', + includeOnEdit: true, + includeOnAdd: true + } + }, + + buttons: { + cancel: { + ngClick: 'formCancel()', + ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' + }, + close: { + ngClick: 'formCancel()', + ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)' + }, + save: { + ngClick: 'formSave()', + ngDisabled: true, + ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' + } + }, + }; + }]; diff --git a/awx/ui/client/src/inventoriesnew/hosts/host.list.js b/awx/ui/client/src/inventoriesnew/hosts/host.list.js new file mode 100644 index 0000000000..632a38129b --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/host.list.js @@ -0,0 +1,121 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['i18n', function(i18n) { + return { + name: 'hosts', + iterator: 'host', + editTitle: '{{ selected_group }}', + searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12', + showTitle: false, + well: true, + index: false, + hover: true, + hasChildren: true, + 'class': 'table-no-border', + trackBy: 'host.id', + title: false, + + fields: { + toggleHost: { + ngDisabled: "!host.summary_fields.user_capabilities.edit", + label: '', + columnClass: 'List-staticColumn--toggle', + type: "toggle", + ngClick: "toggleHost($event, host)", + awToolTip: "" + + i18n._("Indicates if a host is available and should be included in running jobs.") + + "
" + + i18n._("For hosts that are part of an external" + + " inventory, this flag cannot be changed. It will be" + + " set by the inventory sync process.") + + "
", + dataPlacement: "right", + nosort: true, + }, + active_failures: { + label: '', + iconOnly: true, + nosort: true, + // do not remove this ng-click directive + // the list generator case to handle fields without ng-click + // cannot handle the aw-* directives + ngClick: 'noop()', + awPopOver: "{{ host.job_status_html }}", + dataTitle: "{{ host.job_status_title }}", + awToolTip: "{{ host.badgeToolTip }}", + dataPlacement: 'top', + icon: "{{ 'fa icon-job-' + host.active_failures }}", + id: 'active-failures-action', + columnClass: 'status-column List-staticColumn--smallStatus' + }, + name: { + key: true, + label: 'Name', + ngClick: "editHost(host.id)", + columnClass: 'col-lg-6 col-md-8 col-sm-8 col-xs-7', + dataHostId: "{{ host.id }}", + dataType: "host", + class: 'InventoryManage-breakWord' + } + }, + + fieldActions: { + + columnClass: 'col-lg-6 col-md-4 col-sm-4 col-xs-5 text-right', + copy: { + mode: 'all', + ngClick: "copyMoveHost(host.id)", + awToolTip: 'Copy or move host to another group', + dataPlacement: "top", + ngShow: 'host.summary_fields.user_capabilities.edit' + }, + edit: { + //label: 'Edit', + ngClick: "editHost(host.id)", + icon: 'icon-edit', + awToolTip: 'Edit host', + dataPlacement: 'top', + ngShow: 'host.summary_fields.user_capabilities.edit' + }, + view: { + //label: 'Edit', + ngClick: "editHost(host.id)", + awToolTip: 'View host', + dataPlacement: 'top', + ngShow: '!host.summary_fields.user_capabilities.edit' + }, + "delete": { + //label: 'Delete', + ngClick: "deleteHost(host.id, host.name)", + icon: 'icon-trash', + awToolTip: 'Delete host', + dataPlacement: 'top', + ngShow: 'host.summary_fields.user_capabilities.delete' + } + }, + + actions: { + refresh: { + mode: 'all', + awToolTip: "Refresh the page", + ngClick: "refreshGroups()", + ngShow: "socketStatus == 'error'", + actionClass: 'btn List-buttonDefault', + buttonContent: 'REFRESH' + }, + smart_inventory: { + mode: 'all', + ngClick: "smartInventory()", + awToolTip: "Create a new Smart Inventory from results.", + actionClass: 'btn List-buttonDefault', + buttonContent: 'SMART INVENTORY', + ngShow: 'canAdd', + dataPlacement: "top", + } + } + }; +}]; diff --git a/awx/ui/client/src/inventoriesnew/hosts/list/host-list.controller.js b/awx/ui/client/src/inventoriesnew/hosts/list/host-list.controller.js new file mode 100644 index 0000000000..3f5ac46872 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/hosts/list/host-list.controller.js @@ -0,0 +1,80 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +function HostsList($scope, HostsNewList, $rootScope, GetBasePath, + rbacUiControlService, Dataset, $state, $filter, Prompt, Wait, HostManageService) { + + let list = HostsNewList; + + init(); + + function init(){ + $scope.canAdd = false; + + rbacUiControlService.canAdd('hosts') + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + + // Search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + $rootScope.flashMessage = null; + + } + + $scope.createHost = function(){ + $state.go('hostsnew.add'); + }; + $scope.editHost = function(id){ + $state.go('hostsnew.edit', {host_id: id}); + }; + $scope.deleteHost = function(id, name){ + var body = '" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "
" + + "JSON:{\n" + + "YAML:
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
---\n" + + '
somevar: somevalue
password: magic
' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '
' + + '' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '
', + dataTitle: i18n._('Inventory Variables'), + dataPlacement: 'right', + dataContainer: 'body', + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working + } + }, + + buttons: { + cancel: { + ngClick: 'formCancel()', + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + }, + close: { + ngClick: 'formCancel()', + ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + }, + save: { + ngClick: 'formSave()', + ngDisabled: true, + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + } + }, + related: { + permissions: { + name: 'permissions', + awToolTip: i18n._('Please save before assigning permissions'), + dataPlacement: 'top', + basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/', + type: 'collection', + title: i18n._('Permissions'), + iterator: 'permission', + index: false, + open: false, + search: { + order_by: 'username' + }, + actions: { + add: { + label: i18n._('Add'), + ngClick: "$state.go('.add')", + awToolTip: i18n._('Add a permission'), + actionClass: 'btn List-buttonSubmit', + buttonContent: '+ ADD', + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + + } + }, + fields: { + username: { + label: i18n._('User'), + linkBase: 'users', + class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' + }, + role: { + label: i18n._('Role'), + type: 'role', + nosort: true, + class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4', + }, + team_roles: { + label: i18n._('Team Roles'), + type: 'team_roles', + nosort: true, + class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4', + } + } + } + } + + };}]; diff --git a/awx/ui/client/src/inventoriesnew/inventory.list.js b/awx/ui/client/src/inventoriesnew/inventory.list.js new file mode 100644 index 0000000000..c06f3703ec --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/inventory.list.js @@ -0,0 +1,98 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +export default ['i18n', function(i18n) { + return { + + name: 'inventoriesnew', + iterator: 'inventory', + selectTitle: i18n._('Add Inventories'), + editTitle: i18n._('INVENTORIES'), + listTitle: i18n._('INVENTORIES'), + selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Click the %s button to create a new inventory."), " "), + index: false, + hover: true, + basePath: 'inventory', + title: false, + + fields: { + status: { + label: '', + columnClass: 'List-staticColumn--mediumStatus', + nosort: true, + ngClick: "null", + iconOnly: true, + excludeModal: true, + icons: [{ + icon: "{{ 'icon-cloud-' + inventory.syncStatus }}", + awToolTip: "{{ inventory.syncTip }}", + awTipPlacement: "right", + ngClick: "showGroupSummary($event, inventory.id)", + ngClass: "inventory.launch_class" + },{ + icon: "{{ 'icon-job-' + inventory.hostsStatus }}", + awToolTip: false, + ngClick: "showHostSummary($event, inventory.id)", + ngClass: "" + }] + }, + name: { + key: true, + label: i18n._('Name'), + columnClass: 'col-md-5 col-sm-5 col-xs-8 List-staticColumnAdjacent', + modalColumnClass: 'col-md-11', + linkTo: '/#/inventoriesnew/{{inventory.id}}' + }, + organization: { + label: i18n._('Organization'), + ngBind: 'inventory.summary_fields.organization.name', + linkTo: '/#/organizations/{{ inventory.organization }}', + sourceModel: 'organization', + sourceField: 'name', + excludeModal: true, + columnClass: 'col-md-5 col-sm-3 hidden-xs' + } + }, + + actions: { + add: { + mode: 'all', // One of: edit, select, all + ngClick: 'addInventory()', + awToolTip: i18n._('Create a new inventory'), + actionClass: 'btn List-buttonSubmit', + buttonContent: '+ ' + i18n._('ADD'), + ngShow: 'canAdd' + } + }, + + fieldActions: { + + columnClass: 'col-md-2 col-sm-4 col-xs-4', + + edit: { + label: i18n._('Edit'), + ngClick: 'editInventory(inventory.id)', + awToolTip: i18n._('Edit inventory'), + dataPlacement: 'top', + ngShow: 'inventory.summary_fields.user_capabilities.edit' + }, + view: { + label: i18n._('View'), + ngClick: 'editInventory(inventory.id)', + awToolTip: i18n._('View inventory'), + dataPlacement: 'top', + ngShow: '!inventory.summary_fields.user_capabilities.edit' + }, + "delete": { + label: i18n._('Delete'), + ngClick: "deleteInventory(inventory.id, inventory.name)", + awToolTip: i18n._('Delete inventory'), + dataPlacement: 'top', + ngShow: 'inventory.summary_fields.user_capabilities.delete' + } + } + };}]; diff --git a/awx/ui/client/src/inventoriesnew/list/inventory-list.controller.js b/awx/ui/client/src/inventoriesnew/list/inventory-list.controller.js new file mode 100644 index 0000000000..e37f9ab6e8 --- /dev/null +++ b/awx/ui/client/src/inventoriesnew/list/inventory-list.controller.js @@ -0,0 +1,308 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Inventories + * @description This controller's for the Inventory page + */ + +function InventoriesList($scope, $rootScope, $location, + $compile, $filter, Rest, InventoriesNewList, Prompt, + ProcessErrors, GetBasePath, Wait, Find, Empty, $state, rbacUiControlService, Dataset) { + + let list = InventoriesNewList, + defaultUrl = GetBasePath('inventory'); + + init(); + + function init(){ + $scope.canAdd = false; + + rbacUiControlService.canAdd('inventory') + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + + $scope.$watchCollection(list.name, function(){ + _.forEach($scope[list.name], buildStatusIndicators); + }); + + // Search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + $rootScope.flashMessage = null; + + } + + function buildStatusIndicators(inventory){ + inventory.launch_class = ""; + if (inventory.has_inventory_sources) { + if (inventory.inventory_sources_with_failures > 0) { + inventory.syncStatus = 'error'; + inventory.syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details'; + } + else { + inventory.syncStatus = 'successful'; + inventory.syncTip = 'No inventory sync failures. Click for details.'; + } + } + else { + inventory.syncStatus = 'na'; + inventory.syncTip = 'Not configured for inventory sync.'; + inventory.launch_class = "btn-disabled"; + } + if (inventory.has_active_failures) { + inventory.hostsStatus = 'error'; + inventory.hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.'; + } + else if (inventory.total_hosts) { + inventory.hostsStatus = 'successful'; + inventory.hostsTip = 'No hosts with failures. Click for details.'; + } + else { + inventory.hostsStatus = 'none'; + inventory.hostsTip = 'Inventory contains 0 hosts.'; + } + } + + 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| Status | "; + html += "Finished | "; + html += "Name | "; + html += "
|---|---|---|
| \n"; + html += " | " + ($filter('longDate')(row.finished)).replace(/ /,' ') + " | ";
+ html += "" + $filter('sanitize')(ellipsis(row.name)) + " | "; + 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 = "| Status | "; + html += "Last Sync | "; + html += "Group | "; + html += "
|---|---|---|
| `; + html += " | " + ($filter('longDate')(row.last_updated)).replace(/ /,' ') + " | ";
+ html += "" + $filter('sanitize')(ellipsis(row.summary_fields.group.name)) + " | "; + html += "
| "; + html += " | NA | "; + html += "" + $filter('sanitize')(ellipsis(row.summary_fields.group.name)) + " | "; + html += "