mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 06:51:10 +03:00
Related Groups tab for inventories
I've added a way to hookin a custom "related" state without needing to extend the state-generator directly. There is more work that needs to happen here but this gets the scaffolding in place for the related-groups tab.
This commit is contained in:
parent
3727c3a9a5
commit
f5141bcab9
@ -0,0 +1,33 @@
|
||||
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
|
||||
};
|
||||
};
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
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'
|
||||
];
|
@ -0,0 +1,77 @@
|
||||
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' ];
|
@ -0,0 +1,81 @@
|
||||
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'
|
||||
];
|
@ -0,0 +1,46 @@
|
||||
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'
|
||||
];
|
113
awx/ui/client/src/inventories/groups/groups.service.js
Normal file
113
awx/ui/client/src/inventories/groups/groups.service.js
Normal file
@ -0,0 +1,113 @@
|
||||
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'));
|
||||
}
|
||||
};
|
||||
}];
|
@ -0,0 +1,99 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['InventoryGroupsList', '$stateExtender', 'templateUrl', '$injector',
|
||||
function(InventoryGroupsList, $stateExtender, templateUrl, $injector){
|
||||
var val = function(field, formStateDefinition, params) {
|
||||
let state,
|
||||
list = field.include ? $injector.get(field.include) : field,
|
||||
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
|
||||
stateConfig = {
|
||||
searchPrefix: `${list.iterator}`,
|
||||
name: `${formStateDefinition.name}.${list.iterator}s`,
|
||||
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': {
|
||||
templateProvider: function(InventoryGroupsList, generateList, $templateRequest, $stateParams, GetBasePath) {
|
||||
let list = _.cloneDeep(InventoryGroupsList);
|
||||
if($stateParams && $stateParams.group) {
|
||||
list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/children';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/root_groups';
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
// controller: GroupsListController
|
||||
}
|
||||
},
|
||||
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 });
|
||||
}
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
],
|
||||
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return InventoryManageService.getInventory($stateParams.inventory_id).then(res => res.data);
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
if(params.controllers && params.controllers.related && params.controllers.related[field.name]) {
|
||||
stateConfig.views.related.controller = params.controllers.related[field.name];
|
||||
}
|
||||
else if(field.name === 'permissions') {
|
||||
stateConfig.views.related.controller = 'PermissionsList';
|
||||
}
|
||||
else {
|
||||
// Generic controller
|
||||
stateConfig.views.related.controller = ['$scope', 'ListDefinition', 'Dataset',
|
||||
function($scope, list, Dataset) {
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
];
|
@ -0,0 +1,236 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
export default
|
||||
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroupsList', 'InventoryUpdate',
|
||||
'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
|
||||
'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
|
||||
function($scope, $rootScope, $state, $stateParams, InventoryGroupsList, InventoryUpdate,
|
||||
GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath,
|
||||
GetSyncStatusMsg, GetHostsStatusMsg, Dataset, Find, qs, inventoryData){
|
||||
|
||||
let list = InventoryGroupsList;
|
||||
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
$scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc;
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/groups")
|
||||
.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;
|
||||
|
||||
// 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.editGroup") {
|
||||
$scope.rowBeingEdited = $state.params.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
|
||||
}
|
||||
|
||||
function buildStatusIndicators(group){
|
||||
if (group === undefined || group === null) {
|
||||
group = {};
|
||||
}
|
||||
|
||||
let group_status, hosts_status;
|
||||
|
||||
group_status = GetSyncStatusMsg({
|
||||
status: group.summary_fields.inventory_source.status,
|
||||
has_inventory_sources: group.has_inventory_sources,
|
||||
source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
|
||||
});
|
||||
hosts_status = GetHostsStatusMsg({
|
||||
active_failures: group.hosts_with_active_failures,
|
||||
total_hosts: group.total_hosts,
|
||||
inventory_id: $scope.inventory_id,
|
||||
group_id: group.id
|
||||
});
|
||||
_.assign(group,
|
||||
{status_class: group_status.class},
|
||||
{status_tooltip: group_status.tooltip},
|
||||
{launch_tooltip: group_status.launch_tip},
|
||||
{launch_class: group_status.launch_class},
|
||||
{group_schedule_tooltip: group_status.schedule_tip},
|
||||
{hosts_status_tip: hosts_status.tooltip},
|
||||
{hosts_status_class: hosts_status.class},
|
||||
{source: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.source : null},
|
||||
{status: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.status : null});
|
||||
}
|
||||
|
||||
$scope.groupSelect = function(id){
|
||||
var group = $stateParams.group === undefined ? [id] : _($stateParams.group).concat(id).value();
|
||||
$state.go('inventoryManage', {
|
||||
inventory_id: $stateParams.inventory_id,
|
||||
group: group,
|
||||
group_search: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
}
|
||||
}, {reload: true});
|
||||
};
|
||||
$scope.createGroup = function(){
|
||||
$state.go('inventoryManage.addGroup');
|
||||
};
|
||||
$scope.editGroup = function(id){
|
||||
$state.go('inventoryManage.editGroup', {group_id: id});
|
||||
};
|
||||
$scope.deleteGroup = function(group){
|
||||
$scope.toDelete = {};
|
||||
angular.extend($scope.toDelete, group);
|
||||
if($scope.toDelete.total_groups === 0 && $scope.toDelete.total_hosts === 0) {
|
||||
// This group doesn't have any child groups or hosts - the user is just trying to delete
|
||||
// the group
|
||||
$scope.deleteOption = "delete";
|
||||
}
|
||||
$('#group-delete-modal').modal('show');
|
||||
};
|
||||
$scope.confirmDelete = function(){
|
||||
|
||||
// Bind an even listener for the modal closing. Trying to $state.go() before the modal closes
|
||||
// will mean that these two things are running async and the modal may not finish closing before
|
||||
// the state finishes transitioning.
|
||||
$('#group-delete-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
|
||||
// Remove the event handler so that we don't end up with multiple bindings
|
||||
$('#group-delete-modal').off('hidden.bs.modal');
|
||||
// Reload the inventory manage page and show that the group has been removed
|
||||
$state.go('inventoryManage', null, {reload: true});
|
||||
});
|
||||
|
||||
switch($scope.deleteOption){
|
||||
case 'promote':
|
||||
GroupManageService.promote($scope.toDelete.id, $stateParams.inventory_id)
|
||||
.then(() => {
|
||||
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
|
||||
$state.go("inventoryManage", null, {reload: true});
|
||||
} else {
|
||||
$state.go($state.current, null, {reload: true});
|
||||
}
|
||||
$('#group-delete-modal').modal('hide');
|
||||
$('body').removeClass('modal-open');
|
||||
$('.modal-backdrop').remove();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
GroupManageService.delete($scope.toDelete.id).then(() => {
|
||||
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
|
||||
$state.go("inventoryManage", null, {reload: true});
|
||||
} else {
|
||||
$state.go($state.current, null, {reload: true});
|
||||
}
|
||||
$('#group-delete-modal').modal('hide');
|
||||
$('body').removeClass('modal-open');
|
||||
$('.modal-backdrop').remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
$scope.updateGroup = function(group) {
|
||||
GroupManageService.getInventorySource({group: group.id}).then(res =>InventoryUpdate({
|
||||
scope: $scope,
|
||||
group_id: group.id,
|
||||
url: res.data.results[0].related.update,
|
||||
group_name: group.name,
|
||||
group_source: res.data.results[0].source
|
||||
}));
|
||||
};
|
||||
|
||||
$scope.$on(`ws-jobs`, function(e, data){
|
||||
var group = Find({ list: $scope.groups, key: 'id', val: data.group_id });
|
||||
|
||||
if (group === undefined || group === null) {
|
||||
group = {};
|
||||
}
|
||||
|
||||
if(data.status === 'failed' || data.status === 'successful'){
|
||||
let path;
|
||||
if($stateParams && $stateParams.group && $stateParams.group.length > 0) {
|
||||
path = GetBasePath('groups') + _.last($stateParams.group) + '/children';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
path = GetBasePath('inventory') + $stateParams.inventory_id + '/root_groups';
|
||||
}
|
||||
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;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
});
|
||||
} else {
|
||||
var status = GetSyncStatusMsg({
|
||||
status: data.status,
|
||||
has_inventory_sources: group.has_inventory_sources,
|
||||
source: group.source
|
||||
});
|
||||
group.status = data.status;
|
||||
group.status_class = status.class;
|
||||
group.status_tooltip = status.tooltip;
|
||||
group.launch_tooltip = status.launch_tip;
|
||||
group.launch_class = status.launch_class;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.cancelUpdate = function (id) {
|
||||
GroupsCancelUpdate({ scope: $scope, id: id });
|
||||
};
|
||||
$scope.viewUpdateStatus = function (id) {
|
||||
ViewUpdateStatus({
|
||||
scope: $scope,
|
||||
group_id: id
|
||||
});
|
||||
};
|
||||
$scope.showFailedHosts = function() {
|
||||
$state.go('inventoryManage', {failed: true}, {reload: true});
|
||||
};
|
||||
$scope.scheduleGroup = function(id) {
|
||||
// Add this group's id to the array of group id's so that it gets
|
||||
// added to the breadcrumb trail
|
||||
var groupsArr = $stateParams.group ? $stateParams.group : [];
|
||||
groupsArr.push(id);
|
||||
$state.go('inventoryManage.editGroup.schedules', {group_id: id, group: groupsArr}, {reload: true});
|
||||
};
|
||||
// $scope.$parent governed by InventoryManageController, for unified multiSelect options
|
||||
$scope.$on('multiSelectList.selectionChanged', (event, selection) => {
|
||||
$scope.$parent.groupsSelected = selection.length > 0 ? true : false;
|
||||
$scope.$parent.groupsSelectedItems = selection.selectedItems;
|
||||
});
|
||||
|
||||
$scope.copyMoveGroup = function(id){
|
||||
$state.go('inventoryManage.copyMoveGroup', {group_id: id, groups: $stateParams.groups});
|
||||
};
|
||||
|
||||
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
|
||||
if (toState.name === "inventoryManage.editGroup") {
|
||||
$scope.rowBeingEdited = toParams.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
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();
|
||||
});
|
||||
|
||||
}];
|
@ -0,0 +1,236 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
export default
|
||||
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroupsList', 'InventoryUpdate',
|
||||
'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
|
||||
'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
|
||||
function($scope, $rootScope, $state, $stateParams, InventoryGroupsList, InventoryUpdate,
|
||||
GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath,
|
||||
GetSyncStatusMsg, GetHostsStatusMsg, Dataset, Find, qs, inventoryData){
|
||||
|
||||
let list = InventoryGroupsList;
|
||||
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
$scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc;
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/groups")
|
||||
.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;
|
||||
|
||||
// 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.editGroup") {
|
||||
$scope.rowBeingEdited = $state.params.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
|
||||
}
|
||||
|
||||
function buildStatusIndicators(group){
|
||||
if (group === undefined || group === null) {
|
||||
group = {};
|
||||
}
|
||||
|
||||
let group_status, hosts_status;
|
||||
|
||||
group_status = GetSyncStatusMsg({
|
||||
status: group.summary_fields.inventory_source.status,
|
||||
has_inventory_sources: group.has_inventory_sources,
|
||||
source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
|
||||
});
|
||||
hosts_status = GetHostsStatusMsg({
|
||||
active_failures: group.hosts_with_active_failures,
|
||||
total_hosts: group.total_hosts,
|
||||
inventory_id: $scope.inventory_id,
|
||||
group_id: group.id
|
||||
});
|
||||
_.assign(group,
|
||||
{status_class: group_status.class},
|
||||
{status_tooltip: group_status.tooltip},
|
||||
{launch_tooltip: group_status.launch_tip},
|
||||
{launch_class: group_status.launch_class},
|
||||
{group_schedule_tooltip: group_status.schedule_tip},
|
||||
{hosts_status_tip: hosts_status.tooltip},
|
||||
{hosts_status_class: hosts_status.class},
|
||||
{source: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.source : null},
|
||||
{status: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.status : null});
|
||||
}
|
||||
|
||||
$scope.groupSelect = function(id){
|
||||
var group = $stateParams.group === undefined ? [id] : _($stateParams.group).concat(id).value();
|
||||
$state.go('inventoryManage', {
|
||||
inventory_id: $stateParams.inventory_id,
|
||||
group: group,
|
||||
group_search: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
}
|
||||
}, {reload: true});
|
||||
};
|
||||
$scope.createGroup = function(){
|
||||
$state.go('inventoryManage.addGroup');
|
||||
};
|
||||
$scope.editGroup = function(id){
|
||||
$state.go('inventoryManage.editGroup', {group_id: id});
|
||||
};
|
||||
$scope.deleteGroup = function(group){
|
||||
$scope.toDelete = {};
|
||||
angular.extend($scope.toDelete, group);
|
||||
if($scope.toDelete.total_groups === 0 && $scope.toDelete.total_hosts === 0) {
|
||||
// This group doesn't have any child groups or hosts - the user is just trying to delete
|
||||
// the group
|
||||
$scope.deleteOption = "delete";
|
||||
}
|
||||
$('#group-delete-modal').modal('show');
|
||||
};
|
||||
$scope.confirmDelete = function(){
|
||||
|
||||
// Bind an even listener for the modal closing. Trying to $state.go() before the modal closes
|
||||
// will mean that these two things are running async and the modal may not finish closing before
|
||||
// the state finishes transitioning.
|
||||
$('#group-delete-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
|
||||
// Remove the event handler so that we don't end up with multiple bindings
|
||||
$('#group-delete-modal').off('hidden.bs.modal');
|
||||
// Reload the inventory manage page and show that the group has been removed
|
||||
$state.go('inventoryManage', null, {reload: true});
|
||||
});
|
||||
|
||||
switch($scope.deleteOption){
|
||||
case 'promote':
|
||||
GroupManageService.promote($scope.toDelete.id, $stateParams.inventory_id)
|
||||
.then(() => {
|
||||
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
|
||||
$state.go("inventoryManage", null, {reload: true});
|
||||
} else {
|
||||
$state.go($state.current, null, {reload: true});
|
||||
}
|
||||
$('#group-delete-modal').modal('hide');
|
||||
$('body').removeClass('modal-open');
|
||||
$('.modal-backdrop').remove();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
GroupManageService.delete($scope.toDelete.id).then(() => {
|
||||
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
|
||||
$state.go("inventoryManage", null, {reload: true});
|
||||
} else {
|
||||
$state.go($state.current, null, {reload: true});
|
||||
}
|
||||
$('#group-delete-modal').modal('hide');
|
||||
$('body').removeClass('modal-open');
|
||||
$('.modal-backdrop').remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
$scope.updateGroup = function(group) {
|
||||
GroupManageService.getInventorySource({group: group.id}).then(res =>InventoryUpdate({
|
||||
scope: $scope,
|
||||
group_id: group.id,
|
||||
url: res.data.results[0].related.update,
|
||||
group_name: group.name,
|
||||
group_source: res.data.results[0].source
|
||||
}));
|
||||
};
|
||||
|
||||
$scope.$on(`ws-jobs`, function(e, data){
|
||||
var group = Find({ list: $scope.groups, key: 'id', val: data.group_id });
|
||||
|
||||
if (group === undefined || group === null) {
|
||||
group = {};
|
||||
}
|
||||
|
||||
if(data.status === 'failed' || data.status === 'successful'){
|
||||
let path;
|
||||
if($stateParams && $stateParams.group && $stateParams.group.length > 0) {
|
||||
path = GetBasePath('groups') + _.last($stateParams.group) + '/children';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
path = GetBasePath('inventory') + $stateParams.inventory_id + '/root_groups';
|
||||
}
|
||||
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;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
});
|
||||
} else {
|
||||
var status = GetSyncStatusMsg({
|
||||
status: data.status,
|
||||
has_inventory_sources: group.has_inventory_sources,
|
||||
source: group.source
|
||||
});
|
||||
group.status = data.status;
|
||||
group.status_class = status.class;
|
||||
group.status_tooltip = status.tooltip;
|
||||
group.launch_tooltip = status.launch_tip;
|
||||
group.launch_class = status.launch_class;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.cancelUpdate = function (id) {
|
||||
GroupsCancelUpdate({ scope: $scope, id: id });
|
||||
};
|
||||
$scope.viewUpdateStatus = function (id) {
|
||||
ViewUpdateStatus({
|
||||
scope: $scope,
|
||||
group_id: id
|
||||
});
|
||||
};
|
||||
$scope.showFailedHosts = function() {
|
||||
$state.go('inventoryManage', {failed: true}, {reload: true});
|
||||
};
|
||||
$scope.scheduleGroup = function(id) {
|
||||
// Add this group's id to the array of group id's so that it gets
|
||||
// added to the breadcrumb trail
|
||||
var groupsArr = $stateParams.group ? $stateParams.group : [];
|
||||
groupsArr.push(id);
|
||||
$state.go('inventoryManage.editGroup.schedules', {group_id: id, group: groupsArr}, {reload: true});
|
||||
};
|
||||
// $scope.$parent governed by InventoryManageController, for unified multiSelect options
|
||||
$scope.$on('multiSelectList.selectionChanged', (event, selection) => {
|
||||
$scope.$parent.groupsSelected = selection.length > 0 ? true : false;
|
||||
$scope.$parent.groupsSelectedItems = selection.selectedItems;
|
||||
});
|
||||
|
||||
$scope.copyMoveGroup = function(id){
|
||||
$state.go('inventoryManage.copyMoveGroup', {group_id: id, groups: $stateParams.groups});
|
||||
};
|
||||
|
||||
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
|
||||
if (toState.name === "inventoryManage.editGroup") {
|
||||
$scope.rowBeingEdited = toParams.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
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();
|
||||
});
|
||||
|
||||
}];
|
@ -0,0 +1,79 @@
|
||||
<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>
|
@ -0,0 +1,165 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default {
|
||||
name: 'groups',
|
||||
iterator: 'group',
|
||||
editTitle: '{{ inventory.name }}',
|
||||
well: true,
|
||||
wellOverride: true,
|
||||
index: false,
|
||||
hover: true,
|
||||
multiSelect: true,
|
||||
trackBy: 'group.id',
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/root_groups/',
|
||||
|
||||
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: '+ 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"
|
||||
}
|
||||
}
|
||||
};
|
15
awx/ui/client/src/inventories/groups/list/main.js
Normal file
15
awx/ui/client/src/inventories/groups/list/main.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildGroupListState from './build-groups-list-state.factory';
|
||||
import controller from './groups-list.controller';
|
||||
import InventoryGroupsList from './inventory-groups.list';
|
||||
|
||||
export default
|
||||
angular.module('groupList', [])
|
||||
.factory('buildGroupListState', buildGroupListState)
|
||||
.value('InventoryGroupsList', InventoryGroupsList)
|
||||
.controller('GroupsListController', controller);
|
24
awx/ui/client/src/inventories/groups/main.js
Normal file
24
awx/ui/client/src/inventories/groups/main.js
Normal file
@ -0,0 +1,24 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import groupList from './list/main';
|
||||
import service from './groups.service';
|
||||
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';
|
||||
|
||||
export default
|
||||
angular.module('group', [
|
||||
groupList.name
|
||||
])
|
||||
.factory('GetHostsStatusMsg', GetHostsStatusMsg)
|
||||
.factory('GetSourceTypeOptions', GetSourceTypeOptions)
|
||||
.factory('GetSyncStatusMsg', GetSyncStatusMsg)
|
||||
.factory('GroupsCancelUpdate', GroupsCancelUpdate)
|
||||
.factory('ViewUpdateStatus', ViewUpdateStatus)
|
||||
.service('GroupManageService', service);
|
@ -4,11 +4,11 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
function HostsAdd($scope) {
|
||||
function HostsAdd() {
|
||||
|
||||
console.log('inside host add');
|
||||
|
||||
}
|
||||
|
||||
export default ['$scope', HostsAdd
|
||||
export default [ HostsAdd
|
||||
];
|
||||
|
@ -4,11 +4,11 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
function SmartInventoryEdit($scope) {
|
||||
function SmartInventoryEdit() {
|
||||
|
||||
console.log('inside smart inventory add');
|
||||
|
||||
}
|
||||
|
||||
export default ['$scope', SmartInventoryEdit
|
||||
export default [ SmartInventoryEdit
|
||||
];
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
@ -10,8 +10,9 @@
|
||||
* @description This form is for adding/editing an inventory
|
||||
*/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
export default ['i18n', 'buildGroupListState',
|
||||
function(i18n,buildGroupListState) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('NEW INVENTORY'),
|
||||
editTitle: '{{ inventory_name }}',
|
||||
@ -133,35 +134,10 @@ export default ['i18n', function(i18n) {
|
||||
},
|
||||
groups: {
|
||||
name: 'groups',
|
||||
// awToolTip: i18n._('Please save before assigning permissions'),
|
||||
// dataPlacement: 'top',
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/root_groups/',
|
||||
type: 'collection',
|
||||
include: "InventoryGroupsList",
|
||||
title: i18n._('Groups'),
|
||||
iterator: 'group',
|
||||
index: false,
|
||||
open: false,
|
||||
// search: {
|
||||
// order_by: 'username'
|
||||
// },
|
||||
actions: {
|
||||
add: {
|
||||
label: i18n._('Add'),
|
||||
ngClick: "$state.go('.add')",
|
||||
awToolTip: i18n._('Add a permission'),
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD',
|
||||
// ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
label: i18n._('Name'),
|
||||
// linkBase: 'users',
|
||||
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
|
||||
}
|
||||
}
|
||||
stateGeneratorFunction: buildGroupListState
|
||||
},
|
||||
hosts: {
|
||||
name: 'hosts',
|
||||
|
@ -5,6 +5,7 @@
|
||||
*************************************************/
|
||||
|
||||
import host from './hosts/main';
|
||||
import group from './groups/main';
|
||||
import inventoryAdd from './add/main';
|
||||
import inventoryEdit from './edit/main';
|
||||
import inventoryList from './list/main';
|
||||
@ -16,6 +17,7 @@ import InventoryManageService from './inventory-manage.service';
|
||||
export default
|
||||
angular.module('inventory', [
|
||||
host.name,
|
||||
group.name,
|
||||
inventoryAdd.name,
|
||||
inventoryEdit.name,
|
||||
inventoryList.name
|
||||
@ -175,7 +177,10 @@ angular.module('inventory', [
|
||||
controllers: {
|
||||
list: 'InventoryListController',
|
||||
add: 'InventoryAddController',
|
||||
edit: 'InventoryEditController'
|
||||
edit: 'InventoryEditController',
|
||||
related: {
|
||||
groups: 'GroupsListController'
|
||||
}
|
||||
},
|
||||
urls: {
|
||||
list: '/inventories'
|
||||
|
@ -170,7 +170,9 @@ export default ['$compile', 'Attr', 'Icon',
|
||||
}
|
||||
|
||||
if (options.mode !== 'lookup' && (list.well === undefined || list.well)) {
|
||||
html += `<div class="${list.name}List List-well">`;
|
||||
html += `<div class="${list.name}List`; //List-well">`;
|
||||
html += (!list.wellOverride) ? "List-well" : "";
|
||||
html += `">`;
|
||||
// List actions
|
||||
html += "<div class=\"List-actionHolder\">";
|
||||
html += "<div class=\"List-actions\">";
|
||||
|
@ -9,7 +9,8 @@
|
||||
* generateLookupNodes - Attaches to a form node. Builds an abstract '*.lookup' node with field-specific 'lookup.*' children e.g. {name: 'projects.add.lookup.organizations', ...}
|
||||
*/
|
||||
|
||||
export default ['$injector', '$stateExtender', '$log', 'i18n', function($injector, $stateExtender, $log, i18n) {
|
||||
export default ['$injector', '$stateExtender', '$log', 'i18n',
|
||||
function($injector, $stateExtender, $log, i18n) {
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
@ -557,7 +558,11 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
|
||||
|
||||
function buildListNodes(field) {
|
||||
let states = [];
|
||||
if(field.iterator === 'notification'){
|
||||
if(field.iterator === 'group'){
|
||||
states.push(field.stateGeneratorFunction(field, formStateDefinition, params));
|
||||
states = _.flatten(states);
|
||||
}
|
||||
else if(field.iterator === 'notification'){
|
||||
states.push(buildNotificationState(field));
|
||||
states = _.flatten(states);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user