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

Adding nested-groups (related tab) and completed jobs to inventories

This commit is contained in:
Jared Tabor 2017-04-20 22:54:43 -07:00 committed by Jared Tabor
parent 78ff5f5301
commit 0fa9aa6bcb
13 changed files with 779 additions and 110 deletions

View File

@ -0,0 +1,80 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import JobsListController from '../../jobs/jobs-list.controller';
export default ['InventoryCompletedJobsList', '$stateExtender', 'templateUrl', '$injector',
function(InventoryCompletedJobsList, $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}`,
name: `${formStateDefinition.name}.${list.iterator}s`,
url: `/${list.iterator}s`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
label: `${breadcrumbLabel}`
},
params: {
completed_job_search: {
value: {
or__job__inventory: '',
or__adhoccommand__inventory: '',
or__inventoryupdate__inventory_source__inventory: ''
},
squash: ''
}
},
views: {
'related': {
templateProvider: function(FormDefinition, GenerateForm) {
let html = GenerateForm.buildCollection({
mode: 'edit',
related: `${list.iterator}s`,
form: typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition
});
return html;
},
controller: JobsListController
}
},
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 });
}
$stateParams[`${list.iterator}_search`].or__job__inventory = $stateParams.inventory_id;
$stateParams[`${list.iterator}_search`].or__adhoccommand__inventory = $stateParams.inventory_id;
$stateParams[`${list.iterator}_search`].or__inventoryupdate__inventory_source__inventory = $stateParams.inventory_id;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
}
};
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,88 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
return {
// These tooltip fields are consumed to build disabled related tabs tooltips in the form > add view
awToolTip: i18n._('Please save and run a job to view'),
dataPlacement: 'top',
name: 'completed_jobs',
basePath: 'unified_jobs',
iterator: 'completed_job',
search: {
"or__job__inventory": ''
},
editTitle: i18n._('COMPLETED JOBS'),
index: false,
hover: true,
well: false,
emptyListText: i18n._('No completed jobs'),
fields: {
status: {
label: '',
columnClass: 'List-staticColumn--smallStatus',
awToolTip: "{{ completed_job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ completed_job.status_popover_title }}",
icon: 'icon-job-{{ completed_job.status }}',
iconOnly: true,
ngClick:"viewjobResults(completed_job)",
},
id: {
label: 'ID',
ngClick:"viewjobResults(completed_job)",
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ completed_job.status_tip }}",
dataPlacement: 'top'
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
ngClick: "viewjobResults(completed_job)",
awToolTip: "{{ completed_job.name | sanitize }}",
dataPlacement: 'top'
},
type: {
label: i18n._('Type'),
ngBind: 'completed_job.type_label',
link: false,
columnClass: "col-lg-2 col-md-2 hidden-sm hidden-xs",
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
columnClass: "col-lg-3 col-md-3 col-sm-3 hidden-xs",
key: true,
desc: true
}
},
actions: { },
fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
submit: {
icon: 'icon-rocket',
mode: 'all',
ngClick: 'relaunchJob($event, completed_job.id)',
awToolTip: i18n._('Relaunch using the same parameters'),
dataPlacement: 'top',
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start"
},
"delete": {
mode: 'all',
ngClick: 'deleteJob(completed_job.id)',
awToolTip: i18n._('Delete the job'),
dataPlacement: 'top',
ngShow: 'completed_job.summary_fields.user_capabilities.delete'
}
}
};}];

View File

@ -0,0 +1,13 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import list from './completed_jobs.list';
import buildInventoryCompletedJobsState from './build-inventory-completed-jobs-state.factory';
export default
angular.module('inventoryCompletedJobs', [])
.factory('InventoryCompletedJobsList', list)
.factory('buildInventoryCompletedJobsState', buildInventoryCompletedJobsState);

View File

@ -10,70 +10,88 @@
* @description This form is for adding/editing a Group on the inventory page * @description This form is for adding/editing a Group on the inventory page
*/ */
export default { export default ['i18n', 'nestedGroupListState',
addTitle: 'CREATE GROUP', function(i18n, nestedGroupListState){
editTitle: '{{ name }}', return {
showTitle: true, addTitle: 'CREATE GROUP',
name: 'group', editTitle: '{{ name }}',
basePath: 'groups', showTitle: true,
parent: 'inventories.edit.groups', name: 'group',
// the parent node this generated state definition tree expects to attach to basePath: 'groups',
stateTree: 'inventories', parent: 'inventories.edit.groups',
// form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab // the parent node this generated state definition tree expects to attach to
// this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit stateTree: 'inventories',
activeEditState: 'inventories.edit.groups.editGroup', // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab
detailsClick: "$state.go('inventories.edit.groups.editGroup')", // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit
well: false, activeEditState: 'inventories.edit.groups.edit',
fields: { detailsClick: "$state.go('inventories.edit.groups.edit')",
name: { well: false,
label: 'Name', tabs: true,
type: 'text', fields: {
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', name: {
required: true, label: 'Name',
tab: 'properties' 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'
}
}, },
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'
}
},
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()', ngClick: 'formCancel()',
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' 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)'
}
}, },
close: { related: {
ngClick: 'formCancel()', nested_groups: {
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' name: 'nested_groups',
}, ngClick: "$state.go('inventories.edit.groups.edit.nested_groups')",
save: { include: "NestedGroupListDefinition",
ngClick: 'formSave()', includeForm: "NestedGroupFormDefinition",
ngDisabled: true, title: i18n._('Groups'),
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' iterator: 'nested_group',
listState: nestedGroupListState,
// addState: buildGroupsAddState,
// editState: buildGroupsEditState
},
} }
} };
}; }];

View File

@ -31,17 +31,9 @@ export default {
name: { name: {
label: 'Groups', label: 'Groups',
key: true, key: true,
ngClick: "groupSelect(group.id)", ngClick: "editGroup(group.id)",
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6', columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6',
class: 'InventoryManage-breakWord', 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'}}"
} }
}, },

View File

@ -7,6 +7,7 @@
import groupList from './list/main'; import groupList from './list/main';
import groupAdd from './add/main'; import groupAdd from './add/main';
import groupEdit from './edit/main'; import groupEdit from './edit/main';
import nestedGroups from './nested-groups/main';
import groupFormDefinition from './groups.form'; import groupFormDefinition from './groups.form';
import groupListDefinition from './groups.list'; import groupListDefinition from './groups.list';
import service from './groups.service'; import service from './groups.service';
@ -20,9 +21,10 @@ export default
angular.module('group', [ angular.module('group', [
groupList.name, groupList.name,
groupAdd.name, groupAdd.name,
groupEdit.name groupEdit.name,
nestedGroups.name
]) ])
.value('GroupForm', groupFormDefinition) .factory('GroupForm', groupFormDefinition)
.value('GroupList', groupListDefinition) .value('GroupList', groupListDefinition)
.factory('GetHostsStatusMsg', GetHostsStatusMsg) .factory('GetHostsStatusMsg', GetHostsStatusMsg)
.factory('GetSourceTypeOptions', GetSourceTypeOptions) .factory('GetSourceTypeOptions', GetSourceTypeOptions)

View File

@ -0,0 +1,17 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import nestedGroupListState from './nested-groups-list-state.factory';
import nestedGroupListDefinition from './nested-groups.list';
import nestedGroupFormDefinition from './nested-groups.form';
import controller from './nested-groups-list.controller';
export default
angular.module('nestedGroups', [])
.factory('nestedGroupListState', nestedGroupListState)
.value('NestedGroupListDefinition', nestedGroupListDefinition)
.factory('NestedGroupFormDefinition', nestedGroupFormDefinition)
.controller('NestedGroupsListController', controller);

View File

@ -0,0 +1,224 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$state', '$stateParams', 'NestedGroupListDefinition', 'InventoryUpdate',
'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
function($scope, $rootScope, $state, $stateParams, NestedGroupListDefinition, InventoryUpdate,
GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath,
GetSyncStatusMsg, GetHostsStatusMsg, Dataset, Find, qs, inventoryData){
let list = NestedGroupListDefinition;
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 hosts_status;
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,
{hosts_status_tip: hosts_status.tooltip},
{hosts_status_class: hosts_status.class});
}
$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('inventories.edit.groups.add');
};
$scope.editGroup = function(id){
$state.go('inventories.edit.groups.edit', {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();
});
}];

View File

@ -0,0 +1,98 @@
/*************************************************
* 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 ['i18n', 'nestedGroupListState',
function(i18n, nestedGroupListState){
return {
addTitle: 'CREATE GROUP',
editTitle: '{{ name }}',
showTitle: true,
name: 'nested_group',
iterator: "nested_group",
basePath: 'groups',
parent: 'inventories.edit.groups',
// the parent node this generated state definition tree expects to attach to
stateTree: 'inventories',
// 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: 'inventories.edit.groups.edit',
detailsClick: "$state.go('inventories.edit.groups.edit')",
well: false,
tabs: true,
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'
}
},
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: {
nested_groups: {
name: 'related_groups',
ngClick: "$state.go('inventories.edit.groups.edit.related_groups')",
include: "RelatedGroupListDefinition",
includeForm: "RelatedGroupFormDefinition",
title: i18n._('Groups'),
iterator: 'related_group',
listState: nestedGroupListState,
// addState: buildGroupsAddState,
// editState: buildGroupsEditState
},
}
};
}];

View File

@ -0,0 +1,146 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default {
name: 'nested_groups',
iterator: 'nested_group',
editTitle: '{{ inventory.name }}',
well: true,
wellOverride: true,
index: false,
hover: true,
multiSelect: true,
trackBy: 'nested_group.id',
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/root_groups/',
fields: {
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)",
ngClick: "editGroup(group.id)",
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6',
class: 'InventoryManage-breakWord',
}
},
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

@ -13,10 +13,29 @@
export default ['i18n', 'buildGroupsListState', 'buildGroupsAddState', export default ['i18n', 'buildGroupsListState', 'buildGroupsAddState',
'buildGroupsEditState', 'buildHostListState', 'buildHostAddState', 'buildGroupsEditState', 'buildHostListState', 'buildHostAddState',
'buildHostEditState', 'buildSourcesListState', 'buildSourcesAddState', 'buildHostEditState', 'buildSourcesListState', 'buildSourcesAddState',
'buildSourcesEditState', 'buildSourcesEditState', 'buildInventoryCompletedJobsState',
'InventoryCompletedJobsList',
function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState, function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState,
buildHostListState, buildHostAddState, buildHostEditState, buildHostListState, buildHostAddState, buildHostEditState,
buildSourcesListState, buildSourcesAddState,buildSourcesEditState) { buildSourcesListState, buildSourcesAddState,buildSourcesEditState,
buildInventoryCompletedJobsState, InventoryCompletedJobsList) {
var completed_jobs_object = {
name: 'completed_jobs',
index: false,
basePath: "unified_jobs",
include: "InventoryCompletedJobsList",
title: i18n._('Completed Jobs'),
iterator: 'completed_job',
generateList: true,
listState: buildInventoryCompletedJobsState,
search: {
"or__job__inventory": ''
}
};
let clone = _.clone(InventoryCompletedJobsList);
completed_jobs_object = angular.extend(clone, completed_jobs_object);
return { return {
addTitle: i18n._('NEW INVENTORY'), addTitle: i18n._('NEW INVENTORY'),
@ -96,7 +115,7 @@ function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState,
name: 'permissions', name: 'permissions',
awToolTip: i18n._('Please save before assigning permissions'), awToolTip: i18n._('Please save before assigning permissions'),
dataPlacement: 'top', dataPlacement: 'top',
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/', basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/access_list/',
type: 'collection', type: 'collection',
title: i18n._('Permissions'), title: i18n._('Permissions'),
iterator: 'permission', iterator: 'permission',
@ -165,39 +184,7 @@ function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState,
addState: buildSourcesAddState, addState: buildSourcesAddState,
editState: buildSourcesEditState editState: buildSourcesEditState
}, },
//this is a placeholder for when we're ready for completed jobs completed_jobs: completed_jobs_object
completed_jobs: {
name: 'completed_jobs',
// awToolTip: i18n._('Please save before assigning permissions'),
// dataPlacement: 'top',
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/completed_jobs/',
type: 'collection',
title: i18n._('Completed Jobs'),
iterator: 'completed_job',
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: {
name: {
label: i18n._('Name'),
// linkBase: 'users',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
}
}
}
} }
};}]; };}];

View File

@ -8,6 +8,7 @@ import host from './hosts/main';
import group from './groups/main'; import group from './groups/main';
import sources from './sources/main'; import sources from './sources/main';
import relatedHost from './related-hosts/main'; import relatedHost from './related-hosts/main';
import inventoryCompletedJobs from './completed_jobs/main';
import inventoryAdd from './add/main'; import inventoryAdd from './add/main';
import inventoryEdit from './edit/main'; import inventoryEdit from './edit/main';
import inventoryList from './list/main'; import inventoryList from './list/main';
@ -22,6 +23,7 @@ angular.module('inventory', [
group.name, group.name,
sources.name, sources.name,
relatedHost.name, relatedHost.name,
inventoryCompletedJobs.name,
inventoryAdd.name, inventoryAdd.name,
inventoryEdit.name, inventoryEdit.name,
inventoryList.name inventoryList.name

View File

@ -576,6 +576,7 @@ function($injector, $stateExtender, $log, i18n) {
if(field.includeForm){ if(field.includeForm){
let form = field.includeForm ? $injector.get(field.includeForm) : field; let form = field.includeForm ? $injector.get(field.includeForm) : field;
states.push(that.generateLookupNodes(form, formState)); states.push(that.generateLookupNodes(form, formState));
states.push(that.generateFormListDefinitions(form, formState, params));
} }
states = _.flatten(states); states = _.flatten(states);
} }
@ -678,6 +679,7 @@ function($injector, $stateExtender, $log, i18n) {
if (field.search) { if (field.search) {
state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search); state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search);
} }
return state; return state;
} }
return _(form.related).map(buildListNodes).flatten().value(); return _(form.related).map(buildListNodes).flatten().value();