1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-31 15:21:13 +03:00

Set up new inventory directory and states

This commit is contained in:
Michael Abashian 2017-04-05 11:45:02 -04:00 committed by Jared Tabor
parent 8c7adddaf3
commit f9b46b7c3d
24 changed files with 1485 additions and 10 deletions

View File

@ -130,6 +130,10 @@
.noselect;
}
.Form-tab--notitle {
margin-bottom: 0px;
}
.Form-tab:hover {
color: @btn-txt;
background-color: @btn-bg-hov;

View File

@ -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,

View File

@ -250,7 +250,7 @@ function InventoriesList($scope, $rootScope, $location,
};
$scope.addInventory = function () {
$state.go('inventories.add');
$state.go('inventoriesnew.add');
};
$scope.editInventory = function (id) {

View File

@ -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
];

View File

@ -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);

View File

@ -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,
];

View File

@ -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);

View File

@ -0,0 +1,14 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
function HostsAdd($scope) {
console.log('inside host add');
}
export default ['$scope', HostsAdd
];

View File

@ -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);

View File

@ -0,0 +1,14 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
function HostsEdit($scope) {
console.log('inside host edit');
}
export default ['$scope', HostsEdit
];

View File

@ -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);

View File

@ -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: "<p>" +
i18n._("Indicates if a host is available and should be included in running jobs.") +
"</p><p>" +
i18n._("For hosts that are part of an external" +
" inventory, this flag cannot be changed. It will be" +
" set by the inventory sync process.") +
"</p>",
dataTitle: i18n._('Host Enabled'),
ngDisabled: 'host.has_inventory_sources'
}
},
fields: {
name: {
label: i18n._('Host Name'),
type: 'text',
required: true,
awPopOver: "<p>" +
i18n._("Provide a host name, ip address, or ip address:port. Examples include:") +
"</p>" +
"<blockquote>myserver.domain.com<br/>" +
"127.0.0.1<br />" +
"10.1.0.140:25<br />" +
"server.example.com:25" +
"</blockquote>",
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: "<p>" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>' + i18n.sprintf(i18n._('View JSON examples at %s'), '<a href="http://www.json.org" target="_blank">www.json.org</a>') + '</p>' +
'<p>' + i18n.sprintf(i18n._('View YAML examples at %s'), '<a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a>') + '</p>',
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)'
}
},
};
}];

View File

@ -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: "<p>" +
i18n._("Indicates if a host is available and should be included in running jobs.") +
"</p><p>" +
i18n._("For hosts that are part of an external" +
" inventory, this flag cannot be changed. It will be" +
" set by the inventory sync process.") +
"</p>",
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",
}
}
};
}];

View File

@ -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 = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(name) + '</div>';
var action = function(){
delete $rootScope.promptActionBtnClass;
Wait('start');
HostManageService.delete(id).then(() => {
$('#prompt-modal').modal('hide');
if (parseInt($state.params.host_id) === id) {
$state.go("hostsnew", null, {reload: true});
} else {
$state.go($state.current.name, null, {reload: true});
}
Wait('stop');
});
};
// Prompt depends on having $rootScope.promptActionBtnClass available...
Prompt({
hdr: 'Delete Host',
body: body,
action: action,
actionText: 'DELETE',
});
$rootScope.promptActionBtnClass = 'Modal-errorButton';
};
$scope.toggleHost = function(event, host) {
try {
$(event.target).tooltip('hide');
} catch (e) {
// ignore
}
host.enabled = !host.enabled;
HostManageService.put(host).then(function(){
$state.go($state.current, null, {reload: true});
});
};
}
export default ['$scope', 'HostsNewList', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait', 'HostManageService', HostsList
];

View File

@ -0,0 +1,11 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import controller from './host-list.controller';
export default
angular.module('hostsList', [])
.controller('NewHostListController', controller);

View File

@ -0,0 +1,20 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import hostAdd from './add/main';
import hostEdit from './edit/main';
import hostList from './list/main';
import HostsNewList from './host.list';
import HostsNewForm from './host.form';
export default
angular.module('hostnew', [
hostAdd.name,
hostEdit.name,
hostList.name
])
.factory('HostsNewForm', HostsNewForm)
.factory('HostsNewList', HostsNewList);

View File

@ -0,0 +1,14 @@
<div class="tab-pane" id="inventoriesnew-panel">
<div ui-view="form"></div>
<div ng-cloak id="htmlTemplate" class="Panel">
<div class="row Form-tabRow">
<div class="col-lg-12">
<div class="Form-tabHolder">
<div class="Form-tab Form-tab--notitle" ng-click="$state.go('inventoriesnew')" ng-class="{'is-selected': $state.includes('inventoriesnew')}" translate>INVENTORIES</div>
<div class="Form-tab Form-tab--notitle" ng-click="$state.go('hostsnew')" ng-class="{'is-selected': $state.includes('hostsnew')}" translate>HOSTS</div>
</div>
</div>
</div>
<div ui-view="list"></div>
</div>
</div>

View File

@ -0,0 +1,135 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name forms.function:Inventories
* @description This form is for adding/editing an inventory
*/
export default ['i18n', function(i18n) {
return {
addTitle: i18n._('NEW INVENTORY'),
editTitle: '{{ inventory_name }}',
name: 'inventory',
basePath: 'inventory',
// the top-most node of this generated state tree
stateTree: 'inventoriesnew',
tabs: true,
fields: {
inventory_name: {
realName: 'name',
label: i18n._('Name'),
type: 'text',
required: true,
capitalize: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
inventory_description: {
realName: 'description',
label: i18n._('Description'),
type: 'text',
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
organization: {
label: i18n._('Organization'),
type: 'lookup',
basePath: 'organizations',
list: 'OrganizationList',
sourceModel: 'organization',
sourceField: 'name',
awRequiredWhen: {
reqExpression: "organizationrequired",
init: "true"
},
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg',
awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg'
},
variables: {
label: i18n._('Variables'),
type: 'textarea',
class: 'Form-formGroup--fullWidth',
rows: 6,
"default": "---",
awPopOver: "<p>" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>' + i18n.sprintf(i18n._('View JSON examples at %s'), '<a href="http://www.json.org" target="_blank">www.json.org</a>') + '</p>' +
'<p>' + i18n.sprintf(i18n._('View YAML examples at %s'), '<a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a>') + '</p>',
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: '&#43; 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',
}
}
}
}
};}];

View File

@ -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."), "<i class=\"icon-plus\"></i> "),
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: '&#43; ' + 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'
}
}
};}];

View File

@ -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 <div>. 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" });
elem.removeAttr('ng-click');
$compile(elem)($scope);
$scope.triggerPopover(event);
}
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 = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>";
html += "<th>Status</th>";
html += "<th>Finished</th>";
html += "<th>Name</th>";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
data.results.forEach(function(row) {
html += "<tr>\n";
html += "<td><a href=\"#/jobs/" + row.id + "\" " + "aw-tool-tip=\"" + row.status.charAt(0).toUpperCase() + row.status.slice(1) +
". Click for details\" aw-tip-placement=\"top\"><i class=\"fa SmartStatus-tooltip--" + row.status + " icon-job-" + row.status + "\"></i></a></td>\n";
html += "<td>" + ($filter('longDate')(row.finished)).replace(/ /,'<br />') + "</td>";
html += "<td><a href=\"#/jobs/" + row.id + "\" " + "aw-tool-tip=\"" + row.status.charAt(0).toUpperCase() + row.status.slice(1) +
". Click for details\" aw-tip-placement=\"top\">" + $filter('sanitize')(ellipsis(row.name)) + "</a></td>";
html += "</tr>\n";
});
html += "</tbody>\n";
html += "</table>\n";
}
else {
html = "<p>No recent job data available for this inventory.</p>\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 = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>";
html += "<th>Status</th>";
html += "<th>Last Sync</th>";
html += "<th>Group</th>";
html += "</tr>";
html += "</thead>\n";
html += "<tbody>\n";
data.results.forEach( function(row) {
if (row.related.last_update) {
html += "<tr>";
html += `<td><a href="" ng-click="viewJob('${row.related.last_update}')" aw-tool-tip="${row.status.charAt(0).toUpperCase() + row.status.slice(1)}. Click for details" aw-tip-placement="top"><i class="SmartStatus-tooltip--${row.status} fa icon-job-${row.status}"></i></a></td>`;
html += "<td>" + ($filter('longDate')(row.last_updated)).replace(/ /,'<br />') + "</td>";
html += "<td><a href=\"\" ng-click=\"viewJob('" + row.related.last_update + "')\">" + $filter('sanitize')(ellipsis(row.summary_fields.group.name)) + "</a></td>";
html += "</tr>\n";
}
else {
html += "<tr>";
html += "<td><a href=\"\" aw-tool-tip=\"No sync data\" aw-tip-placement=\"top\"><i class=\"fa icon-job-none\"></i></a></td>";
html += "<td>NA</td>";
html += "<td><a href=\"\">" + $filter('sanitize')(ellipsis(row.summary_fields.group.name)) + "</a></td>";
html += "</tr>\n";
}
});
html += "</tbody>\n";
html += "</table>\n";
title = "Sync Status";
attachElem(event, html, title);
});
$scope.showGroupSummary = function(event, id) {
try{
var elem = $(event.target).parent();
// if the popover is visible already, then exit the function here
if(elem.data()['bs.popover'].tip().hasClass('in')){
return;
}
}
catch(err){
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) {
try{
var elem = $(event.target).parent();
// if the popover is visible already, then exit the function here
if(elem.data()['bs.popover'].tip().hasClass('in')){
return;
}
}
catch(err){
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.addInventory = function () {
$state.go('inventoriesnew.add');
};
$scope.editInventory = function (id) {
$state.go('inventoriesnew.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 () {
if (parseInt($state.params.inventory_id) === id) {
$state.go("^", null, {reload: true});
} else {
$state.go('.', null, {reload: true});
Wait('stop');
}
})
.error(function (data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
});
});
};
Prompt({
hdr: 'Delete',
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the inventory below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
action: action,
actionText: 'DELETE'
});
};
// 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',
'$compile', '$filter', 'Rest', 'InventoriesNewList',
'Prompt', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', 'Dataset', InventoriesList
];

View File

@ -0,0 +1,11 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import controller from './inventory-list.controller';
export default
angular.module('newInventoryList', [])
.controller('NewInventoryListController', controller);

View File

@ -0,0 +1,236 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import hostnew from './hosts/main';
import inventoryAdd from './add/main';
import inventoryEdit from './edit/main';
import inventoryList from './list/main';
import { templateUrl } from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
// import inventoriesnewRoute from './inventories.route';
import InventoriesNewList from './inventory.list';
import InventoriesNewForm from './inventory.form';
export default
angular.module('inventorynew', [
hostnew.name,
inventoryAdd.name,
inventoryEdit.name,
inventoryList.name
])
.factory('InventoriesNewForm', InventoriesNewForm)
.factory('InventoriesNewList', InventoriesNewList)
.config(['$stateProvider', '$stateExtenderProvider', 'stateDefinitionsProvider',
function($stateProvider, $stateExtenderProvider, stateDefinitionsProvider) {
// When stateDefinition.lazyLoad() resolves, states matching name.** or /url** will be de-registered and replaced with resolved states
// This means inventoryManage states will not be registered correctly on page refresh, unless they're registered at the same time as the inventories state tree
let stateDefinitions = stateDefinitionsProvider.$get();
$stateProvider.state({
name: 'inventoriesnew',
url: '/inventoriesnew',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'inventoriesnew', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'InventoriesNewList',
form: 'InventoriesNewForm',
controllers: {
list: 'NewInventoryListController',
add: 'NewInventoryAddController',
edit: 'NewInventoryEditController'
},
ncyBreadcrumb: {
label: N_('INVENTORIESNEW')
},
views: {
'@': {
templateUrl: templateUrl('inventoriesnew/inventories')
},
'list@inventoriesnew': {
templateProvider: function(InventoriesNewList, generateList) {
let html = generateList.build({
list: InventoriesNewList,
mode: 'edit'
});
return html;
},
controller: 'NewInventoryListController'
}
}
})
});
$stateProvider.state({
name: 'hostsnew',
url: '/hostsnew',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'hostsnew', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'HostsNewList',
form: 'HostsNewForm',
controllers: {
list: 'NewHostListController',
add: 'NewHostAddController',
edit: 'NewHostEditController'
},
ncyBreadcrumb: {
label: N_('HOSTSNEW')
},
views: {
'@': {
templateUrl: templateUrl('inventoriesnew/inventories')
},
'list@hostsnew': {
templateProvider: function(HostsNewList, generateList) {
let html = generateList.build({
list: HostsNewList,
mode: 'edit'
});
return html;
},
controller: 'NewHostListController'
}
}
})
});
// function generateInvAddStateTree() {
//
// let addInventory = stateDefinitions.generateTree({
// url: '/add',
// name: 'inventoriesnew.add',
// modes: ['add'],
// form: 'InventoriesNewForm',
// controllers: {
// add: 'NewInventoryAddController'
// }
// });
//
// return Promise.all([
// addInventory,
// ]).then((generated) => {
// return {
// states: _.reduce(generated, (result, definition) => {
// return result.concat(definition.states);
// }, [])
// };
// });
// }
//
// function generateInvEditStateTree() {
//
// let editInventory = stateDefinitions.generateTree({
// url: '/:inventory_id',
// name: 'inventoriesnew.edit',
// modes: ['edit'],
// form: 'InventoriesNewForm',
// controllers: {
// edit: 'NewInventoryEditController'
// }
// });
//
// return Promise.all([
// editInventory,
// ]).then((generated) => {
// return {
// states: _.reduce(generated, (result, definition) => {
// return result.concat(definition.states);
// }, [])
// };
// });
// }
//
// let inventoriesnew = {
// name: 'inventoriesnew',
// route: '/inventoriesnew',
// ncyBreadcrumb: {
// label: N_("INVENTORIESNEW")
// },
// params: {
// inventory_search: {
// value: {order_by: 'name', page_size: '20', role_level: 'admin_role'},
// dynamic: true
// }
// },
// resolve: {
// Dataset: ['InventoriesNewList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => {
// let path = GetBasePath(list.basePath) || GetBasePath(list.name);
// return qs.search(path, $stateParams[`${list.iterator}_search`]);
// }],
// ListDefinition: ['InventoriesNewList', (list) => {
// return list;
// }]
// },
// views: {
// '@': {
// templateUrl: templateUrl('inventoriesnew/inventories')
// },
// 'list@inventoriesnew': {
// templateProvider: function(InventoriesNewList, generateList) {
// let html = generateList.build({
// list: InventoriesNewList,
// mode: 'edit'
// });
// return html;
// },
// controller: 'NewInventoryListController'
// }
// }
// };
// stateExtender.addState(inventoriesnew);
//
// let hostsnew = {
// name: 'inventoriesnew.hosts',
// route: '/hosts',
// ncyBreadcrumb: {
// label: N_("HOSTS")
// },
// params: {
// host_search: {
// value: {order_by: 'name', page_size: '20'},
// dynamic: true
// }
// },
// resolve: {
// Dataset: ['HostsNewList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => {
// let path = GetBasePath(list.basePath) || GetBasePath(list.name);
// return qs.search(path, $stateParams[`${list.iterator}_search`]);
// }],
// ListDefinition: ['HostsNewList', (list) => {
// return list;
// }]
// },
// views: {
// 'list@inventoriesnew': {
// templateProvider: function(HostsNewList, generateList) {
// let html = generateList.build({
// list: HostsNewList,
// mode: 'edit'
// });
// return html;
// },
// controller: 'NewHostListController'
// }
// }
// };
// stateExtender.addState(hostsnew);
//
// let addInventoryTree = {
// name: 'inventoriesnew.add',
// url: '/add',
// lazyLoad: () => generateInvAddStateTree()
// };
// $stateProvider.state(addInventoryTree);
//
// let editInventoryTree = {
// name: 'inventoriesnew.edit',
// url: '/:inventory_id',
// lazyLoad: () => generateInvEditStateTree()
// };
// $stateProvider.state(editInventoryTree);
}
]);

View File

@ -27,6 +27,14 @@
<translate>INVENTORIES</translate>
</span>
</a>
<a class="MainMenu-item"
id="main_menu_inventoriesnew_mobile_link"
href="/#/inventoriesnew"
ng-class="{'is-currentRoute' : isCurrentState('inventoriesnew')}">
<span class="MainMenu-itemText">
<translate>INVENTORIES NEW</translate>
</span>
</a>
<a class="MainMenu-item"
id="main_menu_job_templates_mobile_link"
href="/#/templates"
@ -104,6 +112,15 @@
<translate>INVENTORIES</translate>
</span>
</a>
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
id="main_menu_inventoriesnew_link"
href="/#/inventoriesnew"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('inventoriesnew'), 'is-loggedOut' : !current_user || !current_user.username}">
<span class="MainMenu-itemText">
<translate>INVENTORIES NEW</translate>
</span>
</a>
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
id="main_menu_job_templates_link"
href="/#/templates"

View File

@ -89,6 +89,16 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
};
}
}
let views = params.views ? params.views : {
'@': {
// resolves to a variable property name:
// 'templateUrl' OR 'templateProvider'
[params.templates && params.templates.list ? 'templateUrl' : 'templateProvider']: generateTemplateBlock(),
controller: params.controllers.list,
}
};
state = $stateExtender.buildDefinition({
searchPrefix: list.iterator,
name: params.parent,
@ -106,14 +116,7 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
],
ListDefinition: () => list
},
views: {
'@': {
// resolves to a variable property name:
// 'templateUrl' OR 'templateProvider'
[params.templates && params.templates.list ? 'templateUrl' : 'templateProvider']: generateTemplateBlock(),
controller: params.controllers.list,
}
}
views: views
});
// allow passed-in params to override default resolve block
if (params.resolve && params.resolve.list) {
@ -125,7 +128,7 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
}
if (list.search) {
state.params[`${list.iterator}_search`].value = _.merge(state.params[`${list.iterator}_search`].value, list.search);
}
}if(state.name === 'inventoriesnew'){console.log(state);}
return state;
},
/**