Removed old inventories code. Cleaned up new inventories code

This commit is contained in:
Michael Abashian 2017-04-06 15:20:55 -04:00 committed by Jared Tabor
parent 3bcb4b0496
commit b741354ad0
71 changed files with 572 additions and 3536 deletions

View File

@ -40,7 +40,6 @@ 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';
@ -100,7 +99,6 @@ var tower = angular.module('Tower', [
configuration.name,
systemTracking.name,
inventories.name,
inventorynew.name,
inventoryScripts.name,
credentialTypes.name,
organizations.name,

View File

@ -77,7 +77,7 @@ function InventoriesAdd($scope, $location,
.success(function(data) {
var inventory_id = data.id;
Wait('stop');
$location.path('/inventories/' + inventory_id + '/manage');
$location.path('/inventories/' + inventory_id);
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {

View File

@ -7,5 +7,5 @@
import controller from './inventory-add.controller';
export default
angular.module('inventoryAdd', [])
angular.module('InventoryAdd', [])
.controller('InventoryAddController', controller);

View File

@ -11,13 +11,13 @@
*/
function InventoriesEdit($scope, $location,
$stateParams, InventoryForm, Rest, ProcessErrors,
$stateParams, InventoriesForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
ParseVariableString, $state, OrgAdminLookup) {
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = InventoryForm,
form = InventoriesForm,
inventory_id = $stateParams.inventory_id,
master = {},
fld, json_data, data;
@ -122,10 +122,6 @@ function InventoriesEdit($scope, $location,
});
};
$scope.manageInventory = function() {
$location.path($location.path() + '/manage');
};
$scope.formCancel = function() {
$state.go('inventories');
};
@ -133,7 +129,7 @@ function InventoriesEdit($scope, $location,
}
export default ['$scope', '$location',
'$stateParams', 'InventoryForm', 'Rest',
'$stateParams', 'InventoriesForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ToJSON', 'ParseVariableString',
'$state', 'OrgAdminLookup', InventoriesEdit,

View File

@ -7,5 +7,5 @@
import controller from './inventory-edit.controller';
export default
angular.module('inventoryEdit', [])
angular.module('InventoryEdit', [])
.controller('InventoryEditController', controller);

View File

@ -0,0 +1,93 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import GroupEditController from './groups-edit.controller';
export default ['$stateExtender', 'templateUrl', '$injector',
'stateDefinitions','GroupForm','nestedGroupListState',
'nestedHostsListState', 'buildHostAddState', 'buildHostEditState',
'nestedGroupAddState',
function($stateExtender, templateUrl, $injector, stateDefinitions, GroupForm,
nestedGroupListState, nestedHostsListState, buildHostAddState,
buildHostEditState, nestedGroupAddState){
var val = function(field, formStateDefinition, params) {
let state, states = [],
list = field.include ? $injector.get(field.include) : field,
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
stateConfig = {
name: `${formStateDefinition.name}.${list.iterator}s.edit`,
url: `/edit/:group_id`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
label: `${breadcrumbLabel}`
},
views: {
'groupForm@inventories': {
templateProvider: function(GenerateForm, GroupForm) {
let form = GroupForm;
return GenerateForm.buildHTML(form, {
mode: 'edit',
related: false
});
},
controller: GroupEditController
}
},
resolve: {
'FormDefinition': [params.form, function(definition) {
return definition;
}],
groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService) {
return GroupManageService.get({ id: $stateParams.group_id }).then(res => res.data.results[0]);
}]
}
};
state = $stateExtender.buildDefinition(stateConfig);
let relatedGroupListState = nestedGroupListState(GroupForm.related.nested_groups, state, params);
let relatedGroupsAddState = nestedGroupAddState(GroupForm.related.nested_groups, state, params);
relatedGroupListState = $stateExtender.buildDefinition(relatedGroupListState);
relatedGroupsAddState = $stateExtender.buildDefinition(relatedGroupsAddState);
let relatedHostsListState = nestedHostsListState(GroupForm.related.nested_hosts, state, params);
let relatedHostsAddState = buildHostAddState(GroupForm.related.nested_hosts, state, params);
let relatedHostsEditState = buildHostEditState(GroupForm.related.nested_hosts, state, params);
relatedHostsListState = $stateExtender.buildDefinition(relatedHostsListState);
relatedHostsAddState = $stateExtender.buildDefinition(relatedHostsAddState);
if(Array.isArray(relatedHostsEditState))
{
relatedHostsEditState[0] = $stateExtender.buildDefinition(relatedHostsEditState[0]);
relatedHostsEditState[1] = $stateExtender.buildDefinition(relatedHostsEditState[1]);
states.push(state,
relatedGroupListState,
relatedGroupsAddState,
relatedHostsListState,
relatedHostsAddState,
relatedHostsEditState[0],
relatedHostsEditState[1]);
}
else {
relatedHostsEditState = $stateExtender.buildDefinition(relatedHostsEditState);
states.push(state,
relatedGroupListState,
relatedGroupsAddState,
relatedHostsListState,
relatedHostsAddState,
relatedHostsEditState);
}
// states.push(state,
// relatedGroupListState,
// relatedGroupsAddState,
// relatedHostsListState,
// relatedHostsAddState,
// relatedHostsEditState[0],
// relatedHostsEditState[1]);
return states;
};
return val;
}
];

View File

@ -0,0 +1,85 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import NestedGroupsListController from './nested-groups-list.controller';
export default ['$stateExtender', 'templateUrl', '$injector',
function($stateExtender, templateUrl, $injector){
var val = function(field, formStateDefinition) {
let state,
list = field.include ? $injector.get(field.include) : field,
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
stateConfig = {
searchPrefix: `${list.iterator}`,
squash: '',
name: `${formStateDefinition.name}.nested_groups`,
url: `/${list.iterator}s`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
label: `${breadcrumbLabel}`
},
params: {
[list.iterator + '_search']: {
value: { order_by: field.order_by ? field.order_by : 'name' }
},
},
views: {
// 'related@inventories.edit.groups.edit': {
'related': {
templateProvider: function(NestedGroupListDefinition, generateList) {
let list = _.cloneDeep(NestedGroupListDefinition);
let html = generateList.build({
list: list,
mode: 'edit'
});
// Include the custom group delete modal template
// return $templateRequest(templateUrl('inventories/groups/list/groups-list')).then((template) => {
// return html.concat(template);
// });
return html;
},
controller: NestedGroupsListController
}
},
resolve: {
ListDefinition: () => {
return list;
},
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope',
(list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => {
// allow related list definitions to use interpolated $rootScope / $stateParams in basePath field
let path, interpolator;
if (GetBasePath(list.basePath)) {
path = GetBasePath(list.basePath);
} else {
interpolator = $interpolate(list.basePath);
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
}
if($stateParams.group_id){
path = `api/v2/groups/${$stateParams.group_id}/children`;
}
else if($stateParams.host_id){
path = GetBasePath('hosts') + $stateParams.host_id + '/all_groups';
}
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
inventoryData: ['InventoryManageService', '$stateParams', 'host', function(InventoryManageService, $stateParams, host) {
var id = ($stateParams.inventory_id) ? $stateParams.inventory_id : host.data.summary_fields.inventory.id;
return InventoryManageService.getInventory(id).then(res => res.data);
}]
}
};
state = $stateExtender.buildDefinition(stateConfig);
// appy any default search parameters in form definition
if (field.search) {
state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search);
}
return state;
};
return val;
}
];

View File

@ -0,0 +1,162 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostManageService', 'SetStatus',
function($scope, NestedHostsListDefinition, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostManageService, SetStatus) {
let list = NestedHostsListDefinition;
init();
function init(){
$scope.canAdd = false;
$scope.enableSmartInventoryButton = 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.$watchCollection(list.name, function() {
$scope[list.name] = _.map($scope.nested_hosts, function(value) {
value.inventory_name = value.summary_fields.inventory.name;
value.inventory_id = value.summary_fields.inventory.id;
return value;
});
setJobStatus();
});
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if(toState.name === 'hosts.addSmartInventory') {
$scope.enableSmartInventoryButton = false;
}
else {
if(toParams && toParams.host_search) {
let hasMoreThanDefaultKeys = false;
angular.forEach(toParams.host_search, function(value, key) {
if(key !== 'order_by' && key !== 'page_size') {
hasMoreThanDefaultKeys = true;
}
});
$scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false;
}
else {
$scope.enableSmartInventoryButton = false;
}
}
});
$scope.$on('selectedOrDeselected', function(e, value) {
let item = value.value;
if (value.isSelected) {
if(!$scope.hostsSelected) {
$scope.hostsSelected = [];
}
$scope.hostsSelected.push(item);
} else {
_.remove($scope.hostsSelected, { id: item.id });
if($scope.hostsSelected.length === 0) {
$scope.hostsSelected = null;
}
}
$scope.systemTrackingDisabled = ($scope.hostsSelected && $scope.hostsSelected.length > 2) ? true : false;
});
}
function setJobStatus(){
_.forEach($scope.hosts, function(value) {
SetStatus({
scope: $scope,
host: value
});
});
}
$scope.createHost = function(){
$state.go('inventories.edit.groups.edit.nested_hosts.add');
};
$scope.editHost = function(id){
$state.go('inventories.edit.groups.edit.nested_hosts.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("hosts", 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});
});
};
$scope.smartInventory = function() {
$state.go('inventories.addSmartInventory');
};
$scope.systemTracking = function(){
var hostIds = _.map($scope.hostsSelected, (host) => host.id);
$state.go('systemTracking', {
inventoryId: $state.params.inventory_id,
hosts: $scope.hostsSelected,
hostIds: hostIds
});
};
$scope.setAdhocPattern = function(){
var pattern = _($scope.hostsSelected)
.map(function(item){
return item.name;
}).value().join(':');
$state.go('^.adhoc', {pattern: pattern});
};
$scope.copyMoveHost = function(id) {
$state.go('inventories.edit.hosts.copyMoveHost', {host_id: id});
};
}];

View File

@ -10,7 +10,8 @@
* @description This form is for adding/editing a host on the inventory page
*/
export default ['i18n', function(i18n) {
export default ['i18n', 'nestedGroupListState',
function(i18n, nestedGroupListState) {
return {
addTitle: i18n._('CREATE HOST'),
@ -21,6 +22,9 @@ export default ['i18n', function(i18n) {
formLabelSize: 'col-lg-3',
formFieldSize: 'col-lg-9',
iterator: 'host',
// activeEditState: 'inventories.edit.hosts.edit',
activeEditState: 'inventories.edit.groups.edit.nested_hosts.edit',
stateTree: 'inventories.edit.groups.edit.nested_hosts',
headerFields:{
enabled: {
class: 'Form-header-field',
@ -99,5 +103,27 @@ export default ['i18n', function(i18n) {
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
ansible_facts: {
name: 'ansible_facts',
title: i18n._('Facts'),
skipGenerator: true
},
nested_groups: {
name: 'nested_groups',
ngClick: "$state.go('inventories.edit.groups.edit.nested_hosts.edit.nested_groups')",
include: "NestedGroupListDefinition",
includeForm: "NestedGroupFormDefinition",
title: i18n._('Groups'),
iterator: 'nested_group',
listState: nestedGroupListState
},
insights: {
name: 'insights',
title: i18n._('Insights'),
skipGenerator: true
}
}
};
}];

View File

@ -8,4 +8,4 @@ import controller from './host-add.controller';
export default
angular.module('hostsAdd', [])
.controller('NewHostAddController', controller);
.controller('HostAddController', controller);

View File

@ -8,4 +8,4 @@ import controller from './host-edit.controller';
export default
angular.module('hostsEdit', [])
.controller('NewHostEditController', controller);
.controller('HostEditController', controller);

View File

@ -17,6 +17,7 @@ export default ['i18n', function(i18n) {
hasChildren: true,
'class': 'table-no-border',
trackBy: 'host.id',
basePath: 'hosts',
title: false,
fields: {

View File

@ -4,11 +4,12 @@
* All Rights Reserved
*************************************************/
function HostsList($scope, HostsNewList, $rootScope, GetBasePath,
function HostsList($scope, HostsList, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostManageService, SetStatus) {
let list = HostsNewList;
let list = HostsList;
init();
@ -48,10 +49,10 @@ function HostsList($scope, HostsNewList, $rootScope, GetBasePath,
}
$scope.createHost = function(){
$state.go('hostsnew.add');
$state.go('hosts.add');
};
$scope.editHost = function(id){
$state.go('hostsnew.edit', {host_id: id});
$state.go('hosts.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>';
@ -61,7 +62,7 @@ function HostsList($scope, HostsNewList, $rootScope, GetBasePath,
HostManageService.delete(id).then(() => {
$('#prompt-modal').modal('hide');
if (parseInt($state.params.host_id) === id) {
$state.go("hostsnew", null, {reload: true});
$state.go("hosts", null, {reload: true});
} else {
$state.go($state.current.name, null, {reload: true});
}
@ -94,7 +95,7 @@ function HostsList($scope, HostsNewList, $rootScope, GetBasePath,
}
export default ['$scope', 'HostsNewList', '$rootScope', 'GetBasePath',
export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostManageService', 'SetStatus', HostsList
];

View File

@ -8,4 +8,4 @@ import controller from './host-list.controller';
export default
angular.module('hostsList', [])
.controller('NewHostListController', controller);
.controller('HostListController', controller);

View File

@ -0,0 +1,26 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import hostAdd from './add/main';
import hostEdit from './edit/main';
import hostList from './list/main';
import HostsList from './host.list';
import HostsForm from './host.form';
import HostManageService from './hosts.service';
import SetStatus from './set-status.factory';
import SetEnabledMsg from './set-enabled-msg.factory';
export default
angular.module('host', [
hostAdd.name,
hostEdit.name,
hostList.name
])
.factory('HostsForm', HostsForm)
.factory('HostsList', HostsList)
.factory('SetStatus', SetStatus)
.factory('SetEnabledMsg', SetEnabledMsg)
.service('HostManageService', HostManageService);

View File

@ -1,11 +1,11 @@
<div class="tab-pane" id="inventoriesnew-panel">
<div class="tab-pane" id="inventories-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 class="Form-tab Form-tab--notitle" ng-click="$state.go('inventories')" ng-class="{'is-selected': $state.includes('inventories')}" translate>INVENTORIES</div>
<div class="Form-tab Form-tab--notitle" ng-click="$state.go('hosts')" ng-class="{'is-selected': $state.includes('hosts')}" translate>HOSTS</div>
</div>
</div>
</div>

View File

@ -17,6 +17,7 @@ export default ['i18n', function(i18n) {
index: false,
hover: true,
basePath: 'inventory',
title: false,
fields: {
status: {
@ -44,9 +45,9 @@ export default ['i18n', function(i18n) {
label: i18n._('Name'),
columnClass: 'col-md-5 col-sm-5 col-xs-8 List-staticColumnAdjacent',
modalColumnClass: 'col-md-11',
linkTo: '/#/inventories/{{inventory.id}}/manage',
awToolTip: "{{ inventory.description }}",
awTipPlacement: "top"
linkTo: '/#/inventories/{{inventory.id}}'
},
organization: {
label: i18n._('Organization'),

View File

@ -11,10 +11,10 @@
*/
function InventoriesList($scope, $rootScope, $location,
$compile, $filter, Rest, InventoryList, Prompt,
$compile, $filter, Rest, InventoriesList, Prompt,
ProcessErrors, GetBasePath, Wait, Find, Empty, $state, rbacUiControlService, Dataset) {
let list = InventoryList,
let list = InventoriesList,
defaultUrl = GetBasePath('inventory');
init();
@ -250,7 +250,7 @@ function InventoriesList($scope, $rootScope, $location,
};
$scope.addInventory = function () {
$state.go('inventoriesnew.add');
$state.go('inventories.add');
};
$scope.editInventory = function (id) {
@ -303,6 +303,6 @@ function InventoriesList($scope, $rootScope, $location,
}
export default ['$scope', '$rootScope', '$location',
'$compile', '$filter', 'Rest', 'InventoryList',
'$compile', '$filter', 'Rest', 'InventoriesList',
'Prompt', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', 'Dataset', InventoriesList
];

View File

@ -7,5 +7,5 @@
import controller from './inventory-list.controller';
export default
angular.module('inventoryList', [])
angular.module('InventoryList', [])
.controller('InventoryListController', controller);

View File

@ -4,312 +4,101 @@
* All Rights Reserved
*************************************************/
import host from './hosts/main';
import inventoryAdd from './add/main';
import inventoryEdit from './edit/main';
import inventoryList from './list/main';
import inventoryManage from './manage/main';
import inventoryManageListRoute from './manage/inventory-manage.route';
import { copyMoveGroupRoute, copyMoveHostRoute } from './manage/copy-move/copy-move.route';
import adHocRoute from './manage/adhoc/adhoc.route';
import { templateUrl } from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
// actual inventory list config object
import InventoryList from './inventory.list';
import InventoryForm from './inventory.form';
import InventoriesList from './inventory.list';
import InventoriesForm from './inventory.form';
export default
angular.module('inventory', [
host.name,
inventoryAdd.name,
inventoryEdit.name,
inventoryList.name,
inventoryManage.name,
inventoryList.name
])
.factory('InventoryList', InventoryList)
.factory('InventoryForm', InventoryForm)
.config(['$stateProvider', '$stateExtenderProvider', 'stateDefinitionsProvider',
function($stateProvider, $stateExtenderProvider, stateDefinitionsProvider) {
.factory('InventoriesForm', InventoriesForm)
.factory('InventoriesList', InventoriesList)
.config(['$stateProvider', 'stateDefinitionsProvider',
function($stateProvider, 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 stateTree, inventories,
addGroup, editGroup, addHost, editHost,
listSchedules, addSchedule, editSchedule, adhocCredentialLookup,
stateDefinitions = stateDefinitionsProvider.$get(),
stateExtender = $stateExtenderProvider.$get();
let stateDefinitions = stateDefinitionsProvider.$get();
function generateStateTree() {
// inventories state node
inventories = stateDefinitions.generateTree({
parent: 'inventories', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'InventoryList',
form: 'InventoryForm',
controllers: {
list: 'InventoryListController',
add: 'InventoryAddController',
edit: 'InventoryEditController'
},
data: {
activityStream: true,
activityStreamTarget: 'inventory'
},
ncyBreadcrumb: {
label: N_('INVENTORIES')
}
});
// scheduler state nodes
listSchedules = {
name: 'inventoryManage.editGroup.schedules',
url: '/schedules',
searchPrefix: 'schedule',
ncyBreadcrumb: {
parent: 'inventoryManage({group_id: parentObject.id})',
label: N_('SCHEDULES')
},
resolve: {
Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', 'groupData',
function(list, qs, $stateParams, GetBasePath, groupData) {
let path = `${groupData.related.inventory_source}schedules`;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
ParentObject: ['groupData', function(groupData) {
return groupData;
}],
UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q',
function(Rest, GetBasePath, $stateParams, $q) {
Rest.setUrl(GetBasePath('unified_jobs'));
var val = $q.defer();
Rest.options()
.then(function(data) {
val.resolve(data.data);
}, function(data) {
val.reject(data);
});
return val.promise;
}],
ScheduleList: ['SchedulesList', 'groupData',
(SchedulesList, groupData) => {
let list = _.cloneDeep(SchedulesList);
list.basePath = `${groupData.related.inventory_source}schedules`;
return list;
}
]
},
views: {
// clear form template when views render in this substate
'form': {
templateProvider: () => ''
$stateProvider.state({
name: 'inventories',
url: '/inventories',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'inventories', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'InventoriesList',
form: 'InventoriesForm',
controllers: {
list: 'InventoryListController',
add: 'InventoryAddController',
edit: 'InventoryEditController'
},
// target the un-named ui-view @ root level
'@': {
templateProvider: function(ScheduleList, generateList, ParentObject) {
// include name of parent resource in listTitle
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('SCHEDULES');
let html = generateList.build({
list: ScheduleList,
mode: 'edit'
});
html = generateList.wrapPanel(html);
return "<div class='InventoryManage-container'>" + generateList.insertFormView() + html + "</div>";
urls: {
list: '/inventories'
},
ncyBreadcrumb: {
label: N_('INVENTORIES')
},
views: {
'@': {
templateUrl: templateUrl('inventories/inventories')
},
controller: 'schedulerListController'
}
}
};
addSchedule = {
name: 'inventoryManage.editGroup.schedules.add',
url: '/add',
ncyBreadcrumb: {
label: N_("CREATE SCHEDULE")
},
views: {
'form': {
controller: 'schedulerAddController',
templateUrl: templateUrl("scheduler/schedulerForm")
}
}
};
editSchedule = {
name: 'inventoryManage.editGroup.schedules.edit',
url: '/:schedule_id',
ncyBreadcrumb: {
label: "{{schedule_obj.name}}"
},
views: {
'form': {
templateUrl: templateUrl("scheduler/schedulerForm"),
controller: 'schedulerEditController',
}
}
};
// group state nodes
addGroup = stateDefinitions.generateTree({
url: '/add-group',
name: 'inventoryManage.addGroup',
modes: ['add'],
form: 'GroupForm',
controllers: {
add: 'GroupAddController'
}
});
editGroup = stateDefinitions.generateTree({
url: '/edit-group/:group_id',
name: 'inventoryManage.editGroup',
modes: ['edit'],
form: 'GroupForm',
controllers: {
edit: 'GroupEditController'
},
resolve: {
edit: {
groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService) {
return GroupManageService.get({ id: $stateParams.group_id }).then(res => res.data.results[0]);
}],
inventorySourceData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService) {
return GroupManageService.getInventorySource({ group: $stateParams.group_id }).then(res => res.data.results[0]);
}]
}
},
// concat boilerplate schedule state definitions with generated editGroup state definitions
}).then((generated) => {
let schedulerDefinitions = _.map([
stateExtender.buildDefinition(listSchedules),
stateExtender.buildDefinition(addSchedule),
stateExtender.buildDefinition(editSchedule)
],
(state) => stateExtender.buildDefinition(state));
return {
states: _(generated.states)
.concat(schedulerDefinitions)
.value()
};
});
// host state nodes
addHost = stateDefinitions.generateTree({
url: '/add-host',
name: 'inventoryManage.addHost',
modes: ['add'],
form: 'HostForm',
controllers: {
add: 'HostsAddController'
}
});
editHost = stateDefinitions.generateTree({
url: '/edit-host/:host_id',
name: 'inventoryManage.editHost',
modes: ['edit'],
form: 'HostForm',
controllers: {
edit: 'HostEditController'
},
resolve: {
edit: {
host: ['$stateParams', 'HostManageService', function($stateParams, HostManageService) {
return HostManageService.get({ id: $stateParams.host_id }).then(function(res) {
return res.data.results[0];
});
}]
}
},
ncyBreadcrumb: {
label: "{{host.name}}",
},
});
adhocCredentialLookup = {
searchPrefix: 'credential',
name: 'inventoryManage.adhoc.credential',
url: '/credential',
data: {
formChildState: true
},
params: {
credential_search: {
value: {
page_size: '5'
},
squash: true,
dynamic: true
}
},
ncyBreadcrumb: {
skip: true
},
views: {
'related': {
templateProvider: function(ListDefinition, generateList) {
let list_html = generateList.build({
mode: 'lookup',
list: ListDefinition,
input_type: 'radio'
});
return `<lookup-modal>${list_html}</lookup-modal>`;
'list@inventories': {
templateProvider: function(InventoriesList, generateList) {
let html = generateList.build({
list: InventoriesList,
mode: 'edit'
});
return html;
},
controller: 'InventoryListController'
}
}
},
resolve: {
ListDefinition: ['CredentialList', function(CredentialList) {
let list = _.cloneDeep(CredentialList);
list.lookupConfirmText = 'SELECT';
return list;
}],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.name) || GetBasePath(list.basePath);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
},
onExit: function($state) {
if ($state.transition) {
$('#form-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
},
};
return Promise.all([
inventories,
addGroup,
editGroup,
addHost,
editHost,
]).then((generated) => {
return {
states: _.reduce(generated, (result, definition) => {
return result.concat(definition.states);
}, [
stateExtender.buildDefinition(inventoryManageListRoute),
stateExtender.buildDefinition(copyMoveGroupRoute),
stateExtender.buildDefinition(copyMoveHostRoute),
stateExtender.buildDefinition(adHocRoute),
stateExtender.buildDefinition(adhocCredentialLookup)
])
};
})
});
}
stateTree = {
name: 'inventories',
url: '/inventories',
ncyBreadcrumb: {
label: N_("INVENTORIES")
},
lazyLoad: () => generateStateTree()
};
$stateProvider.state(stateTree);
$stateProvider.state({
name: 'hosts',
url: '/hosts',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'hosts', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'HostsList',
form: 'HostsForm',
controllers: {
list: 'HostListController',
add: 'HostAddController',
edit: 'HostEditController'
},
urls: {
list: '/hosts'
},
ncyBreadcrumb: {
label: N_('HOSTS')
},
views: {
'@': {
templateUrl: templateUrl('inventories/inventories')
},
'list@hosts': {
templateProvider: function(HostsList, generateList) {
let html = generateList.build({
list: HostsList,
mode: 'edit'
});
return html;
},
controller: 'HostListController'
}
}
})
});
}
]);

View File

@ -1,308 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Adhoc
* @description This controller controls the adhoc form creation, command launching and navigating to standard out after command has been succesfully ran.
*/
function adhocController($q, $scope, $stateParams,
$state, CheckPasswords, PromptForPasswords, CreateLaunchDialog, CreateSelect2, adhocForm,
GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices,
KindChange, Wait, ParseTypeChange) {
ClearScope();
// this is done so that we can access private functions for testing, but
// we don't want to populate the "public" scope with these internal
// functions
var privateFn = {};
this.privateFn = privateFn;
var id = $stateParams.inventory_id,
hostPattern = $stateParams.pattern;
// note: put any urls that the controller will use in here!!!!
privateFn.setAvailableUrls = function() {
return {
adhocUrl: GetBasePath('inventory') + id + '/ad_hoc_commands/',
inventoryUrl: GetBasePath('inventory') + id + '/',
machineCredentialUrl: GetBasePath('credentials') + '?kind=ssh'
};
};
var urls = privateFn.setAvailableUrls();
// set the default options for the selects of the adhoc form
privateFn.setFieldDefaults = function(verbosity_options, forks_default) {
var verbosity;
for (verbosity in verbosity_options) {
if (verbosity_options[verbosity].isDefault) {
$scope.verbosity = verbosity_options[verbosity];
}
}
$("#forks-number").spinner("value", forks_default);
$scope.forks = forks_default;
};
// set when "working" starts and stops
privateFn.setLoadingStartStop = function() {
var asyncHelper = {},
formReadyPromise = 0;
Wait('start');
if (asyncHelper.removeChoicesReady) {
asyncHelper.removeChoicesReady();
}
asyncHelper.removeChoicesReady = $scope.$on('adhocFormReady',
isFormDone);
// check to see if all requests have completed
function isFormDone() {
formReadyPromise++;
if (formReadyPromise === 2) {
privateFn.setFieldDefaults($scope.adhoc_verbosity_options,
$scope.forks_field.default);
CreateSelect2({
element: '#adhoc_module_name',
multiple: false
});
CreateSelect2({
element: '#adhoc_verbosity',
multiple: false
});
Wait('stop');
}
}
};
// set the arguments help to watch on change of the module
privateFn.instantiateArgumentHelp = function() {
$scope.$watch('module_name', function(val) {
if (val) {
// give the docs for the selected module in the popover
$scope.argsPopOver = '<p>These arguments are used with the ' +
'specified module. You can find information about the ' +
val.value + ' module <a ' +
'id=\"adhoc_module_arguments_docs_link_for_module_' +
val.value + '\" href=\"http://docs.ansible.com/' +
val.value + '_module.html\" ' +
'target=\"_blank\">here</a>.</p>';
} else {
// no module selected
$scope.argsPopOver = "<p>These arguments are used with the" +
" specified module.</p>";
}
}, true);
// initially set to the same as no module selected
$scope.argsPopOver = "<p>These arguments are used with the " +
"specified module.</p>";
};
// pre-populate host patterns from the inventory page and
// delete the value off of rootScope
privateFn.instantiateHostPatterns = function(hostPattern) {
$scope.limit = hostPattern;
$scope.providedHostPatterns = $scope.limit;
};
// call helpers to initialize lookup and select fields through get
// requests
privateFn.initializeFields = function(machineCredentialUrl, adhocUrl) {
// setup module name select
GetChoices({
scope: $scope,
url: adhocUrl,
field: 'module_name',
variable: 'adhoc_module_options',
callback: 'adhocFormReady'
});
// setup verbosity options select
GetChoices({
scope: $scope,
url: adhocUrl,
field: 'verbosity',
variable: 'adhoc_verbosity_options',
callback: 'adhocFormReady'
});
};
// instantiate all variables on scope for display in the partial
privateFn.initializeForm = function(id, urls, hostPattern) {
// inject the adhoc command form
GenerateForm.inject(adhocForm,
{ mode: 'add', related: true, scope: $scope });
// set when "working" starts and stops
privateFn.setLoadingStartStop();
// put the inventory id on scope for the partial to use
$scope.inv_id = id;
// set the arguments help to watch on change of the module
privateFn.instantiateArgumentHelp();
// pre-populate host patterns from the inventory page and
// delete the value off of rootScope
privateFn.instantiateHostPatterns(hostPattern);
privateFn.initializeFields(urls.machineCredentialUrl, urls.adhocUrl);
};
privateFn.initializeForm(id, urls, hostPattern);
// init codemirror
$scope.extra_vars = '---';
$scope.parseType = 'yaml';
$scope.envParseType = 'yaml';
ParseTypeChange({ scope: $scope, field_id: 'adhoc_extra_vars' , variable: "extra_vars"});
$scope.formCancel = function(){
$state.go('inventoryManage');
};
// remove all data input into the form and reset the form back to defaults
$scope.formReset = function () {
GenerateForm.reset();
// pre-populate host patterns from the inventory page and
// delete the value off of rootScope
privateFn.instantiateHostPatterns($scope.providedHostPatterns);
KindChange({ scope: $scope, form: adhocForm, reset: false });
// set the default options for the selects of the adhoc form
privateFn.setFieldDefaults($scope.adhoc_verbosity_options,
$scope.forks_default);
};
// launch the job with the provided form data
$scope.launchJob = function () {
var adhocUrl = GetBasePath('inventory') + $stateParams.inventory_id +
'/ad_hoc_commands/', fld, data={}, html;
html = '<form class="ng-valid ng-valid-required" ' +
'name="job_launch_form" id="job_launch_form" autocomplete="off" ' +
'nonvalidate>';
// stub the payload with defaults from DRF
data = {
"job_type": "run",
"limit": "",
"credential": "",
"module_name": "command",
"module_args": "",
"forks": 0,
"verbosity": 0,
"extra_vars": "",
"privilege_escalation": ""
};
GenerateForm.clearApiErrors($scope);
// populate data with the relevant form values
for (fld in adhocForm.fields) {
if (adhocForm.fields[fld].type === 'select') {
data[fld] = $scope[fld].value;
} else if ($scope[fld]) {
data[fld] = $scope[fld];
}
}
Wait('start');
if ($scope.removeStartAdhocRun) {
$scope.removeStartAdhocRun();
}
$scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() {
var password;
for (password in $scope.passwords) {
data[$scope.passwords[password]] = $scope[
$scope.passwords[password]
];
}
// Launch the adhoc job
Rest.setUrl(GetBasePath('inventory') +
$stateParams.inventory_id + '/ad_hoc_commands/');
Rest.post(data)
.success(function (data) {
Wait('stop');
$state.go('adHocJobStdout', {id: data.id});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, adhocForm, {
hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status });
});
});
if ($scope.removeCreateLaunchDialog) {
$scope.removeCreateLaunchDialog();
}
$scope.removeCreateLaunchDialog = $scope.$on('CreateLaunchDialog',
function(e, html, url) {
CreateLaunchDialog({
scope: $scope,
html: html,
url: url,
callback: 'StartAdhocRun'
});
});
if ($scope.removePromptForPasswords) {
$scope.removePromptForPasswords();
}
$scope.removePromptForPasswords = $scope.$on('PromptForPasswords',
function(e, passwords_needed_to_start,html, url) {
PromptForPasswords({ scope: $scope,
passwords: passwords_needed_to_start,
callback: 'CreateLaunchDialog',
html: html,
url: url
});
});
if ($scope.removeContinueCred) {
$scope.removeContinueCred();
}
$scope.removeContinueCred = $scope.$on('ContinueCred', function(e,
passwords) {
if(passwords.length>0){
$scope.passwords_needed_to_start = passwords;
// only go through the password prompting steps if there are
// passwords to prompt for
$scope.$emit('PromptForPasswords', passwords, html, adhocUrl);
} else {
// if not, go straight to trying to run the job.
$scope.$emit('StartAdhocRun', adhocUrl);
}
});
// start adhoc launching routine
CheckPasswords({
scope: $scope,
credential: $scope.credential,
callback: 'ContinueCred'
});
};
}
export default ['$q', '$scope', '$stateParams',
'$state', 'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'CreateSelect2',
'adhocForm', 'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
'GetChoices', 'KindChange', 'Wait', 'ParseTypeChange',
adhocController];

View File

@ -1,159 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name forms.function:Adhoc
* @description This form is for executing an adhoc command
*/
export default ['i18n', function(i18n) {
return {
addTitle: 'EXECUTE COMMAND',
name: 'adhoc',
well: true,
forceListeners: true,
fields: {
module_name: {
label: 'Module',
excludeModal: true,
type: 'select',
ngOptions: 'module.label for module in adhoc_module_options' +
' track by module.value',
ngChange: 'moduleChange()',
required: true,
awPopOver:'<p>These are the modules that Tower supports ' +
'running commands against.',
dataTitle: 'Module',
dataPlacement: 'right',
dataContainer: 'body'
},
module_args: {
label: 'Arguments',
type: 'text',
awPopOverWatch: 'argsPopOver',
awPopOver: '{{ argsPopOver }}',
dataTitle: 'Arguments',
dataPlacement: 'right',
dataContainer: 'body',
autocomplete: false
},
limit: {
label: 'Limit',
type: 'text',
awPopOver: '<p>The pattern used to target hosts in the ' +
'inventory. Leaving the field blank, all, and * will ' +
'all target all hosts in the inventory. You can find ' +
'more information about Ansible\'s host patterns ' +
'<a id=\"adhoc_form_hostpatterns_doc_link\"' +
'href=\"http://docs.ansible.com/intro_patterns.html\" ' +
'target=\"_blank\">here</a>.</p>',
dataTitle: 'Limit',
dataPlacement: 'right',
dataContainer: 'body'
},
credential: {
label: 'Machine Credential',
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
sourceModel: 'credential',
sourceField: 'name',
class: 'squeeze',
awPopOver: '<p>Select the credential you want to use when ' +
'accessing the remote hosts to run the command. ' +
'Choose the credential containing ' +
'the username and SSH key or password that Ansbile ' +
'will need to log into the remote hosts.</p>',
dataTitle: 'Credential',
dataPlacement: 'right',
dataContainer: 'body',
awRequiredWhen: {
reqExpression: 'credRequired',
init: 'false'
}
},
become_enabled: {
label: 'Enable Privilege Escalation',
type: 'checkbox',
column: 2,
awPopOver: "<p>If enabled, run this playbook as an administrator. This is the equivalent of passing the<code> --become</code> option to the <code> ansible</code> command. </p>",
dataPlacement: 'right',
dataTitle: 'Become Privilege Escalation',
dataContainer: "body"
},
verbosity: {
label: 'Verbosity',
excludeModal: true,
type: 'select',
ngOptions: 'verbosity.label for verbosity in ' +
'adhoc_verbosity_options ' +
'track by verbosity.value',
required: true,
awPopOver:'<p>These are the verbosity levels for standard ' +
'out of the command run that are supported.',
dataTitle: 'Verbosity',
dataPlacement: 'right',
dataContainer: 'body',
"default": 1
},
forks: {
label: 'Forks',
id: 'forks-number',
type: 'number',
integer: true,
min: 0,
spinner: true,
"default": 0,
required: true,
'class': "input-small",
column: 1,
awPopOver: '<p>The number of parallel or simultaneous processes to use while executing the command. 0 signifies ' +
'the default value from the <a id="ansible_forks_docs" href=\"http://docs.ansible.com/intro_configuration.html#the-ansible-configuration-file\" ' +
' target=\"_blank\">ansible configuration file</a>.</p>',
dataTitle: 'Forks',
dataPlacement: 'right',
dataContainer: "body"
},
extra_vars: {
label: i18n._('Extra Variables'),
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
"default": "---",
column: 2,
awPopOver: "<p>" + i18n.sprintf(i18n._("Pass extra command line variables. This is the %s or %s command line parameter " +
"for %s. Provide key/value pairs using either YAML or JSON."), '<code>-e</code>', '<code>--extra-vars</code>', '<code>ansible</code>') + "</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",
dataTitle: i18n._('Extra Variables'),
dataPlacement: 'right',
dataContainer: "body"
}
},
buttons: {
reset: {
ngClick: 'formReset()',
ngDisabled: true,
label: 'Reset',
'class': 'btn btn-sm Form-cancelButton'
},
launch: {
label: 'Save',
ngClick: 'launchJob()',
ngDisabled: true,
'class': 'btn btn-sm List-buttonSubmit launchButton'
}
},
related: {}
};
}];

View File

@ -1,4 +0,0 @@
<div class="Panel" id="adhoc">
<div ng-cloak id="htmlTemplate">
</div>
</div>

View File

@ -1,28 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory';
import { N_ } from '../../../i18n';
export default {
url: '/adhoc',
params:{
pattern: {
value: 'all',
squash: true
}
},
name: 'inventoryManage.adhoc',
views: {
'form@inventoryManage': {
templateUrl: templateUrl('inventories/manage/adhoc/adhoc'),
controller: 'adhocController'
}
},
ncyBreadcrumb: {
label: N_("RUN COMMAND")
}
};

View File

@ -1,7 +0,0 @@
import adhocController from './adhoc.controller';
import form from './adhoc.form';
export default
angular.module('adhoc', [])
.controller('adhocController', adhocController)
.factory('adhocForm', form);

View File

@ -1,25 +0,0 @@
.InventoryManageBreadCrumbs .BreadCrumb-list{
padding-right: 0px;
}
.InventoryManageBreadCrumb-ncy.BreadCrumb-list{
padding-left: 0px;
}
.InventoryManageBreadCrumbs-separator{
content: "/";
padding: 0 5px;
color: #B7B7B7;
}
.InventoryManageBreadCrumbs{
position: relative;
height: auto;
top: -36px;
.BreadCrumb-list{
margin-bottom: 0px;
}
}
.InventoryManage-breakWord{
word-break: break-all;
}
ol.BreadCrumb-list{
display: inline-block;
}

View File

@ -1,38 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$state', '$stateParams', '$scope', '$rootScope', 'inventoryData', 'breadCrumbData', function($state, $stateParams, $scope, $rootScope, inventoryData, breadCrumbData){
// process result data into the same order specified in the traversal path
$scope.groups = _.sortBy(breadCrumbData, function(item){
var index = _.indexOf($stateParams.group, item.id);
return (index === -1) ? $stateParams.group.length : index;
});
$scope.inventory = inventoryData;
$scope.currentState = $state.current.name;
// The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope.
// In this case, we don't want to incidentally bind to this scope when editing a host or a group. See:
// https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the
// problem that this solves.
$scope.ncyBreadcrumbIgnore = true;
// slices the group stack at $index to supply new group params to $state.go()
$scope.goToGroup = function(index){
var group = $stateParams.group.slice(0, index);
$state.go('inventoryManage', {group: group}, {reload: true});
};
$scope.goToInventory = function(){
$state.go('inventoryManage', {group: undefined}, {reload: true});
};
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState){
$scope.currentState = toState.name;
});
// Remove the listener when the scope is destroyed to avoid a memory leak
$scope.$on('$destroy', function() {
cleanUpStateChangeListener();
});
}];

View File

@ -1,22 +0,0 @@
<div class="BreadCrumb InventoryManageBreadCrumbs">
<ol class="BreadCrumb-list">
<li class="BreadCrumb-item"><a ui-sref="inventories">INVENTORIES</a></li>
<li class="BreadCrumb-item BreadCrumb-invItem">
<a href ng-if="currentState !== 'inventoryManage' || groups.length > 0" ng-click="goToInventory()">{{inventory.name}}</a>
<span ng-if="currentState === 'inventoryManage' && groups.length === 0">{{inventory.name}}</span>
</li>
<!-- inside inventoryManage list view (last item is not clickable) -->
<li ng-repeat="group in groups | limitTo:(groups.length-1) track by $index" class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState === 'inventoryManage'">
<a href ng-click="goToGroup($index+1)">{{group.name}}</a>
</li>
<li ng-hide="groups.length == 0" class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState === 'inventoryManage'">
<span>{{groups[groups.length-1].name}}</span>
</li>
<!-- inside inventoryManage.child like add/edit (last item is clickable)-->
<li ng-repeat="group in groups track by $index" class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState !== 'inventoryManage'">
<a href ng-click="goToGroup($index+1)">{{group.name}}</a>
</li>
<li class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState !== 'inventoryManage'"></li>
<div class="InventoryManageBreadCrumb-ncy" ng-if="!licenseMissing" ncy-breadcrumb></div>
</ol>
</div>

View File

@ -1,24 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default {
name: 'groups',
iterator: 'copy',
selectTitle: 'Copy Groups',
index: false,
well: false,
emptyListText: 'PLEASE CREATE ADDITIONAL GROUPS / HOSTS TO PERFORM THIS ACTION',
fields: {
name: {
key: true,
label: 'Target Group Name'
}
},
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/groups'
};

View File

@ -1,33 +0,0 @@
@import "./client/src/shared/branding/colors.default.less";
#Inventory-copyMovePanel {
.List-searchRow {
width: 50%;
}
.Form-header {
width: 50%;
margin-top: -20px;
}
.Form-saveButton {
&:disabled {
border-color: @default-icon-hov;
}
}
}
.copyMove-choices {
float: right;
width: 25%;
text-align: right;
}
.copyMove-buttons{
height: 30px;
margin-top: 20px;
button {
margin-left: 20px;
}
}
.copyMove-root{
margin-top: 10px;
}

View File

@ -1,21 +0,0 @@
<div class="Panel copyMove-panel">
<div class="Form-header">
<div class="Form-title ng-binding">{{item.name}}</div>
</div>
<div class="form-group copyMove-choices clearfix">
<label class="radio-inline" ng-disabled="atRootLevel || targetRootGroup">
<input type="radio" ng-model="submitMode" value="copy" class="ng-pristine ng-valid ng-touched" ng-disabled="atRootLevel || targetRootGroup"> Copy
</label>
<label class="radio-inline">
<input type="radio" ng-model="submitMode" value="move" class="ng-pristine ng-untouched ng-valid"> Move
</label>
</div>
<div id="copyMove-list" ui-view="copyMoveList"></div>
<div class="copyMove-root form-group pull-left" ng-hide="atRootLevel">
<span><input type="checkbox" ng-model="targetRootGroup" ng-change="toggleTargetRootGroup()"> Use the inventory root</span>
</div>
<div class="copyMove-buttons">
<button type="button" class="pull-right btn btn-sm Form-saveButton" ng-disabled="!selected" ng-click="formSave()">Save</button>
<button type="button" class="pull-right btn btn-sm btn-default Form-cancelButton" ng-click="formCancel()">Cancel</button>
</div>
</div>

View File

@ -1,15 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import CopyMoveGroupsController from './copy-move-groups.controller';
import CopyMoveHostsController from './copy-move-hosts.controller';
import CopyMoveGroupList from './copy-move-groups.list';
export default
angular.module('manageCopyMove', [])
.controller('CopyMoveGroupsController', CopyMoveGroupsController)
.controller('CopyMoveHostsController', CopyMoveHostsController)
.value('CopyMoveGroupList', CopyMoveGroupList);

View File

@ -1,33 +0,0 @@
export default
function GetHostsStatusMsg() {
return function(params) {
var active_failures = params.active_failures,
total_hosts = params.total_hosts,
tip, failures, html_class;
// Return values for use on host status indicator
if (active_failures > 0) {
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + ' with failed jobs.';
html_class = 'error';
failures = true;
} else {
failures = false;
if (total_hosts === 0) {
// no hosts
tip = "Contains 0 hosts.";
html_class = 'none';
} else {
// many hosts with 0 failures
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. No job failures';
html_class = 'success';
}
}
return {
tooltip: tip,
failures: failures,
'class': html_class
};
};
}

View File

@ -1,37 +0,0 @@
export default
function GetSourceTypeOptions(Rest, ProcessErrors, GetBasePath) {
return function(params) {
var scope = params.scope,
variable = params.variable;
if (scope[variable] === undefined) {
scope[variable] = [];
Rest.setUrl(GetBasePath('inventory_sources'));
Rest.options()
.success(function (data) {
var i, choices = data.actions.GET.source.choices;
for (i = 0; i < choices.length; i++) {
if (choices[i][0] !== 'file') {
scope[variable].push({
label: choices[i][1],
value: choices[i][0]
});
}
}
scope.cloudCredentialRequired = false;
scope.$emit('sourceTypeOptionsReady');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status
});
});
}
};
}
GetSourceTypeOptions.$inject =
[ 'Rest',
'ProcessErrors',
'GetBasePath'
];

View File

@ -1,77 +0,0 @@
export default
function GetSyncStatusMsg(Empty) {
return function(params) {
var status = params.status,
source = params.source,
has_inventory_sources = params.has_inventory_sources,
launch_class = '',
launch_tip = 'Start sync process',
schedule_tip = 'Schedule future inventory syncs',
stat, stat_class, status_tip;
stat = status;
stat_class = stat;
switch (status) {
case 'never updated':
stat = 'never';
stat_class = 'na';
status_tip = 'Sync not performed. Click <i class="fa fa-refresh"></i> to start it now.';
break;
case 'none':
case 'ok':
case '':
launch_class = 'btn-disabled';
stat = 'n/a';
stat_class = 'na';
status_tip = 'Cloud source not configured. Click <i class="fa fa-pencil"></i> to update.';
launch_tip = 'Cloud source not configured.';
break;
case 'canceled':
status_tip = 'Sync canceled. Click to view log.';
break;
case 'failed':
status_tip = 'Sync failed. Click to view log.';
break;
case 'successful':
status_tip = 'Sync completed. Click to view log.';
break;
case 'pending':
status_tip = 'Sync pending.';
launch_class = "btn-disabled";
launch_tip = "Sync pending";
break;
case 'updating':
case 'running':
launch_class = "btn-disabled";
launch_tip = "Sync running";
status_tip = "Sync running. Click to view log.";
break;
}
if (has_inventory_sources && Empty(source)) {
// parent has a source, therefore this group should not have a source
launch_class = "btn-disabled";
status_tip = 'Managed by an external cloud source.';
launch_tip = 'Can only be updated by running a sync on the parent group.';
}
if (has_inventory_sources === false && Empty(source)) {
launch_class = 'btn-disabled';
status_tip = 'Cloud source not configured. Click <i class="fa fa-pencil"></i> to update.';
launch_tip = 'Cloud source not configured.';
}
return {
"class": stat_class,
"tooltip": status_tip,
"status": stat,
"launch_class": launch_class,
"launch_tip": launch_tip,
"schedule_tip": schedule_tip
};
};
}
GetSyncStatusMsg.$inject =
[ 'Empty' ];

View File

@ -1,81 +0,0 @@
export default
function GroupsCancelUpdate(Empty, Rest, ProcessErrors, Alert, Wait, Find) {
return function(params) {
var scope = params.scope,
id = params.id,
group = params.group;
if (scope.removeCancelUpdate) {
scope.removeCancelUpdate();
}
scope.removeCancelUpdate = scope.$on('CancelUpdate', function (e, url) {
// Cancel the update process
Rest.setUrl(url);
Rest.post()
.success(function () {
Wait('stop');
//Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' +
// 'Click the <i class="fa fa-refresh fa-lg"></i> button to monitor the status.', 'alert-info');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. POST status: ' + status
});
});
});
if (scope.removeCheckCancel) {
scope.removeCheckCancel();
}
scope.removeCheckCancel = scope.$on('CheckCancel', function (e, last_update, current_update) {
// Check that we have access to cancelling an update
var url = (current_update) ? current_update : last_update;
url += 'cancel/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
if (data.can_cancel) {
scope.$emit('CancelUpdate', url);
//} else {
// Wait('stop');
// Alert('Cancel Inventory Sync', 'The sync process completed. Click the <i class="fa fa-refresh fa-lg"></i> button to view ' +
// 'the latest status.', 'alert-info');
}
else {
Wait('stop');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET status: ' + status
});
});
});
// Cancel the update process
if (Empty(group)) {
group = Find({ list: scope.groups, key: 'id', val: id });
scope.selected_group_id = group.id;
}
if (group && (group.status === 'running' || group.status === 'pending')) {
// We found the group, and there is a running update
Wait('start');
Rest.setUrl(group.related.inventory_source);
Rest.get()
.success(function (data) {
scope.$emit('CheckCancel', data.related.last_update, data.related.current_update);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + group.related.inventory_source + ' failed. GET status: ' + status
});
});
}
};
}
GroupsCancelUpdate.$inject =
[ 'Empty', 'Rest', 'ProcessErrors',
'Alert', 'Wait', 'Find'
];

View File

@ -1,46 +0,0 @@
export default
function ViewUpdateStatus($state, Rest, ProcessErrors, Alert, Wait, Empty, Find) {
return function(params) {
var scope = params.scope,
group_id = params.group_id,
group = Find({ list: scope.groups, key: 'id', val: group_id });
if (scope.removeSourceReady) {
scope.removeSourceReady();
}
scope.removeSourceReady = scope.$on('SourceReady', function(e, source) {
// Get the ID from the correct summary field
var update_id = (source.summary_fields.current_update) ? source.summary_fields.current_update.id : source.summary_fields.last_update.id;
$state.go('inventorySyncStdout', {id: update_id});
});
if (group) {
if (Empty(group.source)) {
// do nothing
} else if (Empty(group.status) || group.status === "never updated") {
Alert('No Status Available', '<div>An inventory sync has not been performed for the selected group. Start the process by ' +
'clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
} else {
Wait('start');
Rest.setUrl(group.related.inventory_source);
Rest.get()
.success(function (data) {
scope.$emit('SourceReady', data);
})
.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 });
});
}
}
};
}
ViewUpdateStatus.$inject =
[ '$state', 'Rest', 'ProcessErrors',
'Alert', 'Wait', 'Empty', 'Find'
];

View File

@ -1,79 +0,0 @@
<div class="modal fade GroupDelete" id="group-delete-modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content Modal-content">
<div class="Modal-header">
<div class="Modal-title ng-binding">
Delete Group
<a href="" id="awp-promote" href=""
aw-pop-over="<dl><dt>Delete</dt><dd>Deletes groups and hosts associated with the group being deleted. If a group or host is associated with other groups, it will still exist within those groups. Otherwise, the associated groups and hosts will no longer appear in the inventory.</dd>\n<dt style='margin-top: 5px;'>Promote</dt><dd>Groups and hosts associated with the group being removed will be promoted root level. Note: groups already associated with other groups cannot be promoted.</dd></dl>\n" aw-tool-tip="Click for help"
data-placement="right"
data-container="body"
data-title="Delete Group"
class="help-link">
<i class="fa fa-question-circle"></i>
</a>
</div>
<div class="Modal-exitHolder">
<button class="close Modal-exit" data-target="#group-delete-modal" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times-circle"></i></button>
</div>
</div>
<div class="Modal-body">
<div ng-show="toDelete.total_groups > 0 || toDelete.total_hosts > 0">
<div>
<p class="Prompt-bodyQuery">Deleting group <em>{{ toDelete.name }}</em>.
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0"> This group contains {{ toDelete.total_groups }} groups and {{ toDelete.total_hosts }} hosts. </span>
<span ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0"> This group contains {{ toDelete.total_hosts }} hosts. </span>
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0"> This group contains {{ toDelete.total_groups }} groups. </span>
Delete or promote the group's children?</p>
<div style="margin: 15px auto;">
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="promote"> Promote groups and hosts
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="delete"> Delete groups and hosts
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
<label>
<input type="radio" ng-model="deleteOption" value="promote"> Promote groups
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
<label>
<input type="radio" ng-model="deleteOption" value="delete"> Delete groups
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="promote"> Promote hosts
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="delete"> Delete hosts
</label>
</div>
</div>
</div>
</div>
<div ng-show="toDelete.total_groups == 0 && toDelete.total_hosts == 0">
<div class="Prompt-bodyQuery">Are you sure you want to permanently delete the group below from the inventory?</div>
<div class="Prompt-bodyTarget">{{ toDelete.name }}</div>
</div>
<div class="Modal-footer">
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn_groups_list" class="btn Modal-defaultButton Modal-footerButton">CANCEL</a>
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn_groups_list" class="btn Modal-footerButton Modal-errorButton">DELETE</a>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,16 +0,0 @@
.select2-selection.select2-selection--multiple.Form-dropDown{
height: auto !important;
}
.GroupDelete .Modal-header{
margin-bottom: 20px;
}
.GroupDelete .modal-body{
padding-top: 20px;
}
.Inventory-groupManage{
// ugly hack to avoid the surface area of changing form generator's default classes
.checkbox-inline{
display: block;
padding-bottom: 5px;
}
}

View File

@ -1,360 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name forms.function:Groups
* @description This form is for adding/editing a Group on the inventory page
*/
export default ['NotificationsList',
function(NotificationsList) {
return function() {
var GroupFormObject = {
addTitle: 'CREATE GROUP',
editTitle: '{{ name }}',
showTitle: true,
name: 'group',
basePath: 'groups',
// the parent node this generated state definition tree expects to attach to
stateTree: 'inventoryManage',
// form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab
// this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit
activeEditState: 'inventoryManage.editGroup',
detailsClick: "$state.go('inventoryManage.editGroup')",
well: false,
fields: {
name: {
label: 'Name',
type: 'text',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
required: true,
tab: 'properties'
},
description: {
label: 'Description',
type: 'text',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
tab: 'properties'
},
variables: {
label: 'Variables',
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
'default': '---',
dataTitle: 'Group Variables',
dataPlacement: 'right',
parseTypeName: 'parseType',
awPopOver: "<p>Variables defined here apply to all child groups and hosts.</p>" +
"<p>Enter 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>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body',
tab: 'properties'
},
source: {
label: 'Source',
type: 'select',
ngOptions: 'source.label for source in source_type_options track by source.value',
ngChange: 'sourceChange(source)',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
ngModel: 'source'
},
credential: {
// initializes a default value for this search param
// search params with default values set will not generate user-interactable search tags
search: {
kind: null
},
label: 'Cloud Credential',
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
ngShow: "source && source.value !== '' && source.value !== 'custom'",
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookupCredential()',
awRequiredWhen: {
reqExpression: "cloudCredentialRequired",
init: "false"
},
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
watchBasePath: "credentialBasePath"
},
source_regions: {
label: 'Regions',
type: 'select',
ngOptions: 'source.label for source in source_region_choices track by source.value',
multiSelect: true,
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure' || source.value == 'azure_rm')",
dataTitle: 'Source Regions',
dataPlacement: 'right',
awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
"or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." +
"</p>",
dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
instance_filters: {
label: 'Instance Filters',
type: 'text',
ngShow: "source && source.value == 'ec2'",
dataTitle: 'Instance Filters',
dataPlacement: 'right',
awPopOver: "<p>Provide a comma-separated list of filter expressions. " +
"Hosts are imported to Tower when <em>ANY</em> of the filters match.</p>" +
"Limit to hosts having a tag:<br />\n" +
"<blockquote>tag-key=TowerManaged</blockquote>\n" +
"Limit to hosts using either key pair:<br />\n" +
"<blockquote>key-name=staging, key-name=production</blockquote>\n" +
"Limit to hosts where the Name tag begins with <em>test</em>:<br />\n" +
"<blockquote>tag:Name=test*</blockquote>\n" +
"<p>View the <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\" target=\"_blank\">Describe Instances documentation</a> " +
"for a complete list of supported filters.</p>",
dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
group_by: {
label: 'Only Group By',
type: 'select',
ngShow: "source && source.value == 'ec2'",
ngOptions: 'source.label for source in group_by_choices track by source.value',
multiSelect: true,
dataTitle: 'Only Group By',
dataPlacement: 'right',
awPopOver: "<p>Select which groups to create automatically. " +
"Tower will create group names similar to the following examples based on the options selected:</p><ul>" +
"<li>Availability Zone: <strong>zones &raquo; us-east-1b</strong></li>" +
"<li>Image ID: <strong>images &raquo; ami-b007ab1e</strong></li>" +
"<li>Instance ID: <strong>instances &raquo; i-ca11ab1e</strong></li>" +
"<li>Instance Type: <strong>types &raquo; type_m1_medium</strong></li>" +
"<li>Key Name: <strong>keys &raquo; key_testing</strong></li>" +
"<li>Region: <strong>regions &raquo; us-east-1</strong></li>" +
"<li>Security Group: <strong>security_groups &raquo; security_group_default</strong></li>" +
"<li>Tags: <strong>tags &raquo; tag_Name &raquo; tag_Name_host1</strong></li>" +
"<li>VPC ID: <strong>vpcs &raquo; vpc-5ca1ab1e</strong></li>" +
"<li>Tag None: <strong>tags &raquo; tag_none</strong></li>" +
"</ul><p>If blank, all groups above are created except <em>Instance ID</em>.</p>",
dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
inventory_script: {
label : "Custom Inventory Script",
type: 'lookup',
basePath: 'inventory_scripts',
list: 'InventoryScriptsList',
ngShow: "source && source.value === 'custom'",
sourceModel: 'inventory_script',
sourceField: 'name',
awRequiredWhen: {
reqExpression: "source && source.value === 'custom'",
init: "false"
},
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
},
custom_variables: {
id: 'custom_variables',
label: 'Environment Variables', //"{{vars_label}}" ,
ngShow: "source && source.value=='custom' ",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: "Environment Variables",
dataPlacement: 'right',
awPopOver: "<p>Provide environment variables to pass to the custom inventory script.</p>" +
"<p>Enter 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>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
ec2_variables: {
id: 'ec2_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && source.value == 'ec2'",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: "Source Variables",
dataPlacement: 'right',
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/ec2.ini\" target=\"_blank\">" +
"view ec2.ini in the Ansible github repo.</a></p>" +
"<p>Enter 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>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
vmware_variables: {
id: 'vmware_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && source.value == 'vmware'",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: "Source Variables",
dataPlacement: 'right',
awPopOver: "<p>Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables " +
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/vmware_inventory.ini\" target=\"_blank\">" +
"view vmware_inventory.ini in the Ansible github repo.</a></p>" +
"<p>Enter 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>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
openstack_variables: {
id: 'openstack_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && source.value == 'openstack'",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: "Source Variables",
dataPlacement: 'right',
awPopOver: "<p>Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration " +
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/openstack.yml\" target=\"_blank\">" +
"view openstack.yml in the Ansible github repo.</a></p>" +
"<p>Enter 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>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
checkbox_group: {
label: 'Update Options',
type: 'checkbox_group',
ngShow: "source && (source.value !== '' && source.value !== null)",
class: 'Form-checkbox--stacked',
fields: [{
name: 'overwrite',
label: 'Overwrite',
type: 'checkbox',
ngShow: "source.value !== '' && source.value !== null",
awPopOver: '<p>If checked, all child groups and hosts not found on the external source will be deleted from ' +
'the local inventory.</p><p>When not checked, local child hosts and groups not found on the external source will ' +
'remain untouched by the inventory update process.</p>',
dataTitle: 'Overwrite',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, {
name: 'overwrite_vars',
label: 'Overwrite Variables',
type: 'checkbox',
ngShow: "source.value !== '' && source.value !== null",
awPopOver: '<p>If checked, all variables for child groups and hosts will be removed and replaced by those ' +
'found on the external source.</p><p>When not checked, a merge will be performed, combining local variables with ' +
'those found on the external source.</p>',
dataTitle: 'Overwrite Variables',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, {
name: 'update_on_launch',
label: 'Update on Launch',
type: 'checkbox',
ngShow: "source.value !== '' && source.value !== null",
awPopOver: '<p>Each time a job runs using this inventory, refresh the inventory from the selected source before ' +
'executing job tasks.</p>',
dataTitle: 'Update on Launch',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}]
},
update_cache_timeout: {
label: "Cache Timeout <span class=\"small-text\"> (seconds)</span>",
id: 'source-cache-timeout',
type: 'number',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
integer: true,
min: 0,
ngShow: "source && source.value !== '' && update_on_launch",
spinner: true,
"default": 0,
awPopOver: '<p>Time in seconds to consider an inventory sync to be current. During job runs and callbacks the task system will ' +
'evaluate the timestamp of the latest sync. If it is older than Cache Timeout, it is not considered current, ' +
'and a new inventory sync will be performed.</p>',
dataTitle: 'Cache Timeout',
dataPlacement: 'right',
dataContainer: "body"
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
"notifications": {
include: "NotificationsList"
}
}
};
var itm;
for (itm in GroupFormObject.related) {
if (GroupFormObject.related[itm].include === "NotificationsList") {
GroupFormObject.related[itm] = angular.copy(NotificationsList);
GroupFormObject.related[itm].generateList = true;
GroupFormObject.related[itm].disabled = "source === undefined || source.value === ''";
GroupFormObject.related[itm].ngClick = "$state.go('inventoryManage.editGroup.notifications')";
}
}
return GroupFormObject;
};
}];

View File

@ -1,113 +0,0 @@
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){
return {
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&';
}, '');
},
// cute abstractions via fn.bind()
url: function(){
return '';
},
error: function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + this.url + '. GET returned: ' + status });
},
success: function(data){
return data;
},
// HTTP methods
get: function(params){
Wait('start');
this.url = GetBasePath('groups') + '?' + this.stringifyParams(params);
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
post: function(group){
Wait('start');
this.url = GetBasePath('groups');
Rest.setUrl(this.url);
return Rest.post(group)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
put: function(group){
Wait('start');
this.url = GetBasePath('groups') + group.id;
Rest.setUrl(this.url);
return Rest.put(group)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
delete: function(id){
Wait('start');
this.url = GetBasePath('groups') + id;
Rest.setUrl(this.url);
return Rest.destroy()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
getCredential: function(id){
Wait('start');
this.url = GetBasePath('credentials') + id;
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
getInventorySource: function(params){
Wait('start');
this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params);
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
putInventorySource: function(params, url){
Wait('start');
this.url = url;
Rest.setUrl(this.url);
return Rest.put(params)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
// these relationship setters could be consolidated, but verbosity makes the operation feel more clear @ controller level
associateGroup: function(group, target){
Wait('start');
this.url = GetBasePath('groups') + target + '/children/';
Rest.setUrl(this.url);
return Rest.post(group)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
disassociateGroup: function(group, parent){
Wait('start');
this.url = GetBasePath('groups') + parent + '/children/';
Rest.setUrl(this.url);
return Rest.post({id: group, disassociate: 1})
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
promote: function(group, inventory){
Wait('start');
this.url = GetBasePath('inventory') + inventory + '/groups/';
Rest.setUrl(this.url);
return Rest.post({id: group, disassociate: 1})
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
}
};
}];

View File

@ -1,167 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default {
name: 'groups',
iterator: 'group',
editTitle: '{{ inventory.name }}',
listTitle: 'GROUPS',
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
showTitle: false,
well: true,
index: false,
hover: true,
'class': 'table-no-border',
multiSelect: true,
trackBy: 'group.id',
fields: {
sync_status: {
label: '',
nosort: true,
mode: 'all',
iconOnly: true,
ngClick: 'viewUpdateStatus(group.id)',
awToolTip: "{{ group.status_tooltip }}",
dataTipWatch: "group.status_tooltip",
icon: "{{ 'fa icon-cloud-' + group.status_class }}",
ngClass: "group.status_class",
dataPlacement: "top",
columnClass: 'status-column List-staticColumn--smallStatus'
},
failed_hosts: {
label: '',
nosort: true,
mode: 'all',
iconOnly: true,
awToolTip: "{{ group.hosts_status_tip }}",
dataPlacement: "top",
ngClick: "showFailedHosts(group)",
icon: "{{ 'fa icon-job-' + group.hosts_status_class }}",
columnClass: 'status-column List-staticColumn--smallStatus'
},
name: {
label: 'Groups',
key: true,
ngClick: "groupSelect(group.id)",
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6',
class: 'InventoryManage-breakWord',
},
total_groups: {
nosort: true,
label: '',
type: 'badgeCount',
ngHide: 'group.total_groups == 0',
noLink: true,
awToolTip: "{{group.name | sanitize}} contains {{group.total_groups}} {{group.total_groups === 1 ? 'child' : 'children'}}"
}
},
actions: {
refresh: {
mode: 'all',
awToolTip: "Refresh the page",
ngClick: "refreshGroups()",
ngShow: "socketStatus == 'error'",
actionClass: 'btn List-buttonDefault',
buttonContent: 'REFRESH'
},
launch: {
mode: 'all',
// $scope.$parent is governed by InventoryManageController,
ngDisabled: '!$parent.groupsSelected && !$parent.hostsSelected',
ngClick: '$parent.setAdhocPattern()',
awToolTip: "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups.",
dataTipWatch: "adhocCommandTooltip",
actionClass: 'btn List-buttonDefault',
buttonContent: 'RUN COMMANDS',
showTipWhenDisabled: true,
tooltipInnerClass: "Tooltip-wide",
ngShow: 'canAdhoc'
// TODO: set up a tip watcher and change text based on when
// things are selected/not selected. This is started and
// commented out in the inventory controller within the watchers.
// awToolTip: "{{ adhocButtonTipContents }}",
// dataTipWatch: "adhocButtonTipContents"
},
create: {
mode: 'all',
ngClick: "createGroup()",
awToolTip: "Create a new group",
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD GROUP',
ngShow: 'canAdd',
dataPlacement: "top",
}
},
fieldActions: {
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right',
group_update: {
//label: 'Sync',
mode: 'all',
ngClick: 'updateGroup(group)',
awToolTip: "{{ group.launch_tooltip }}",
dataTipWatch: "group.launch_tooltip",
ngShow: "(group.status !== 'running' && group.status " +
"!== 'pending' && group.status !== 'updating') && group.summary_fields.user_capabilities.start",
ngClass: "group.launch_class",
dataPlacement: "top",
},
cancel: {
//label: 'Cancel',
mode: 'all',
ngClick: "cancelUpdate(group.id)",
awToolTip: "Cancel sync process",
'class': 'red-txt',
ngShow: "(group.status == 'running' || group.status == 'pending' " +
"|| group.status == 'updating') && group.summary_fields.user_capabilities.start",
dataPlacement: "top",
iconClass: "fa fa-minus-circle"
},
copy: {
mode: 'all',
ngClick: "copyMoveGroup(group.id)",
awToolTip: 'Copy or move group',
ngShow: "group.id > 0 && group.summary_fields.user_capabilities.copy",
dataPlacement: "top"
},
schedule: {
mode: 'all',
ngClick: "scheduleGroup(group.id)",
awToolTip: "{{ group.group_schedule_tooltip }}",
ngClass: "group.scm_type_class",
dataPlacement: 'top',
ngShow: "!(group.summary_fields.inventory_source.source === '')"
},
edit: {
//label: 'Edit',
mode: 'all',
ngClick: "editGroup(group.id)",
awToolTip: 'Edit group',
dataPlacement: "top",
ngShow: "group.summary_fields.user_capabilities.edit"
},
view: {
//label: 'Edit',
mode: 'all',
ngClick: "editGroup(group.id)",
awToolTip: 'View group',
dataPlacement: "top",
ngShow: "!group.summary_fields.user_capabilities.edit"
},
"delete": {
//label: 'Delete',
mode: 'all',
ngClick: "deleteGroup(group)",
awToolTip: 'Delete group',
dataPlacement: "top",
ngShow: "group.summary_fields.user_capabilities.delete"
}
}
};

View File

@ -1,27 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import GroupAddController from './groups-add.controller';
import GroupEditController from './groups-edit.controller';
import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory';
import GetSourceTypeOptions from './factories/get-source-type-options.factory';
import GetSyncStatusMsg from './factories/get-sync-status-msg.factory';
import GroupsCancelUpdate from './factories/groups-cancel-update.factory';
import ViewUpdateStatus from './factories/view-update-status.factory';
import InventoryGroups from './inventory-groups.list';
import GroupForm from './groups.form';
export default
angular.module('manageGroups', [])
.factory('GetHostsStatusMsg', GetHostsStatusMsg)
.factory('GetSourceTypeOptions', GetSourceTypeOptions)
.factory('GetSyncStatusMsg', GetSyncStatusMsg)
.factory('GroupsCancelUpdate', GroupsCancelUpdate)
.factory('ViewUpdateStatus', ViewUpdateStatus)
.factory('GroupForm', GroupForm)
.value('InventoryGroups', InventoryGroups)
.controller('GroupAddController', GroupAddController)
.controller('GroupEditController', GroupEditController);

View File

@ -1,84 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'HostManageService', 'host', 'ToJSON',
function($state, $stateParams, $scope, HostForm, ParseTypeChange, HostManageService, host, ToJSON){
init();
function init(){
$scope.$watch('host.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.parseType = 'yaml';
$scope.host = host;
$scope.variables = getVars(host.variables);
$scope.name = host.name;
$scope.description = host.description;
ParseTypeChange({
scope: $scope,
field_id: 'host_variables',
});
}
// Adding this function b/c sometimes extra vars are returned to the
// UI as a string (ex: "foo: bar"), and other times as a
// json-object-string (ex: "{"foo": "bar"}"). CodeMirror wouldn't know
// how to prettify the latter. The latter occurs when host vars were
// system generated and not user-input (such as adding a cloud host);
function getVars(str){
// Quick function to test if the host vars are a json-object-string,
// by testing if they can be converted to a JSON object w/o error.
function IsJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
if(str === ''){
return '---';
}
else if(IsJsonString(str)){
str = JSON.parse(str);
return jsyaml.safeDump(str);
}
else if(!IsJsonString(str)){
return str;
}
}
$scope.formCancel = function(){
$state.go('^');
};
$scope.toggleHostEnabled = function(){
if ($scope.host.has_inventory_sources){
return;
}
$scope.host.enabled = !$scope.host.enabled;
};
$scope.formSave = function(){
var json_data = ToJSON($scope.parseType, $scope.variables, true),
host = {
id: $scope.host.id,
variables: json_data,
name: $scope.name,
description: $scope.description,
enabled: $scope.host.enabled
};
HostManageService.put(host).then(function(){
$state.go($state.current, null, {reload: true});
});
};
}];

View File

@ -1,120 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryHosts', 'HostManageService',
'hostsUrl', 'SetStatus', 'Prompt', 'Wait', 'inventoryData', '$filter', 'hostsDataset', 'GetBasePath', 'rbacUiControlService', 'QuerySet',
function($scope, $rootScope, $state, $stateParams, InventoryHosts, HostManageService,
hostsUrl, SetStatus, Prompt, Wait, inventoryData, $filter, hostsDataset, GetBasePath, rbacUiControlService, qs){
var list = InventoryHosts;
init();
function init(){
$scope.inventory_id = $stateParams.inventory_id;
$scope.canAdd = false;
rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/hosts")
.then(function(params) {
$scope.canAdd = params.canAdd;
});
// Search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = hostsDataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.$watch(`${list.iterator}_dataset`, () => {
$scope.hosts
.forEach((host) => SetStatus({scope: $scope,
host: host}));
});
$scope.$on(`ws-jobs`, function(e, data){
if(data.status === 'failed' || data.status === 'successful'){
let path = hostsUrl;
qs.search(path, $state.params[`${list.iterator}_search`])
.then(function(searchResponse) {
$scope[`${list.iterator}_dataset`] = searchResponse.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
});
}
});
// The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope.
// In this case, we don't want to incidentally bind to this scope when editing a host or a group. See:
// https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the
// problem that this solves.
$scope.ncyBreadcrumbIgnore = true;
if($state.current.name === "inventoryManage.editHost") {
$scope.rowBeingEdited = $state.params.host_id;
$scope.listBeingEdited = "hosts";
}
}
$scope.createHost = function(){
$state.go('inventoryManage.addHost');
};
$scope.editHost = function(id){
$state.go('inventoryManage.editHost', {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("inventoryManage", 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.copyMoveHost = function(id){
$state.go('inventoryManage.copyMoveHost', {host_id: id});
};
$scope.systemTracking = function(){
var hostIds = _.map($scope.$parent.hostsSelectedItems, (host) => host.id);
$state.go('systemTracking', {
inventory: inventoryData,
inventoryId: $stateParams.inventory_id,
hosts: $scope.$parent.hostsSelectedItems,
hostIds: hostIds
});
};
// $scope.$parent governed by InventoryManageController, for unified multiSelect options
$scope.$on('multiSelectList.selectionChanged', (event, selection) => {
$scope.$parent.hostsSelected = selection.length > 0 ? true : false;
$scope.$parent.hostsSelectedItems = selection.selectedItems;
$scope.$parent.systemTrackingDisabled = selection.length > 0 && selection.length < 3 ? false : true;
$scope.$parent.systemTrackingTooltip = selection.length > 0 && selection.length < 3 ? "Compare host facts over time" : "Select one or two hosts by clicking the checkbox beside the host. System tracking offers the ability to compare the results of two scan runs from different dates on one host or the same date on two hosts.";
});
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if (toState.name === "inventoryManage.editHost") {
$scope.rowBeingEdited = toParams.host_id;
$scope.listBeingEdited = "hosts";
}
else {
delete $scope.rowBeingEdited;
delete $scope.listBeingEdited;
}
});
// Remove the listener when the scope is destroyed to avoid a memory leak
$scope.$on('$destroy', function() {
cleanUpStateChangeListener();
});
}];

View File

@ -1,119 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default {
name: 'hosts',
iterator: 'host',
editTitle: '{{ selected_group }}',
listTitle: 'HOSTS',
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',
multiSelect: true,
trackBy: 'host.id',
fields: {
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: 'Hosts',
ngClick: "editHost(host.id)",
ngClass: "{ 'host-disabled-label': !host.enabled }",
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: {
system_tracking: {
buttonContent: 'System Tracking',
ngClick: 'systemTracking()',
awToolTip: "Select one or two hosts by clicking the checkbox beside the host. System tracking offers the ability to compare the results of two scan runs from different dates on one host or the same date on two hosts.",
dataTipWatch: "systemTrackingTooltip",
dataPlacement: 'top',
awFeature: 'system_tracking',
actionClass: 'btn List-buttonDefault system-tracking',
ngDisabled: 'systemTrackingDisabled || !hostsSelected',
showTipWhenDisabled: true,
tooltipInnerClass: "Tooltip-wide",
ngShow: true
},
refresh: {
mode: 'all',
awToolTip: "Refresh the page",
ngClick: "refreshGroups()",
ngShow: "socketStatus == 'error'",
actionClass: 'btn List-buttonDefault',
buttonContent: 'REFRESH'
},
create: {
mode: 'all',
ngClick: "createHost()",
awToolTip: "Create a new host",
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD HOST',
ngShow: 'canAdd',
dataPlacement: "top",
}
}
};

View File

@ -1,21 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import HostsAddController from './hosts-add.controller';
import HostsEditController from './hosts-edit.controller';
import SetStatus from './factories/set-status.factory';
import SetEnabledMsg from './factories/set-enabled-msg.factory';
import InventoryHosts from './inventory-hosts.list';
import HostForm from './hosts.form';
export default
angular.module('manageHosts', [])
.factory('SetStatus', SetStatus)
.factory('SetEnabledMsg', SetEnabledMsg)
.factory('HostForm', HostForm)
.value('InventoryHosts', InventoryHosts)
.controller('HostsAddController', HostsAddController)
.controller('HostEditController', HostsEditController);

View File

@ -1,3 +0,0 @@
.InventoryManage-container{
margin-top: -36px;
}

View File

@ -1,28 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$state', 'inventoryData', function($scope, $state, inventoryData){
$scope.groupsSelected = false;
$scope.hostsSelected = false;
$scope.hostsSelectedItems = [];
$scope.groupsSelectedItems = [];
$scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc;
$scope.setAdhocPattern = function(){
var pattern = _($scope.groupsSelectedItems)
.concat($scope.hostsSelectedItems)
.map(function(item){
return item.name;
}).value().join(':');
$state.go('inventoryManage.adhoc', {pattern: pattern});
};
$scope.$watchGroup(['groupsSelected', 'hostsSelected'], function(newVals) {
$scope.adhocCommandTooltip = (newVals[0] || newVals[1]) ? "Run a command on the selected inventory" : "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups.";
});
}];

View File

@ -1,8 +0,0 @@
<div class="tab-pane InventoryManage-container" id="inventory_edit">
<div ui-view="form"></div>
<div class="row">
<div ui-view="groupsList" class="col-lg-6"></div>
<div ui-view="hostsList" class="col-lg-6"></div>
</div>
<div ng-include="'/static/partials/logviewer.html'"></div>
</div>

View File

@ -1,24 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import InventoryManageService from './inventory-manage.service';
import HostManageService from './hosts/hosts.service';
import GroupManageService from './groups/groups.service';
import hosts from './hosts/main';
import groups from './groups/main';
import adhoc from './adhoc/main';
import copyMove from './copy-move/main';
export default
angular.module('inventoryManage', [
hosts.name,
groups.name,
copyMove.name,
adhoc.name
])
.service('InventoryManageService', InventoryManageService)
.service('HostManageService', HostManageService)
.service('GroupManageService', GroupManageService);

View File

@ -0,0 +1,73 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import RelatedHostEditController from './host-edit.controller';
export default ['$stateExtender', 'templateUrl', '$injector',
'RelatedHostsFormDefinition', 'NestedHostsFormDefinition',
'nestedGroupListState',
function($stateExtender, templateUrl, $injector,
RelatedHostsFormDefinition, NestedHostsFormDefinition,
nestedGroupListState){
var val = function(field, formStateDefinition, params) {
let state, states = [],
list = field.include ? $injector.get(field.include) : field,
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
stateConfig = {
name: `${formStateDefinition.name}.${list.iterator}s.edit`,
url: `/edit/:host_id`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
label: `${breadcrumbLabel}`
},
views: {
'hostForm@inventories': {
templateProvider: function(GenerateForm, RelatedHostsFormDefinition, NestedHostsFormDefinition, $stateParams) {
let form = RelatedHostsFormDefinition;
if($stateParams.group_id){
form = NestedHostsFormDefinition;
}
return GenerateForm.buildHTML(form, {
mode: 'edit',
related: false
});
},
controller: RelatedHostEditController
}
},
resolve: {
'FormDefinition': [params.form, function(definition) {
return definition;
}],
host: ['$stateParams', 'HostManageService', function($stateParams, HostManageService) {
return HostManageService.get({ id: $stateParams.host_id }).then(function(res) {
return res.data.results[0];
});
}]
}
};
var relatedGroupListState;
state = $stateExtender.buildDefinition(stateConfig);
if(stateConfig.name === "inventories.edit.groups.edit.nested_hosts.edit"){
relatedGroupListState = nestedGroupListState(NestedHostsFormDefinition.related.nested_groups, state, params);
relatedGroupListState = $stateExtender.buildDefinition(relatedGroupListState);
states.push(state, relatedGroupListState);
return states;
}
if(stateConfig.name === "inventories.edit.hosts.edit"){
relatedGroupListState = nestedGroupListState(RelatedHostsFormDefinition.related.nested_groups, state, params);
relatedGroupListState = $stateExtender.buildDefinition(relatedGroupListState);
states.push(state, relatedGroupListState);
return states;
}
else {
return state;
}
};
return val;
}
];

View File

@ -1,104 +0,0 @@
/*************************************************
* 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

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

View File

@ -1,136 +0,0 @@
/*************************************************
* 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

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

View File

@ -1,20 +0,0 @@
/*************************************************
* 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

@ -1,135 +0,0 @@
/*************************************************
* 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

@ -1,98 +0,0 @@
/*************************************************
* 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

@ -1,308 +0,0 @@
/*************************************************
* 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

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

View File

@ -1,236 +0,0 @@
/*************************************************
* 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,14 +27,6 @@
<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"
@ -112,15 +104,6 @@
<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

@ -128,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;
},
/**

View File

@ -35,8 +35,7 @@
<body data-user-agent="{{userAgent}}">
<main-menu></main-menu>
<bread-crumb ng-show="!includesCurrentState('inventoryManage')"></bread-crumb>
<div ui-view="groupBreadcrumbs" ng-show="includesCurrentState('inventoryManage')"></div>
<bread-crumb></bread-crumb>
<toast></toast>
<div class="container-fluid" id="content-container">
<div class="row">